Skip to main content

qualia_core_db/specialized_libs/
symbolic_series.rs

1//! **Taylor / Maclaurin series** (Calculus plan §4.2) — the series of a CAS expression
2//! about a point, by repeated symbolic differentiation. `cₖ = f⁽ᵏ⁾(a)/k!`.
3
4use super::symbolic_algebra::{differentiate, simplify, Expr};
5
6/// Taylor coefficients `[c₀, …, c_order]` of `f` about `x = a`. `None` if any derivative
7/// fails to evaluate at `a` (e.g. a singularity there).
8pub fn taylor_coefficients(f: &Expr, x: &str, a: f64, order: usize) -> Option<Vec<f64>> {
9    let mut coeffs = Vec::with_capacity(order + 1);
10    let mut deriv = f.clone();
11    let mut factorial = 1.0;
12    for k in 0..=order {
13        if k > 0 {
14            deriv = simplify(&differentiate(&deriv, x));
15            factorial *= k as f64;
16        }
17        let mut env = std::collections::HashMap::new();
18        env.insert(x.to_string(), a);
19        coeffs.push(deriv.eval(&env)? / factorial);
20    }
21    Some(coeffs)
22}
23
24/// Evaluate the truncated Taylor polynomial `Σ cₖ (x−a)ᵏ` at `x`.
25pub fn taylor_eval(coeffs: &[f64], a: f64, x: f64) -> f64 {
26    let d = x - a;
27    coeffs.iter().rev().fold(0.0, |acc, &c| acc * d + c)
28}
29
30#[cfg(test)]
31mod tests {
32    use super::super::symbolic_algebra::{add, c, mul, pow, sqrt, var};
33    use super::*;
34
35    #[test]
36    fn series_of_polynomial_is_exact() {
37        // f = x³ − 2x + 1 about 0 → coeffs [1, −2, 0, 1], higher are 0.
38        let f = add(
39            super::super::symbolic_algebra::sub(pow(var("x"), 3), mul(c(2.0), var("x"))),
40            c(1.0),
41        );
42        let coeffs = taylor_coefficients(&f, "x", 0.0, 5).unwrap();
43        assert!((coeffs[0] - 1.0).abs() < 1e-9);
44        assert!((coeffs[1] + 2.0).abs() < 1e-9);
45        assert!(coeffs[2].abs() < 1e-9);
46        assert!((coeffs[3] - 1.0).abs() < 1e-9);
47        assert!(coeffs[4].abs() < 1e-9 && coeffs[5].abs() < 1e-9);
48    }
49
50    #[test]
51    fn series_of_sqrt_about_one() {
52        // √x about 1: c₀=1, c₁=1/2, c₂=−1/8, c₃=1/16.
53        let coeffs = taylor_coefficients(&sqrt(var("x")), "x", 1.0, 3).unwrap();
54        assert!((coeffs[0] - 1.0).abs() < 1e-9);
55        assert!((coeffs[1] - 0.5).abs() < 1e-9);
56        assert!((coeffs[2] + 0.125).abs() < 1e-9);
57        assert!((coeffs[3] - 0.0625).abs() < 1e-9);
58        // The truncated series approximates √x near 1.
59        let approx = taylor_eval(&coeffs, 1.0, 1.1);
60        assert!((approx - 1.1_f64.sqrt()).abs() < 1e-4);
61    }
62
63    #[test]
64    fn singularity_fails_closed() {
65        // 1/x has no Taylor series about 0.
66        let f = super::super::symbolic_algebra::div(c(1.0), var("x"));
67        assert!(taylor_coefficients(&f, "x", 0.0, 3).is_none());
68    }
69}