use symplex::prelude::*;
fn assert_coeff_eq(series: &symplex::formal_series::FormalPowerSeries, k: usize, p: i64, q: i64) {
use num_bigint::BigInt;
use num_rational::Ratio;
let actual = series.coefficient_rational(k).unwrap_or_else(|| {
panic!(
"coefficient a_{k} is not rational: {}",
series.coefficient(k)
)
});
let expected = Ratio::new(BigInt::from(p), BigInt::from(q));
assert_eq!(
actual,
expected,
"coefficient a_{k} should be {p}/{q}, got {actual}",
k = k
);
}
fn assert_coeff_zero(series: &symplex::formal_series::FormalPowerSeries, k: usize) {
assert!(
series.coefficient_rational(k).is_some_and(|r| r.is_zero()),
"coefficient a_{k} should be 0, got {}",
series.coefficient(k),
k = k
);
}
use num_traits::Zero;
#[test]
fn fps_exp_x_has_closed_form() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.exp().fps_maclaurin(&x);
assert!(series.has_closed_form());
}
#[test]
fn fps_exp_x_coefficients() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.exp().fps_maclaurin(&x);
assert_coeff_eq(&series, 0, 1, 1);
assert_coeff_eq(&series, 1, 1, 1);
assert_coeff_eq(&series, 2, 1, 2);
assert_coeff_eq(&series, 3, 1, 6);
assert_coeff_eq(&series, 4, 1, 24);
assert_coeff_eq(&series, 5, 1, 120);
}
#[test]
fn fps_exp_x_coefficient_5_is_1_over_120() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.exp().fps_maclaurin(&x);
assert_coeff_eq(&series, 5, 1, 120);
}
#[test]
fn fps_sin_x_has_closed_form() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.sin().fps_maclaurin(&x);
assert!(series.has_closed_form());
}
#[test]
fn fps_sin_x_odd_coefficients() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.sin().fps_maclaurin(&x);
assert_coeff_eq(&series, 1, 1, 1);
assert_coeff_eq(&series, 3, -1, 6);
assert_coeff_eq(&series, 5, 1, 120);
assert_coeff_eq(&series, 7, -1, 5040);
}
#[test]
fn fps_sin_x_even_coefficients_are_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.sin().fps_maclaurin(&x);
assert_coeff_zero(&series, 0);
assert_coeff_zero(&series, 2);
assert_coeff_zero(&series, 4);
assert_coeff_zero(&series, 6);
}
#[test]
fn fps_cos_x_even_coefficients() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.cos().fps_maclaurin(&x);
assert_coeff_eq(&series, 0, 1, 1);
assert_coeff_eq(&series, 2, -1, 2);
assert_coeff_eq(&series, 4, 1, 24);
assert_coeff_eq(&series, 6, -1, 720);
}
#[test]
fn fps_cos_x_odd_coefficients_are_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.cos().fps_maclaurin(&x);
assert_coeff_zero(&series, 1);
assert_coeff_zero(&series, 3);
assert_coeff_zero(&series, 5);
}
#[test]
fn fps_geometric_series_all_ones() {
let ctx = Context::new();
let x = ctx.symbol("x");
let one = ctx.int(1);
let zero = ctx.int(0);
let expr = (&one - &x).powi(-1);
let series = expr.fps(&x, &zero);
for k in 0..10 {
assert_coeff_eq(&series, k, 1, 1);
}
}
#[test]
fn fps_ln_1_plus_x_coefficients() {
let ctx = Context::new();
let x = ctx.symbol("x");
let one = ctx.int(1);
let zero = ctx.int(0);
let expr = (&one + &x).ln();
let series = expr.fps(&x, &zero);
assert_coeff_zero(&series, 0);
assert_coeff_eq(&series, 1, 1, 1);
assert_coeff_eq(&series, 2, -1, 2);
assert_coeff_eq(&series, 3, 1, 3);
assert_coeff_eq(&series, 4, -1, 4);
assert_coeff_eq(&series, 5, 1, 5);
}
#[test]
fn fps_binomial_sqrt_coefficients() {
let ctx = Context::new();
let x = ctx.symbol("x");
let one = ctx.int(1);
let zero = ctx.int(0);
let half = ctx.rational(1, 2);
let expr = (&one + &x).pow(&half);
let series = expr.fps(&x, &zero);
assert!(series.has_closed_form());
assert_coeff_eq(&series, 0, 1, 1);
assert_coeff_eq(&series, 1, 1, 2);
assert_coeff_eq(&series, 2, -1, 8);
assert_coeff_eq(&series, 3, 1, 16);
}
#[test]
fn fps_atan_x_odd_coefficients() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.atan().fps_maclaurin(&x);
assert_coeff_zero(&series, 0);
assert_coeff_eq(&series, 1, 1, 1);
assert_coeff_zero(&series, 2);
assert_coeff_eq(&series, 3, -1, 3);
assert_coeff_zero(&series, 4);
assert_coeff_eq(&series, 5, 1, 5);
assert_coeff_zero(&series, 6);
assert_coeff_eq(&series, 7, -1, 7);
}
#[test]
fn fps_exp_truncate_5_terms() {
let ctx = Context::new();
let x = ctx.symbol("x");
let zero = ctx.int(0);
let exp_x = x.exp();
let series = exp_x.fps(&x, &zero);
let truncated = series.truncate(5).expand().eval();
let mac = exp_x.maclaurin(&x, 5).expand().eval();
assert_eq!(
format!("{truncated}"),
format!("{mac}"),
"FPS truncate should match Maclaurin"
);
let mac_s = format!("{mac}");
assert!(mac_s.contains("x"), "maclaurin should have x: {mac_s}");
assert!(mac_s.contains("x^2"), "maclaurin should have x^2: {mac_s}");
assert!(mac_s.contains("x^3"), "maclaurin should have x^3: {mac_s}");
assert!(mac_s.contains("x^4"), "maclaurin should have x^4: {mac_s}");
}
#[test]
fn fps_coefficient_access_exp_5() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.exp().fps_maclaurin(&x);
use num_bigint::BigInt;
use num_rational::Ratio;
let c5 = series.coefficient_rational(5);
assert_eq!(c5, Some(Ratio::new(BigInt::from(1), BigInt::from(120))));
}
#[test]
fn fps_sinh_x_coefficients() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.sinh().fps_maclaurin(&x);
assert!(series.has_closed_form());
assert_coeff_zero(&series, 0);
assert_coeff_eq(&series, 1, 1, 1);
assert_coeff_zero(&series, 2);
assert_coeff_eq(&series, 3, 1, 6);
assert_coeff_zero(&series, 4);
assert_coeff_eq(&series, 5, 1, 120);
}
#[test]
fn fps_cosh_x_coefficients() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.cosh().fps_maclaurin(&x);
assert!(series.has_closed_form());
assert_coeff_eq(&series, 0, 1, 1);
assert_coeff_zero(&series, 1);
assert_coeff_eq(&series, 2, 1, 2);
assert_coeff_zero(&series, 3);
assert_coeff_eq(&series, 4, 1, 24);
}
#[test]
fn fps_non_elementary_falls_back_to_truncated() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.exp() + &x.sin();
let series = expr.fps_maclaurin(&x);
assert!(!series.has_closed_form());
use num_bigint::BigInt;
use num_rational::Ratio;
let c0 = series.coefficient_rational(0);
assert_eq!(c0, Some(Ratio::from_integer(BigInt::from(1))));
assert_eq!(series.coefficient(1).to_string(), "2");
}