use symplex::prelude::*;
fn assert_ftc_parametric(integrand: &Ex, var: &Ex, param_subs: &[(&Ex, f64)], label: &str) {
let ctx = integrand.context();
let anti = integrand.integrate(var);
let s = format!("{anti}");
assert!(
!s.contains("Integral"),
"{label}: integration returned unevaluated: {s}"
);
let deriv = anti.diff(var);
let test_x = 1.0_f64;
let x_val = ctx.rational(1, 1);
let mut integrand_sub = integrand.clone();
let mut deriv_sub = deriv.clone();
for &(param, val) in param_subs {
let val_expr = ctx.int(val as i64);
integrand_sub = integrand_sub.subs(param, &val_expr);
deriv_sub = deriv_sub.subs(param, &val_expr);
}
integrand_sub = integrand_sub.subs(var, &x_val);
deriv_sub = deriv_sub.subs(var, &x_val);
if let (Ok(orig), Ok(diff)) = (integrand_sub.eval_f64(), deriv_sub.eval_f64()) {
if orig.is_finite() && diff.is_finite() {
let err = (orig - diff).abs();
let tol = 1e-8 * orig.abs().max(1.0);
assert!(
err < tol,
"{label}: FTC failed at x={test_x} — integrand={orig:.12}, deriv={diff:.12}, err={err:.2e}",
);
}
} else {
panic!("{label}: eval_f64 failed for integrand or derivative");
}
}
#[test]
fn integrate_sin_ax() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let ax = &a * &x;
let integrand = ax.sin();
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
assert!(s.contains("cos"), "should contain cos: {s}");
assert_ftc_parametric(&integrand, &x, &[(&a, 2.0)], "∫sin(a*x)dx");
}
#[test]
fn integrate_sin_ax_numeric_check() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let ax = &a * &x;
let anti = ax.sin().integrate(&x);
let val = anti
.subs(&a, &ctx.int(2))
.subs(&x, &ctx.int(1))
.eval_f64()
.expect("should evaluate");
let expected = -(2.0_f64).cos() / 2.0;
assert!(
(val - expected).abs() < 1e-10,
"∫sin(2x)dx at x=1: got {val}, expected {expected}"
);
}
#[test]
fn integrate_cos_ax() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let ax = &a * &x;
let integrand = ax.cos();
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
assert!(s.contains("sin"), "should contain sin: {s}");
assert_ftc_parametric(&integrand, &x, &[(&a, 3.0)], "∫cos(a*x)dx");
}
#[test]
fn integrate_exp_ax() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let ax = &a * &x;
let integrand = ax.exp();
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
assert!(s.contains("exp"), "should contain exp: {s}");
assert_ftc_parametric(&integrand, &x, &[(&a, 2.0)], "∫exp(a*x)dx");
}
#[test]
fn integrate_exp_ax_numeric_check() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let ax = &a * &x;
let anti = ax.exp().integrate(&x);
let val = anti
.subs(&a, &ctx.int(3))
.subs(&x, &ctx.int(1))
.eval_f64()
.expect("should evaluate");
let expected = (3.0_f64).exp() / 3.0;
assert!(
(val - expected).abs() < 1e-6,
"∫exp(3x)dx at x=1: got {val}, expected {expected}"
);
}
#[test]
fn integrate_inv_x2_plus_a2() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let x2 = x.powi(2);
let a2 = a.powi(2);
let denom = &x2 + &a2;
let integrand = denom.powi(-1);
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
assert!(s.contains("atan"), "should contain atan: {s}");
let val = anti
.subs(&a, &ctx.int(2))
.subs(&x, &ctx.int(1))
.eval_f64()
.expect("should evaluate");
let expected = 0.5 * (0.5_f64).atan();
assert!(
(val - expected).abs() < 1e-10,
"∫1/(x²+4)dx at x=1: got {val}, expected {expected}"
);
}
#[test]
fn integrate_sin_2x_plus_3() {
let ctx = Context::new();
let x = ctx.symbol("x");
let two = ctx.int(2);
let three = ctx.int(3);
let inner = &(&two * &x) + &three;
let integrand = inner.sin();
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
let deriv = anti.diff(&x);
let test_pt = ctx.rational(7, 10);
if let (Ok(o), Ok(d)) = (
integrand.subs(&x, &test_pt).eval_f64(),
deriv.subs(&x, &test_pt).eval_f64(),
) {
let err = (o - d).abs();
assert!(err < 1e-8, "FTC for ∫sin(2x+3)dx: integrand={o}, deriv={d}");
}
}
#[test]
fn integrate_a_times_sin_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let integrand = &a * &x.sin();
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
let val = anti
.subs(&a, &ctx.int(3))
.subs(&x, &ctx.int(1))
.eval_f64()
.expect("should evaluate");
let expected = 3.0 * (-(1.0_f64).cos());
assert!(
(val - expected).abs() < 1e-10,
"∫a·sin(x)dx at a=3,x=1: got {val}, expected {expected}"
);
}
#[test]
fn integrate_exp_ax_plus_b() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let b = ctx.symbol("b");
let inner = &(&a * &x) + &b;
let integrand = inner.exp();
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
assert!(s.contains("exp"), "should contain exp: {s}");
let val = anti
.subs(&a, &ctx.int(2))
.subs(&b, &ctx.int(1))
.subs(&x, &ctx.int(1))
.eval_f64()
.expect("should evaluate");
let expected = (3.0_f64).exp() / 2.0;
assert!(
(val - expected).abs() < 1e-6,
"∫exp(2x+1)dx at x=1: got {val}, expected {expected}"
);
}
#[test]
fn integrate_tan_ax() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let ax = &a * &x;
let integrand = ax.tan();
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
assert_ftc_parametric(&integrand, &x, &[(&a, 2.0)], "∫tan(a*x)dx");
}
#[test]
fn integrate_sinh_ax() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let ax = &a * &x;
let integrand = ax.sinh();
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
assert_ftc_parametric(&integrand, &x, &[(&a, 2.0)], "∫sinh(a*x)dx");
}
#[test]
fn integrate_cosh_ax() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let ax = &a * &x;
let integrand = ax.cosh();
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
assert_ftc_parametric(&integrand, &x, &[(&a, 3.0)], "∫cosh(a*x)dx");
}
#[test]
fn integrate_ax_plus_b_cubed() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let b = ctx.symbol("b");
let inner = &(&a * &x) + &b;
let integrand = inner.powi(3);
let anti = integrand.integrate(&x);
let s = format!("{anti}");
assert!(!s.contains("Integral"), "should not be unevaluated: {s}");
let val = anti
.subs(&a, &ctx.int(2))
.subs(&b, &ctx.int(1))
.subs(&x, &ctx.int(1))
.eval_f64()
.expect("should evaluate");
let expected = 81.0 / 8.0;
assert!(
(val - expected).abs() < 1e-10,
"∫(2x+1)³dx at x=1: got {val}, expected {expected}"
);
}