use crate::error::{MathError, Result};
use crate::eval::{eval, Context};
use crate::expr::Expr;
use crate::parser::Parser;
use crate::simplify::simplify;
use crate::symbolic::differentiate;
pub fn taylor_series(f: &Expr, var: &str, a: f64, order: usize) -> Result<Expr> {
if order == 0 {
return Err(MathError::InvalidArgument("taylor_series: order must be > 0".into()));
}
let ctx = Context::standard();
let mut terms: Vec<Expr> = Vec::with_capacity(order);
let mut current = f.clone();
for n in 0..order {
let mut eval_ctx = ctx.clone();
eval_ctx.set(var, a);
let coeff = eval(¤t, &eval_ctx).unwrap_or(0.0);
if coeff.abs() < 1e-15 {
if n + 1 < order {
current = differentiate(¤t, var)?;
}
continue;
}
let factorial_n = factorial(n);
let normalized = coeff / factorial_n as f64;
let x_minus_a = if a == 0.0 {
Expr::var(var)
} else {
Expr::sub(Expr::var(var), Expr::num(a))
};
let power = if n == 0 {
Expr::num(1.0)
} else if n == 1 {
x_minus_a
} else {
Expr::pow(x_minus_a, Expr::num(n as f64))
};
let term = if normalized == 1.0 {
power
} else if normalized == -1.0 {
Expr::neg(power)
} else {
Expr::mul(Expr::num(normalized), power)
};
terms.push(term);
if n + 1 < order {
current = differentiate(¤t, var)?;
}
}
if terms.is_empty() {
return Ok(Expr::num(0.0));
}
let mut result = terms[0].clone();
for t in &terms[1..] {
result = Expr::add(result, t.clone());
}
Ok(simplify(&result))
}
pub fn taylor_series_str(src: &str, var: &str, a: f64, order: usize) -> Result<Expr> {
let f = Parser::parse(src)?;
taylor_series(&f, var, a, order)
}
fn factorial(n: usize) -> u64 {
let mut r: u64 = 1;
for i in 2..=n {
r *= i as u64;
}
r
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() < eps
}
fn eval_at(e: &Expr, x: f64) -> f64 {
let mut ctx = Context::standard();
ctx.set("x", x);
eval(e, &ctx).unwrap_or(f64::NAN)
}
#[test]
fn taylor_exp_around_zero() {
let series = taylor_series_str("exp(x)", "x", 0.0, 5).unwrap();
for &x in &[0.0, 0.1, 0.5] {
let approx = eval_at(&series, x);
let exact = x.exp();
assert!(close(approx, exact, 1e-3), "at x={}: got {} want {}", x, approx, exact);
}
}
#[test]
fn taylor_sin_around_zero() {
let series = taylor_series_str("sin(x)", "x", 0.0, 7).unwrap();
for &x in &[0.0, 0.1, 0.5] {
let approx = eval_at(&series, x);
let exact = x.sin();
assert!(close(approx, exact, 1e-4), "at x={}: got {} want {}", x, approx, exact);
}
}
#[test]
fn taylor_cos_around_zero() {
let series = taylor_series_str("cos(x)", "x", 0.0, 6).unwrap();
for &x in &[0.0, 0.1, 0.5] {
let approx = eval_at(&series, x);
let exact = x.cos();
assert!(close(approx, exact, 1e-4), "at x={}: got {} want {}", x, approx, exact);
}
}
#[test]
fn taylor_polynomial_exact() {
let series = taylor_series_str("x^2 + 3*x + 2", "x", 1.0, 4).unwrap();
for &x in &[-1.0, 0.0, 1.0, 2.0, 5.0] {
let approx = eval_at(&series, x);
let exact = x * x + 3.0 * x + 2.0;
assert!(close(approx, exact, 1e-10), "at x={}: got {} want {}", x, approx, exact);
}
}
#[test]
fn taylor_around_nonzero() {
let series = taylor_series_str("exp(x)", "x", 1.0, 6).unwrap();
for &x in &[0.8, 1.0, 1.2] {
let approx = eval_at(&series, x);
let exact = x.exp();
assert!(close(approx, exact, 1e-3), "at x={}: got {} want {}", x, approx, exact);
}
}
}