use symplex::finite_diff::{apply_finite_diff, equispaced_grid, finite_diff_weights};
use symplex::prelude::*;
fn strs(v: &[Ex]) -> Vec<String> {
v.iter().map(|e| e.to_string()).collect()
}
#[test]
fn differentiate_finite_replaces_derivative_node() {
let ctx = Context::new();
let x = ctx.symbol("x");
let h = ctx.symbol("h");
let stencil = equispaced_grid(&x, &h, 1);
let expr = x.powi(2).formal_diff(&x);
let finite = expr.differentiate_finite(&x, &stencil, 0);
let s = format!("{finite}");
assert!(
!s.contains("Derivative"),
"should not contain unevaluated Derivative: {s}"
);
assert!(s.contains('h'), "should contain step size symbol h: {s}");
assert_eq!(finite.expand().to_string(), "2*x");
}
#[test]
fn differentiate_finite_x_cubed() {
let ctx = Context::new();
let x = ctx.symbol("x");
let h = ctx.symbol("h");
let stencil = equispaced_grid(&x, &h, 1);
let expr = x.powi(3).formal_diff(&x);
let finite = expr.differentiate_finite(&x, &stencil, 0);
let s = format!("{finite}");
assert!(
!s.contains("Derivative"),
"should not contain Derivative: {s}"
);
assert_eq!(finite.expand().to_string(), "h^2 + 3*x^2");
}
#[test]
fn differentiate_finite_sin_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let h = ctx.symbol("h");
let stencil = equispaced_grid(&x, &h, 1);
let expr = x.sin().formal_diff(&x);
let finite = expr.differentiate_finite(&x, &stencil, 0);
let s = format!("{finite}");
assert!(
!s.contains("Derivative"),
"should not contain Derivative: {s}"
);
assert!(s.contains('h'), "should contain h: {s}");
assert!(s.contains("sin"), "should reference sin: {s}");
let v = finite
.subs_i64(&x, 1)
.subs(&h, &ctx.rational(1, 1000))
.eval_f64()
.unwrap();
assert!((v - 1f64.cos()).abs() < 1e-6, "{v}");
}
#[test]
fn differentiate_finite_preserves_non_derivative_expr() {
let ctx = Context::new();
let x = ctx.symbol("x");
let h = ctx.symbol("h");
let stencil = equispaced_grid(&x, &h, 1);
let expr = x.powi(2);
let finite = expr.differentiate_finite(&x, &stencil, 0);
assert_eq!(
format!("{expr}"),
format!("{finite}"),
"non-derivative expression should be unchanged at order 0"
);
}
#[test]
fn differentiate_finite_direct_order() {
let ctx = Context::new();
let x = ctx.symbol("x");
let h = ctx.symbol("h");
let stencil = equispaced_grid(&x, &h, 1);
let d2 = x.powi(4).differentiate_finite(&x, &stencil, 2).expand();
assert_eq!(d2.to_string(), "2*h^2 + 12*x^2");
}
#[test]
fn forward_diff_weights_two_points() {
let ctx = Context::new();
let w = finite_diff_weights(1, &[ctx.int(0), ctx.int(1)], &ctx.int(0));
assert_eq!(strs(&w), ["-1", "1"]);
}
#[test]
fn central_diff_first_derivative_weights() {
let ctx = Context::new();
let grid = [ctx.int(-1), ctx.int(0), ctx.int(1)];
let w = finite_diff_weights(1, &grid, &ctx.int(0));
assert_eq!(strs(&w), ["-1/2", "0", "1/2"]);
}
#[test]
fn central_diff_second_derivative_weights() {
let ctx = Context::new();
let grid = [ctx.int(-1), ctx.int(0), ctx.int(1)];
let w = finite_diff_weights(2, &grid, &ctx.int(0));
assert_eq!(strs(&w), ["1", "-2", "1"]);
}
#[test]
fn four_point_forward_first_deriv_weights() {
let ctx = Context::new();
let grid = [ctx.int(0), ctx.int(1), ctx.int(2), ctx.int(3)];
let w = finite_diff_weights(1, &grid, &ctx.int(0));
assert_eq!(strs(&w), ["-11/6", "3", "-3/2", "1/3"]);
}
#[test]
fn symbolic_grid_weights_are_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let h = ctx.symbol("h");
let grid = equispaced_grid(&x, &h, 2);
let w = finite_diff_weights(1, &grid, &x);
let scaled: Vec<String> = w
.iter()
.map(|wi| (wi * &h).simplify().to_string())
.collect();
assert_eq!(scaled, ["1/12", "-2/3", "0", "2/3", "-1/12"]);
}
#[test]
fn apply_finite_diff_quadratic_first_deriv_at_zero() {
let ctx = Context::new();
let xs = [ctx.int(-1), ctx.int(0), ctx.int(1)];
let ys = [ctx.int(1), ctx.int(0), ctx.int(1)];
let r = apply_finite_diff(1, &xs, &ys, &ctx.int(0)).unwrap();
assert_eq!(r.to_string(), "0");
}
#[test]
fn apply_finite_diff_quadratic_second_deriv() {
let ctx = Context::new();
let xs = [ctx.int(-1), ctx.int(0), ctx.int(1)];
let ys = [ctx.int(1), ctx.int(0), ctx.int(1)];
let r = apply_finite_diff(2, &xs, &ys, &ctx.int(0)).unwrap();
assert_eq!(r.to_string(), "2");
}
#[test]
fn apply_finite_diff_linear_first_deriv() {
let ctx = Context::new();
let xs = [ctx.int(0), ctx.int(1)];
let ys = [ctx.int(1), ctx.int(4)];
let r = apply_finite_diff(1, &xs, &ys, &ctx.int(0)).unwrap();
assert_eq!(r.to_string(), "3");
}
#[test]
fn apply_finite_diff_rejects_mismatched_lengths() {
let ctx = Context::new();
let xs = [ctx.int(0), ctx.int(1)];
let ys = [ctx.int(1)];
assert!(matches!(
apply_finite_diff(1, &xs, &ys, &ctx.int(0)),
Err(SymplexError::InvalidArgument { .. })
));
}
#[test]
fn equispaced_grid_correct_count() {
let ctx = Context::new();
let h = ctx.symbol("h");
let grid = equispaced_grid(&ctx.int(0), &h, 2);
assert_eq!(grid.len(), 5);
assert_eq!(strs(&grid), ["-2*h", "-h", "0", "h", "2*h"]);
}
#[test]
fn equispaced_grid_single_point() {
let ctx = Context::new();
let h = ctx.symbol("h");
let grid = equispaced_grid(&ctx.int(0), &h, 0);
assert_eq!(grid.len(), 1);
}