mod common;
use symplex::prelude::*;
fn assert_exprs_equal_in_domain(a: &Ex, b: &Ex, var: &Ex, label: &str) {
let ctx = a.context();
let points: &[(i64, i64)] = &[(1, 4), (1, 2), (3, 4), (1, 1), (3, 2)];
let mut checked = 0;
for &(p, q) in points {
let pt = ctx.rational(p, q);
let va = a.subs(var, &pt).eval().eval_f64();
let vb = b.subs(var, &pt).eval().eval_f64();
if let (Ok(av), Ok(bv)) = (va, vb) {
checked += 1;
let scale = av.abs().max(bv.abs()).max(1.0);
assert!(
(av - bv).abs() < 1e-8 * scale,
"{label} at {var}={p}/{q}: {av} vs {bv} (diff={})",
(av - bv).abs()
);
}
}
assert!(
checked > 0,
"{label}: no evaluation points succeeded — test is vacuous"
);
}
fn assert_ftc_custom(integrand: &Ex, var: &Ex, label: &str) -> String {
let antideriv = integrand.integrate(var);
let s = format!("{antideriv}");
assert!(
!s.contains("Integral"),
"{label}: integration returned unevaluated Integral: {s}"
);
let deriv = antideriv.diff(var);
let ctx = integrand.context();
let mut checked = 0;
for &pt_f in &[0.3, 0.7, 1.4, 2.1] {
let numer = (pt_f * 1000.0_f64).round() as i64;
let pt = ctx.rational(numer, 1000);
let orig_val = integrand.subs(var, &pt).eval().eval_f64();
let deriv_val = deriv.subs(var, &pt).eval().eval_f64();
if let (Ok(o), Ok(d)) = (orig_val, deriv_val) {
checked += 1;
let scale = o.abs().max(d.abs()).max(1.0);
assert!(
(o - d).abs() < 1e-8 * scale,
"FTC failed for {label} at {var}={pt_f}: integrand={o}, d/dx(antideriv)={d}, \
antideriv='{antideriv}', deriv='{deriv}'",
);
}
}
assert!(
checked > 0,
"FTC {label}: no evaluation points succeeded — test is vacuous"
);
s
}
fn assert_ftc_domain(integrand: &Ex, var: &Ex, points: &[f64], tol: f64, label: &str) -> String {
let antideriv = integrand.integrate(var);
let s = format!("{antideriv}");
assert!(
!s.contains("Integral"),
"{label}: integration returned unevaluated Integral: {s}"
);
let deriv = antideriv.diff(var);
let ctx = integrand.context();
let mut checked = 0;
for &pt_f in points {
let numer = (pt_f * 10000.0).round() as i64;
let pt = ctx.rational(numer, 10000);
let orig_val = integrand.subs(var, &pt).eval().eval_f64();
let deriv_val = deriv.subs(var, &pt).eval().eval_f64();
if let (Ok(o), Ok(d)) = (orig_val, deriv_val) {
checked += 1;
let scale = o.abs().max(d.abs()).max(1.0);
assert!(
(o - d).abs() < tol * scale,
"FTC failed for {label} at {var}={pt_f}: integrand={o}, d/dx(antideriv)={d}, \
antideriv='{antideriv}', deriv='{deriv}'",
);
}
}
assert!(
checked > 0,
"FTC {label}: no evaluation points succeeded — test is vacuous"
);
s
}
#[test]
fn diff_chain_sin_of_exp() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.exp().sin();
let df = f.diff(&x);
let expected = &x.exp().cos() * &x.exp();
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx sin(exp(x))");
}
#[test]
fn diff_chain_exp_of_sin() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.sin().exp();
let df = f.diff(&x);
let expected = &x.sin().exp() * &x.cos();
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx exp(sin(x))");
}
#[test]
fn diff_chain_ln_of_cos() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.cos().ln();
let df = f.diff(&x);
let expected = -&x.tan();
let ctx2 = df.context();
for &(p, q) in &[(1, 10), (1, 4), (1, 2)] {
let pt = ctx2.rational(p, q);
let got = df.subs(&x, &pt).eval().eval_f64();
let want = expected.subs(&x, &pt).eval().eval_f64();
if let (Ok(g), Ok(w)) = (got, want) {
assert!(
(g - w).abs() < 1e-9 * g.abs().max(w.abs()).max(1.0),
"d/dx ln(cos(x)) at x={p}/{q}: got {g}, want {w}"
);
}
}
}
#[test]
fn diff_chain_triple_nested() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.exp().cos().sin();
let df = f.diff(&x);
let expected = &x.exp().cos().cos() * &(-&x.exp().sin()) * &x.exp();
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx sin(cos(exp(x)))");
}
#[test]
fn diff_chain_sqrt_of_polynomial() {
let ctx = Context::new();
let x = ctx.symbol("x");
let inner = &x.powi(2) + 1;
let f = inner.sqrt();
let df = f.diff(&x);
let expected = &x / &(&x.powi(2) + 1).sqrt();
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx sqrt(x^2+1)");
}
#[test]
fn diff_higher_order_sin() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.sin();
let d4 = f.diff_n(&x, 4);
assert_exprs_equal_in_domain(&d4, &f, &x, "d^4/dx^4 sin(x) = sin(x)");
}
#[test]
fn diff_higher_order_cos() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.cos();
let d2 = f.diff_n(&x, 2);
let expected = -&x.cos();
assert_exprs_equal_in_domain(&d2, &expected, &x, "d^2/dx^2 cos(x) = -cos(x)");
}
#[test]
fn diff_higher_order_exp() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.exp();
for n in 1..=6 {
let dn = f.diff_n(&x, n);
assert_exprs_equal_in_domain(&dn, &f, &x, &format!("d^{n}/dx^{n} exp(x) = exp(x)"));
}
}
#[test]
fn diff_higher_order_x_to_the_n() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.powi(5);
let d5 = f.diff_n(&x, 5);
let s = format!("{}", d5.eval());
assert_eq!(s, "120", "d^5/dx^5 x^5 should be 120, got {s}");
}
#[test]
fn diff_sixth_of_x_fifth_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.powi(5);
let d6 = f.diff_n(&x, 6);
let s = format!("{}", d6.eval());
assert_eq!(s, "0", "d^6/dx^6 x^5 should be 0, got {s}");
}
#[test]
fn diff_leibniz_product_second_derivative() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x * &x.sin();
let d2 = f.diff_n(&x, 2);
let expected = &(&ctx.int(2) * &x.cos()) - &(&x * &x.sin());
assert_exprs_equal_in_domain(&d2, &expected, &x, "(x*sin(x))''");
}
#[test]
fn diff_partial_x_of_xy() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let f = &x * &y;
let df = f.diff(&x);
assert_eq!(format!("{df}"), "y", "∂/∂x (x*y) should be y, got {df}");
}
#[test]
fn diff_partial_y_of_xy() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let f = &x * &y;
let df = f.diff(&y);
assert_eq!(format!("{df}"), "x", "∂/∂y (x*y) should be x, got {df}");
}
#[test]
fn diff_y_wrt_x_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let df = y.diff(&x);
assert_eq!(format!("{df}"), "0", "∂/∂x (y) should be 0, got {df}");
}
#[test]
fn diff_mixed_partials_commute() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let f = &x.powi(3) * &y.powi(2);
let fxy = f.diff(&x).diff(&y);
let fyx = f.diff(&y).diff(&x);
let val_xy = fxy
.subs(&x, &ctx.int(2))
.subs(&y, &ctx.int(3))
.eval()
.eval_f64()
.expect("fxy eval");
let val_yx = fyx
.subs(&x, &ctx.int(2))
.subs(&y, &ctx.int(3))
.eval()
.eval_f64()
.expect("fyx eval");
assert!(
(val_xy - 72.0).abs() < 1e-10,
"∂²(x³y²)/∂x∂y at (2,3) should be 72, got {val_xy}"
);
assert!(
(val_xy - val_yx).abs() < 1e-10,
"mixed partials should commute: {val_xy} vs {val_yx}"
);
}
#[test]
fn diff_multivar_chain_rule() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let f = (&x * &y).sin();
let df_dx = f.diff(&x);
let expected = &y * &(&x * &y).cos();
let got = df_dx
.subs(&x, &ctx.int(1))
.subs(&y, &ctx.int(2))
.eval()
.eval_f64()
.expect("df/dx eval");
let want = expected
.subs(&x, &ctx.int(1))
.subs(&y, &ctx.int(2))
.eval()
.eval_f64()
.expect("expected eval");
assert!(
(got - want).abs() < 1e-10,
"∂/∂x sin(xy) at (1,2): got {got}, want {want}"
);
}
#[test]
fn diff_abs_x_squared_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (&x.powi(2) + 1).abs();
let df = f.diff(&x);
let val = df
.subs(&x, &ctx.int(2))
.eval()
.eval_f64()
.expect("df.subs(&x, &ctx.int(2)).eval().eval_f64() must evaluate");
assert!(
(val - 4.0).abs() < 1e-8,
"d/dx |x^2+1| at x=2 should be 4, got {val}"
);
}
#[test]
fn diff_quotient_rule() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.sin() / &x;
let df = f.diff(&x);
let expected = &(&(&x * &x.cos()) - &x.sin()) / &x.powi(2);
for &pt in &[1i64, 2, 3] {
let got = df.subs_i64(&x, pt).eval().eval_f64();
let want = expected.subs_i64(&x, pt).eval().eval_f64();
if let (Ok(g), Ok(w)) = (got, want) {
assert!(
(g - w).abs() < 1e-9 * g.abs().max(w.abs()).max(1.0),
"d/dx(sin(x)/x) at x={pt}: got {g}, want {w}"
);
}
}
}
#[test]
fn integrate_then_diff_polynomial() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = expr!(ctx, 3 * x ^ 2 + 2 * x + 1);
assert_ftc_custom(&f, &x, "∫(3x²+2x+1)dx");
}
#[test]
fn integrate_then_diff_sin_cos() {
let ctx = Context::new();
let x = ctx.symbol("x");
assert_ftc_custom(&x.sin(), &x, "∫sin(x)dx");
assert_ftc_custom(&x.cos(), &x, "∫cos(x)dx");
}
#[test]
fn integrate_then_diff_exp() {
let ctx = Context::new();
let x = ctx.symbol("x");
assert_ftc_custom(&x.exp(), &x, "∫exp(x)dx");
}
#[test]
fn integrate_then_diff_one_over_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &x;
assert_ftc_custom(&integrand, &x, "∫(1/x)dx");
}
#[test]
fn integrate_one_over_x2_plus_1_is_arctan() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &(&x.powi(2) + 1);
let result = integrand.integrate(&x);
let s = format!("{result}");
assert!(
s.contains("atan"),
"∫ 1/(x²+1) dx should be atan(x), got: {s}"
);
assert_ftc_custom(&integrand, &x, "∫1/(x²+1)dx = atan(x)");
}
#[test]
fn integrate_sec_squared_is_tan() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.cos().powi(-2);
let s = assert_ftc_custom(&integrand, &x, "∫sec²(x)dx = tan(x)");
let _ = s; }
#[test]
fn integrate_one_over_sqrt_1_minus_x2_is_arcsin() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &(&ctx.int(1) - &x.powi(2)).sqrt();
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_domain(
&integrand,
&x,
&[0.1, 0.3, 0.5, 0.7],
1e-7,
"∫1/√(1-x²)dx = asin(x)",
);
}
#[test]
fn integrate_x_exp_x_by_parts() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x * &x.exp();
assert_ftc_custom(&integrand, &x, "∫x·exp(x)dx");
}
#[test]
fn integrate_x_sin_x_by_parts() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x * &x.sin();
assert_ftc_custom(&integrand, &x, "∫x·sin(x)dx");
}
#[test]
fn integrate_x_cos_x_by_parts() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x * &x.cos();
assert_ftc_custom(&integrand, &x, "∫x·cos(x)dx");
}
#[test]
fn integrate_exp_sin_cyclic() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x.exp() * &x.sin();
assert_ftc_custom(&integrand, &x, "∫exp(x)·sin(x)dx");
}
#[test]
fn integrate_exp_cos_cyclic() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x.exp() * &x.cos();
assert_ftc_custom(&integrand, &x, "∫exp(x)·cos(x)dx");
}
#[test]
fn integrate_ln_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
assert_ftc_custom(&x.ln(), &x, "∫ln(x)dx");
}
#[test]
fn integrate_x_squared_exp_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x.powi(2) * &x.exp();
assert_ftc_custom(&integrand, &x, "∫x²·exp(x)dx");
}
#[test]
fn definite_integral_x_squared_0_to_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(2).integrate_definite(&x, &ctx.int(0), &ctx.int(1));
let val = result
.eval()
.eval_f64()
.expect("definite integral should evaluate");
assert!(
(val - 1.0 / 3.0).abs() < 1e-10,
"∫₀¹ x² dx should be 1/3, got {val}"
);
}
#[test]
fn definite_integral_sin_0_to_pi() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.sin().integrate_definite(&x, &ctx.int(0), &ctx.pi());
let val = result
.eval()
.eval_f64()
.expect("definite integral should evaluate");
assert!(
(val - 2.0).abs() < 1e-10,
"∫₀^π sin(x) dx should be 2, got {val}"
);
}
#[test]
fn definite_integral_exp_0_to_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.exp().integrate_definite(&x, &ctx.int(0), &ctx.int(1));
let val = result
.eval()
.eval_f64()
.expect("definite integral should evaluate");
let expected = std::f64::consts::E - 1.0;
assert!(
(val - expected).abs() < 1e-10,
"∫₀¹ exp(x) dx should be e-1 ≈ {expected}, got {val}"
);
}
#[test]
fn definite_integral_1_over_x_1_to_e() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &x;
let result = integrand.integrate_definite(&x, &ctx.int(1), &ctx.e());
let val = result
.eval()
.eval_f64()
.expect("definite integral should evaluate");
assert!(
(val - 1.0).abs() < 1e-10,
"∫₁^e 1/x dx should be 1, got {val}"
);
}
#[test]
fn integrate_sin_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.sin().powi(2);
assert_ftc_custom(&integrand, &x, "∫sin²(x)dx");
}
#[test]
fn integrate_cos_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.cos().powi(2);
assert_ftc_custom(&integrand, &x, "∫cos²(x)dx");
}
#[test]
fn integrate_tan_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
assert_ftc_custom(&x.tan(), &x, "∫tan(x)dx");
}
#[test]
fn integrate_linearity_sum() {
let ctx = Context::new();
let x = ctx.symbol("x");
let combined = (&x.sin() + &x.exp()).integrate(&x);
let separate = &x.sin().integrate(&x) + &x.exp().integrate(&x);
assert_exprs_equal_in_domain(&combined, &separate, &x, "linearity of integration");
}
#[test]
fn integrate_linearity_constant_factor() {
let ctx = Context::new();
let x = ctx.symbol("x");
let combined = (&ctx.int(5) * &x.sin()).integrate(&x);
let separate = &ctx.int(5) * &x.sin().integrate(&x);
assert_exprs_equal_in_domain(&combined, &separate, &x, "constant factor in integration");
}
#[test]
fn limit_sin_x_over_x_at_0() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.sin() / &x;
let result = expr.limit(&x, &ctx.int(0));
assert_eq!(format!("{result}"), "1", "lim(x→0) sin(x)/x should be 1");
}
#[test]
fn limit_exp_minus_1_over_x_at_0() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &(&x.exp() - 1) / &x;
let result = expr.try_limit(&x, &ctx.int(0));
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should be numeric");
assert!(
(val - 1.0).abs() < 1e-8,
"lim(x→0) (exp(x)-1)/x should be 1, got {val}"
);
}
#[test]
fn limit_1_minus_cos_over_x2_at_0() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &(&ctx.int(1) - &x.cos()) / &x.powi(2);
let result = expr.try_limit(&x, &ctx.int(0));
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should be numeric");
assert!(
(val - 0.5).abs() < 1e-8,
"lim(x→0) (1-cos(x))/x² should be 1/2, got {val}"
);
}
#[test]
fn limit_tan_x_over_x_at_0() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.tan() / &x;
let result = expr.try_limit(&x, &ctx.int(0));
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should be numeric");
assert!(
(val - 1.0).abs() < 1e-8,
"lim(x→0) tan(x)/x should be 1, got {val}"
);
}
#[test]
fn limit_x_ln_x_at_0_from_right() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x * &x.ln();
let result = expr.try_limit(&x, &ctx.int(0));
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should be numeric");
assert!(val.abs() < 1e-8, "lim(x→0+) x*ln(x) should be 0, got {val}");
}
#[test]
fn limit_1_over_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &ctx.int(1) / &x;
let result = expr.limit(&x, &ctx.infinity());
assert_eq!(format!("{result}"), "0", "lim(x→∞) 1/x should be 0");
}
#[test]
fn limit_x_exp_neg_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x * &(-&x).exp();
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
assert_eq!(format!("{r}"), "0", "lim(x→∞) x·exp(-x) should be 0");
}
#[test]
fn limit_1_plus_1_over_x_to_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let base = &ctx.int(1) + &(&ctx.int(1) / &x);
let expr = base.pow(&x);
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let v = r.eval_f64().expect("limit must be a number");
assert!(
(v - std::f64::consts::E).abs() < 1e-12,
"lim(x→∞) (1+1/x)^x should be e, got {r} = {v}"
);
}
#[test]
fn limit_lhopital_0_over_0() {
let ctx = Context::new();
let x = ctx.symbol("x");
let numer = &x.exp() - &ctx.int(1) - &x;
let expr = &numer / &x.powi(2);
let result = expr.try_limit(&x, &ctx.int(0));
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should be numeric");
assert!(
(val - 0.5).abs() < 1e-8,
"lim(x→0) (e^x-1-x)/x² should be 1/2, got {val}"
);
}
#[test]
fn limit_lhopital_inf_over_inf() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x / &x.exp();
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
assert_eq!(format!("{r}"), "0", "lim(x→∞) x/exp(x) should be 0");
}
#[test]
fn limit_polynomial_ratio_same_degree() {
let ctx = Context::new();
let x = ctx.symbol("x");
let numer = expr!(ctx, 3 * x ^ 2 + 2 * x + 1);
let denom = expr!(ctx, x ^ 2 + 1);
let expr = &numer / &denom;
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should be numeric");
assert!(
(val - 3.0).abs() < 1e-8,
"lim(x→∞) (3x²+2x+1)/(x²+1) should be 3, got {val}"
);
}
#[test]
fn limit_polynomial_ratio_higher_degree_numerator() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(3) / &(&x.powi(2) + 1);
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let s = format!("{r}");
assert!(
s.contains("∞") || s.contains("oo") || s.contains("Inf") || s.contains("inf"),
"lim(x→∞) x³/(x²+1) should be ∞, got: {s}"
);
}
#[test]
fn limit_sin_x_at_pi_over_2() {
let ctx = Context::new();
let x = ctx.symbol("x");
let pi_over_2 = &ctx.pi() / 2;
let result = x.sin().limit(&x, &pi_over_2);
let val = result.eval_f64().expect("should evaluate");
assert!(
(val - 1.0).abs() < 1e-10,
"lim(x→π/2) sin(x) should be 1, got {val}"
);
}
fn eval_series_at(series: &Ex, var: &Ex, p: i64, q: i64) -> f64 {
let ctx = series.context();
let pt = ctx.rational(p, q);
series
.subs(var, &pt)
.eval()
.eval_f64()
.expect("series evaluation should succeed")
}
#[test]
fn series_exp_coefficients_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.exp().maclaurin(&x, 6);
assert!(!series.has_unevaluated(), "exp(x) maclaurin should work");
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 1);
let exact = std::f64::consts::E;
assert!(
(val - exact).abs() < 0.01,
"exp(x) series (order 6) at x=1 should be close to e={exact}, got {val}"
);
}
#[test]
fn series_exp_at_zero_is_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.exp().maclaurin(&x, 5);
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 0, 1);
assert!(
(val - 1.0).abs() < 1e-12,
"exp(x) series at x=0 should be 1, got {val}"
);
}
#[test]
fn series_sin_coefficients() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.sin().maclaurin(&x, 8);
assert!(!series.has_unevaluated(), "sin(x) maclaurin should work");
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 5236, 10000);
let exact = (std::f64::consts::PI / 6.0).sin();
assert!(
(val - exact).abs() < 1e-5,
"sin(x) series at x≈π/6: got {val}, expected {exact}"
);
}
#[test]
fn series_sin_at_zero_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.sin().maclaurin(&x, 5);
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 0, 1);
assert!(
val.abs() < 1e-12,
"sin(x) series at x=0 should be 0, got {val}"
);
}
#[test]
fn series_cos_coefficients() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.cos().maclaurin(&x, 8);
assert!(!series.has_unevaluated(), "cos(x) maclaurin should work");
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 1);
let exact = 1.0_f64.cos();
assert!(
(val - exact).abs() < 1e-4,
"cos(x) series at x=1: got {val}, expected {exact}"
);
}
#[test]
fn series_cos_at_zero_is_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.cos().maclaurin(&x, 5);
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 0, 1);
assert!(
(val - 1.0).abs() < 1e-12,
"cos(x) series at x=0 should be 1, got {val}"
);
}
#[test]
fn series_ln_1_plus_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (&x + 1).ln();
let series = f.try_maclaurin(&x, 6);
let s = series.expect("series must evaluate");
let expanded = s.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 2);
let exact = 1.5_f64.ln();
assert!(
(val - exact).abs() < 0.01,
"ln(1+x) series at x=0.5: got {val}, expected {exact}"
);
let val0 = eval_series_at(&expanded, &x, 0, 1);
assert!(
val0.abs() < 1e-12,
"ln(1+x) series at x=0 should be 0, got {val0}"
);
}
#[test]
fn series_convergence_exp() {
let ctx = Context::new();
let x = ctx.symbol("x");
let exact = 0.5_f64.exp();
let mut prev_err = f64::MAX;
for order in [3u32, 5, 7, 9] {
let series = x.exp().maclaurin(&x, order);
let expanded = series.expand().eval();
let val = expanded
.subs(&x, &ctx.rational(1, 2))
.eval()
.eval_f64()
.expect("expanded.subs(&x, &ctx.rational(1, 2)).eval().eval_f64() must evaluate");
let err = (val - exact).abs();
assert!(
err < prev_err,
"exp series error should decrease: order {order} err={err} >= prev_err={prev_err}"
);
prev_err = err;
}
}
#[test]
fn series_convergence_sin() {
let ctx = Context::new();
let x = ctx.symbol("x");
let exact = 0.5_f64.sin();
let mut prev_err = f64::MAX;
for order in [3u32, 5, 7, 9] {
let series = x.sin().maclaurin(&x, order);
let expanded = series.expand().eval();
let val = expanded
.subs(&x, &ctx.rational(1, 2))
.eval()
.eval_f64()
.expect("expanded.subs(&x, &ctx.rational(1, 2)).eval().eval_f64() must evaluate");
let err = (val - exact).abs();
assert!(
err < prev_err,
"sin series error should decrease: order {order} err={err} >= prev_err={prev_err}"
);
prev_err = err;
}
}
#[test]
fn series_geometric() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &ctx.int(1) / &(&ctx.int(1) - &x);
let series = f.try_maclaurin(&x, 5);
let s = series.expect("series must evaluate");
let expanded = s.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 10);
let exact = 1.0 / 0.9;
assert!(
(val - exact).abs() < 0.001,
"geometric series at x=0.1: got {val}, expected {exact}"
);
}
#[test]
fn series_arctan() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.atan().try_maclaurin(&x, 7);
let s = series.expect("series must evaluate");
let expanded = s.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 2);
let exact = 0.5_f64.atan();
assert!(
(val - exact).abs() < 0.01,
"arctan series at x=0.5: got {val}, expected {exact}"
);
}
#[test]
fn series_exp_around_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.exp().series(&x, &ctx.int(1), 6);
assert!(
!series.has_unevaluated(),
"exp(x) series around 1 should work"
);
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 1);
let exact = std::f64::consts::E;
assert!(
(val - exact).abs() < 1e-10,
"exp(x) series at expansion point x=1 should be e={exact}, got {val}"
);
let val2 = eval_series_at(&expanded, &x, 11, 10);
let exact2 = 1.1_f64.exp();
assert!(
(val2 - exact2).abs() < 1e-4,
"exp(x) series(about 1) at x=1.1: got {val2}, expected {exact2}"
);
}
#[test]
fn series_ln_around_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.ln().series(&x, &ctx.int(1), 8);
assert!(
!series.has_unevaluated(),
"ln(x) series around 1 should work"
);
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 1);
assert!(
val.abs() < 1e-12,
"ln(x) series at x=1 should be 0, got {val}"
);
let val2 = eval_series_at(&expanded, &x, 11, 10);
let exact2 = 1.1_f64.ln();
assert!(
(val2 - exact2).abs() < 1e-6,
"ln(x) series(about 1) at x=1.1: got {val2}, expected {exact2}"
);
}
#[test]
fn diff_of_definite_integral_gives_integrand() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) / 3;
let df = f.diff(&x);
let expected = x.powi(2);
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx(x³/3) = x²");
}
#[test]
fn series_of_derivative_matches_derivative_of_series() {
let ctx = Context::new();
let x = ctx.symbol("x");
let sin_series = x.sin().maclaurin(&x, 6);
let cos_series = x.cos().maclaurin(&x, 6);
let deriv_of_sin_series = sin_series.diff(&x);
let expanded_deriv = deriv_of_sin_series.expand().eval();
let expanded_cos_series = cos_series.expand().eval();
let val_d = eval_series_at(&expanded_deriv, &x, 1, 2);
let val_c = eval_series_at(&expanded_cos_series, &x, 1, 2);
assert!(
(val_d - val_c).abs() < 1e-3,
"d/dx(sin series) should ≈ cos series at x=0.5: {val_d} vs {val_c}"
);
}
#[test]
fn diff_of_constant_expression_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let dp = ctx.pi().diff(&x);
assert_eq!(format!("{dp}"), "0", "d/dx(π) should be 0");
let de = ctx.e().diff(&x);
assert_eq!(format!("{de}"), "0", "d/dx(e) should be 0");
let d42 = ctx.int(42).diff(&x);
assert_eq!(format!("{d42}"), "0", "d/dx(42) should be 0");
}
#[test]
fn diff_zero_times_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.sin();
let d0 = f.diff_n(&x, 0);
assert_exprs_equal_in_domain(&d0, &f, &x, "d^0/dx^0 sin(x) = sin(x)");
}
#[test]
fn integrate_zero_is_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = ctx.int(0).integrate(&x);
assert_eq!(format!("{result}"), "0", "∫0 dx should be 0");
}
#[test]
fn limit_constant_is_constant() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = ctx.int(42).limit(&x, &ctx.int(0));
assert_eq!(format!("{result}"), "42", "lim(x→0) 42 should be 42");
}
#[test]
fn diff_arcsin_numerical() {
let ctx = Context::new();
let x = ctx.symbol("x");
let df = x.asin().diff(&x);
let expected = &ctx.int(1) / &(&ctx.int(1) - &x.powi(2)).sqrt();
for &(p, q) in &[(3, 10), (1, 2)] {
let pt = ctx.rational(p, q);
let got = df.subs(&x, &pt).eval().eval_f64();
let want = expected.subs(&x, &pt).eval().eval_f64();
if let (Ok(g), Ok(w)) = (got, want) {
assert!(
(g - w).abs() < 1e-8,
"d/dx arcsin(x) at x={p}/{q}: got {g}, want {w}"
);
}
}
}
#[test]
fn diff_arccos_numerical() {
let ctx = Context::new();
let x = ctx.symbol("x");
let df = x.acos().diff(&x);
let expected = &ctx.int(-1) / &(&ctx.int(1) - &x.powi(2)).sqrt();
for &(p, q) in &[(3, 10), (1, 2)] {
let pt = ctx.rational(p, q);
let got = df.subs(&x, &pt).eval().eval_f64();
let want = expected.subs(&x, &pt).eval().eval_f64();
if let (Ok(g), Ok(w)) = (got, want) {
assert!(
(g - w).abs() < 1e-8,
"d/dx arccos(x) at x={p}/{q}: got {g}, want {w}"
);
}
}
}
#[test]
fn diff_arctan_numerical() {
let ctx = Context::new();
let x = ctx.symbol("x");
let df = x.atan().diff(&x);
let expected = &ctx.int(1) / &(&ctx.int(1) + &x.powi(2));
for &pt_val in &[1i64, 2, 3] {
let got = df.subs_i64(&x, pt_val).eval().eval_f64();
let want = expected.subs_i64(&x, pt_val).eval().eval_f64();
if let (Ok(g), Ok(w)) = (got, want) {
assert!(
(g - w).abs() < 1e-10,
"d/dx arctan(x) at x={pt_val}: got {g}, want {w}"
);
}
}
}
#[test]
fn diff_sinh() {
let ctx = Context::new();
let x = ctx.symbol("x");
let df = x.sinh().diff(&x);
let expected = x.cosh();
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx sinh(x) = cosh(x)");
}
#[test]
fn diff_cosh() {
let ctx = Context::new();
let x = ctx.symbol("x");
let df = x.cosh().diff(&x);
let expected = x.sinh();
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx cosh(x) = sinh(x)");
}
#[test]
fn diff_tanh() {
let ctx = Context::new();
let x = ctx.symbol("x");
let df = x.tanh().diff(&x);
let expected = &ctx.int(1) - &x.tanh().powi(2);
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx tanh(x) = 1 - tanh²(x)");
}
#[test]
fn integrate_sinh() {
let ctx = Context::new();
let x = ctx.symbol("x");
assert_ftc_custom(&x.sinh(), &x, "∫sinh(x)dx");
}
#[test]
fn integrate_cosh() {
let ctx = Context::new();
let x = ctx.symbol("x");
assert_ftc_custom(&x.cosh(), &x, "∫cosh(x)dx");
}
#[test]
fn diff_power_rule_fractional_exponent() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.sqrt();
let df = f.diff(&x);
let val = df
.subs(&x, &ctx.int(4))
.eval()
.eval_f64()
.expect("should eval");
assert!(
(val - 0.25).abs() < 1e-10,
"d/dx sqrt(x) at x=4 should be 0.25, got {val}"
);
}
#[test]
fn diff_exponential_with_constant_base() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ctx.int(2).pow(&x);
let df = f.diff(&x);
let val = df
.subs(&x, &ctx.int(1))
.eval()
.eval_f64()
.expect("should eval");
let expected = 2.0 * 2.0_f64.ln();
assert!(
(val - expected).abs() < 1e-9,
"d/dx 2^x at x=1 should be 2·ln(2) ≈ {expected}, got {val}"
);
}
#[test]
fn diff_x_to_the_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.pow(&x);
let df = f.diff(&x);
let expected = &x.pow(&x) * &(&x.ln() + 1);
let got = df.subs(&x, &ctx.int(2)).eval().eval_f64();
let want = expected.subs(&x, &ctx.int(2)).eval().eval_f64();
if let (Ok(g), Ok(w)) = (got, want) {
assert!((g - w).abs() < 1e-6, "d/dx x^x at x=2: got {g}, want {w}");
}
}
#[test]
fn integrate_derivative_recovers_original_up_to_constant() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(3) + &x.sin();
let df = f.diff(&x);
let antideriv = df.integrate(&x);
let diff_at_1 = (&antideriv - &f).subs(&x, &ctx.int(1)).eval().eval_f64();
let diff_at_2 = (&antideriv - &f).subs(&x, &ctx.int(2)).eval().eval_f64();
if let (Ok(d1), Ok(d2)) = (diff_at_1, diff_at_2) {
assert!(
(d1 - d2).abs() < 1e-8,
"∫(d/dx(x³+sin(x)))dx should differ from x³+sin(x) by a constant: \
diff at x=1 is {d1}, diff at x=2 is {d2}"
);
}
}
#[test]
fn integrate_cos_2x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = (&x * 2).cos();
assert_ftc_custom(&integrand, &x, "∫cos(2x)dx");
}
#[test]
fn integrate_exp_3x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = (&x * 3).exp();
assert_ftc_custom(&integrand, &x, "∫exp(3x)dx");
}
#[test]
fn integrate_sin_squared_plus_cos_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x.sin().powi(2) + &x.cos().powi(2);
let result = integrand.integrate(&x);
let deriv = result.diff(&x);
let val = deriv
.subs(&x, &ctx.int(1))
.eval()
.eval_f64()
.expect("deriv.subs(&x, &ctx.int(1)).eval().eval_f64() must evaluate");
assert!(
(val - 1.0).abs() < 1e-8,
"d/dx ∫(sin²x+cos²x)dx should be 1, got {val}"
);
}
#[test]
fn limit_squeeze_theorem_x_sin_1_over_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x * &(&ctx.int(1) / &x).sin();
let result = expr.try_limit(&x, &ctx.int(0));
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
val.abs() < 1e-8,
"lim(x→0) x·sin(1/x) should be 0, got {val}"
);
}
#[test]
fn series_sinh_matches_odd_exp_terms() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.sinh().maclaurin(&x, 8);
assert!(!series.has_unevaluated(), "sinh series should work");
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 1);
let exact = 1.0_f64.sinh();
assert!(
(val - exact).abs() < 1e-4,
"sinh series at x=1: got {val}, expected {exact}"
);
}
#[test]
fn series_cosh_matches_even_exp_terms() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.cosh().maclaurin(&x, 8);
assert!(!series.has_unevaluated(), "cosh series should work");
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 1);
let exact = 1.0_f64.cosh();
assert!(
(val - exact).abs() < 1e-4,
"cosh series at x=1: got {val}, expected {exact}"
);
}
#[test]
fn series_exp_equals_cosh_plus_sinh() {
let ctx = Context::new();
let x = ctx.symbol("x");
let exp_series = x.exp().maclaurin(&x, 8).expand().eval();
let sinh_series = x.sinh().maclaurin(&x, 8).expand().eval();
let cosh_series = x.cosh().maclaurin(&x, 8).expand().eval();
let sum = &cosh_series + &sinh_series;
for &(p, q) in &[(1, 4), (1, 2), (3, 4), (1, 1)] {
let e_val = eval_series_at(&exp_series, &x, p, q);
let s_val = eval_series_at(&sum, &x, p, q);
assert!(
(e_val - s_val).abs() < 1e-6,
"exp series ≠ cosh + sinh series at x={p}/{q}: {e_val} vs {s_val}"
);
}
}
#[test]
fn integrate_high_degree_polynomial() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.powi(10);
assert_ftc_custom(&integrand, &x, "∫x^10 dx");
}
#[test]
fn integrate_negative_power() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.powi(-3);
assert_ftc_custom(&integrand, &x, "∫x^(-3) dx");
}
#[test]
fn definite_integral_symmetry_odd_function() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(3).integrate_definite(&x, &ctx.int(-1), &ctx.int(1));
let val = result
.eval()
.eval_f64()
.expect("definite integral should evaluate");
assert!(
val.abs() < 1e-10,
"∫₋₁¹ x³ dx should be 0 (odd function), got {val}"
);
}
#[test]
fn definite_integral_symmetry_even_function() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(2).integrate_definite(&x, &ctx.int(-1), &ctx.int(1));
let val = result
.eval()
.eval_f64()
.expect("definite integral should evaluate");
assert!(
(val - 2.0 / 3.0).abs() < 1e-10,
"∫₋₁¹ x² dx should be 2/3, got {val}"
);
}
#[test]
fn diff_triple_product() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &(&x * &x.sin()) * &x.exp();
let df = f.diff(&x);
let term1 = &x.sin() * &x.exp();
let term2 = &(&x * &x.cos()) * &x.exp();
let term3 = &(&x * &x.sin()) * &x.exp();
let expected = &(&term1 + &term2) + &term3;
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx(x·sin(x)·exp(x))");
}
#[test]
fn diff_nested_exp_exp() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.exp().exp();
let df = f.diff(&x);
let expected = &x.exp().exp() * &x.exp();
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx exp(exp(x))");
}
#[test]
fn diff_sin_of_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.powi(2).sin();
let df = f.diff(&x);
let expected = &(&ctx.int(2) * &x) * &x.powi(2).cos();
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx sin(x²) = 2x·cos(x²)");
}
#[test]
fn diff_ln_of_x_squared_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (&x.powi(2) + 1).ln();
let df = f.diff(&x);
let expected = &(&ctx.int(2) * &x) / &(&x.powi(2) + 1);
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx ln(x²+1) = 2x/(x²+1)");
}
#[test]
fn integrate_1_over_x_squared_minus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &(&x.powi(2) - 1);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return; }
assert_ftc_domain(
&integrand,
&x,
&[0.2, 0.4, 0.6],
1e-7,
"∫1/(x²-1)dx partial fractions",
);
}
#[test]
fn integrate_x_over_x2_plus_1_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x / &(&x.powi(2) + 1).powi(2);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫x/(x²+1)² dx");
}
#[test]
fn integrate_1_over_x2_plus_1_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &(&x.powi(2) + 1).powi(2);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫1/(x²+1)² dx");
}
#[test]
fn integrate_x_squared_over_x2_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x.powi(2) / &(&x.powi(2) + 1);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫x²/(x²+1) dx");
}
#[test]
fn integrate_1_over_x2_plus_4() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &(&x.powi(2) + 4);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫1/(x²+4) dx");
}
#[test]
fn integrate_2x_plus_3_over_x2_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &(&ctx.int(2) * &x + 3) / &(&x.powi(2) + 1);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫(2x+3)/(x²+1) dx");
}
#[test]
fn integrate_arctan_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.atan();
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫arctan(x) dx");
}
#[test]
fn integrate_arcsin_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.asin();
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_domain(&integrand, &x, &[0.1, 0.3, 0.5, 0.7], 1e-7, "∫arcsin(x) dx");
}
#[test]
fn diff_arctan_of_2x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (&x * 2).atan();
let df = f.diff(&x);
let expected = &ctx.int(2) / &(&ctx.int(1) + &(&ctx.int(4) * &x.powi(2)));
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx arctan(2x)");
}
#[test]
fn diff_arcsin_of_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.powi(2).asin();
let df = f.diff(&x);
let expected = &(&ctx.int(2) * &x) / &(&ctx.int(1) - &x.powi(4)).sqrt();
let ctx2 = df.context();
for &(p, q) in &[(1, 10), (3, 10), (1, 2)] {
let pt = ctx2.rational(p, q);
let got = df.subs(&x, &pt).eval().eval_f64();
let want = expected.subs(&x, &pt).eval().eval_f64();
if let (Ok(g), Ok(w)) = (got, want) {
assert!(
(g - w).abs() < 1e-8 * g.abs().max(w.abs()).max(1.0),
"d/dx arcsin(x²) at x={p}/{q}: got {g}, want {w}"
);
}
}
}
#[test]
fn series_exp_exact_coefficient_check() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.exp().maclaurin(&x, 7);
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 1);
let partial_sum: f64 = 1.0 + 1.0 + 0.5 + 1.0 / 6.0 + 1.0 / 24.0 + 1.0 / 120.0 + 1.0 / 720.0;
assert!(
(val - partial_sum).abs() < 1e-12,
"exp(x) order-7 series at x=1: got {val}, expected {partial_sum}"
);
}
#[test]
fn series_sin_exact_coefficient_check() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.sin().maclaurin(&x, 8);
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 1);
let partial_sum: f64 = 1.0 - 1.0 / 6.0 + 1.0 / 120.0 - 1.0 / 5040.0;
assert!(
(val - partial_sum).abs() < 1e-10,
"sin(x) order-8 series at x=1: got {val}, expected partial sum {partial_sum}"
);
}
#[test]
fn series_cos_exact_coefficient_check() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.cos().maclaurin(&x, 7);
let expanded = series.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 1);
let partial_sum: f64 = 1.0 - 0.5 + 1.0 / 24.0 - 1.0 / 720.0;
assert!(
(val - partial_sum).abs() < 1e-10,
"cos(x) order-7 series at x=1: got {val}, expected partial sum {partial_sum}"
);
}
#[test]
fn definite_integral_1_over_1_plus_x2_0_to_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &(&x.powi(2) + 1);
let result = integrand.integrate_definite(&x, &ctx.int(0), &ctx.int(1));
let val = result
.eval()
.eval_f64()
.expect("definite integral should evaluate");
let expected = std::f64::consts::FRAC_PI_4;
assert!(
(val - expected).abs() < 1e-10,
"∫₀¹ 1/(1+x²) dx should be π/4 ≈ {expected}, got {val}"
);
}
#[test]
fn definite_integral_x_exp_neg_x_0_to_inf() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x * &(-&x).exp();
let result = integrand.integrate_definite(&x, &ctx.int(0), &ctx.infinity());
let val = result
.eval()
.eval_f64()
.expect("result.eval().eval_f64() must evaluate");
assert!(
(val - 1.0).abs() < 1e-8,
"∫₀^∞ x·exp(-x) dx should be 1 (Gamma(2)), got {val}"
);
}
#[test]
fn definite_integral_cos_squared_0_to_pi() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.cos().powi(2);
let result = integrand.integrate_definite(&x, &ctx.int(0), &ctx.pi());
let val = result
.eval()
.eval_f64()
.expect("definite integral should evaluate");
let expected = std::f64::consts::PI / 2.0;
assert!(
(val - expected).abs() < 1e-8,
"∫₀^π cos²(x) dx should be π/2 ≈ {expected}, got {val}"
);
}
#[test]
fn definite_integral_sin_squared_0_to_pi() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.sin().powi(2);
let result = integrand.integrate_definite(&x, &ctx.int(0), &ctx.pi());
let val = result
.eval()
.eval_f64()
.expect("definite integral should evaluate");
let expected = std::f64::consts::PI / 2.0;
assert!(
(val - expected).abs() < 1e-8,
"∫₀^π sin²(x) dx should be π/2 ≈ {expected}, got {val}"
);
}
#[test]
fn definite_integral_x_squared_neg1_to_2() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(2).integrate_definite(&x, &ctx.int(-1), &ctx.int(2));
let val = result
.eval()
.eval_f64()
.expect("definite integral should evaluate");
assert!(
(val - 3.0).abs() < 1e-10,
"∫₋₁² x² dx should be 3, got {val}"
);
}
#[test]
fn diff_third_derivative_of_x_exp_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x * &x.exp();
let d3 = f.diff_n(&x, 3);
let expected = &(&ctx.int(3) + &x) * &x.exp();
assert_exprs_equal_in_domain(&d3, &expected, &x, "d³/dx³(x·exp(x)) = (3+x)·exp(x)");
}
#[test]
fn diff_fourth_derivative_of_x_sin_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x * &x.sin();
let d4 = f.diff_n(&x, 4);
let expected = &(&ctx.int(-4) * &x.cos()) + &(&x * &x.sin());
assert_exprs_equal_in_domain(&d4, &expected, &x, "d⁴/dx⁴(x·sin(x))");
}
#[test]
fn diff_second_derivative_of_exp_of_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.powi(2).exp();
let d2 = f.diff_n(&x, 2);
let expected = &(&ctx.int(2) + &(&ctx.int(4) * &x.powi(2))) * &x.powi(2).exp();
assert_exprs_equal_in_domain(&d2, &expected, &x, "d²/dx²(exp(x²))");
}
#[test]
fn integrate_sin_of_2x_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = (&(&x * 2) + 1).sin();
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫sin(2x+1) dx");
}
#[test]
fn integrate_exp_of_neg_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = (-&x).exp();
assert_ftc_custom(&integrand, &x, "∫exp(-x) dx");
}
#[test]
fn integrate_x_times_x2_plus_1_cubed() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x * &(&x.powi(2) + 1).powi(3);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫x(x²+1)³ dx");
}
#[test]
fn integrate_cos_x_times_exp_sin_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x.cos() * &x.sin().exp();
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫cos(x)·exp(sin(x)) dx");
}
#[test]
fn integrate_2x_over_x2_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &(&ctx.int(2) * &x) / &(&x.powi(2) + 1);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫2x/(x²+1) dx = ln(x²+1)");
}
#[test]
fn limit_x_squared_sin_1_over_x_at_0() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(2) * &(&ctx.int(1) / &x).sin();
let result = expr.try_limit(&x, &ctx.int(0));
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
val.abs() < 1e-8,
"lim(x→0) x²·sin(1/x) should be 0, got {val}"
);
}
#[test]
fn limit_sin_3x_over_x_at_0() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &(&x * 3).sin() / &x;
let result = expr.try_limit(&x, &ctx.int(0));
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
(val - 3.0).abs() < 1e-8,
"lim(x→0) sin(3x)/x should be 3, got {val}"
);
}
#[test]
fn limit_sin_ax_over_sin_bx_at_0() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &(&x * 2).sin() / &(&x * 3).sin();
let result = expr.try_limit(&x, &ctx.int(0));
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
(val - 2.0 / 3.0).abs() < 1e-8,
"lim(x→0) sin(2x)/sin(3x) should be 2/3, got {val}"
);
}
#[test]
fn limit_ln_x_over_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.ln() / &x;
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
assert_eq!(format!("{r}"), "0", "lim(x→∞) ln(x)/x should be 0");
}
#[test]
fn limit_x_to_the_1_over_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.pow(&(&ctx.int(1) / &x));
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
(val - 1.0).abs() < 1e-8,
"lim(x→∞) x^(1/x) should be 1, got {val}"
);
}
#[test]
fn limit_x_to_the_2_over_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.pow(&(&ctx.int(2) / &x));
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
(val - 1.0).abs() < 1e-8,
"lim(x→∞) x^(2/x) should be 1, got {val}"
);
}
#[test]
fn limit_2x_to_the_1_over_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let base = &ctx.int(2) * &x;
let expr = base.pow(&(&ctx.int(1) / &x));
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
(val - 1.0).abs() < 1e-8,
"lim(x→∞) (2x)^(1/x) should be 1, got {val}"
);
}
#[test]
fn limit_1_plus_1_over_x_to_x_still_works() {
let ctx = Context::new();
let x = ctx.symbol("x");
let base = &ctx.int(1) + &(&ctx.int(1) / &x);
let expr = base.pow(&x);
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let s = format!("{r}");
if s == "E" {
} else if let Ok(v) = r.eval_f64() {
assert!(
(v - std::f64::consts::E).abs() < 0.01,
"lim(x→∞) (1+1/x)^x should be e ≈ 2.71828, got {v}"
);
}
}
#[test]
fn limit_1_plus_2_over_x_to_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let base = &ctx.int(1) + &(&ctx.int(2) / &x);
let expr = base.pow(&x);
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
let expected = std::f64::consts::E * std::f64::consts::E;
assert!(
(val - expected).abs() < 1e-12,
"lim(x→∞) (1+2/x)^x should be e² ≈ {expected}, got {r} = {val}"
);
}
#[test]
fn limit_exp_neg_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = (-&x).exp();
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
assert_eq!(format!("{r}"), "0", "lim(x→∞) exp(-x) should be 0");
}
#[test]
fn series_tan_first_terms() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.tan().try_maclaurin(&x, 6);
let s = series.expect("series must evaluate");
let expanded = s.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 5); let exact = 0.2_f64.tan();
assert!(
(val - exact).abs() < 1e-5,
"tan(x) series at x=0.2: got {val}, expected {exact}"
);
}
#[test]
fn series_arctan_first_terms() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.atan().try_maclaurin(&x, 8);
let s = series.expect("series must evaluate");
let expanded = s.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 2);
let exact = 0.5_f64.atan();
assert!(
(val - exact).abs() < 5e-4,
"arctan(x) series at x=0.5: got {val}, expected {exact}, err={}",
(val - exact).abs()
);
}
#[test]
fn ftc_part2_derivative_of_integral_with_variable_upper_bound() {
let ctx = Context::new();
let x = ctx.symbol("x");
let t = ctx.symbol("t");
let anti_t = t.powi(2).integrate(&t);
let fx = anti_t.subs(&t, &x) - anti_t.subs(&t, &ctx.int(0));
let dfx = fx.diff(&x);
let expected = x.powi(2);
assert_exprs_equal_in_domain(&dfx, &expected, &x, "FTC part 2");
}
#[test]
fn mean_value_theorem_numerical_check() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.powi(3);
let df = f.diff(&x);
let f3 = f.subs(&x, &ctx.int(3)).eval().eval_f64().unwrap();
let f1 = f.subs(&x, &ctx.int(1)).eval().eval_f64().unwrap();
let mvt_slope = (f3 - f1) / 2.0;
assert!(
(mvt_slope - 13.0).abs() < 1e-10,
"MVT slope should be 13, got {mvt_slope}"
);
let c = (13.0_f64 / 3.0).sqrt();
let c_expr = ctx.rational((c * 10000.0).round() as i64, 10000);
let fc = df.subs(&x, &c_expr).eval().eval_f64().unwrap();
assert!(
(fc - 13.0).abs() < 0.01,
"f'(√(13/3)) should be ≈ 13, got {fc}"
);
}
#[test]
fn integrate_ln_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.ln().powi(2);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_domain(
&integrand,
&x,
&[0.5, 1.0, 1.5, 2.0, 3.0],
1e-7,
"∫ln(x)² dx",
);
}
#[test]
fn integrate_x_ln_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x * &x.ln();
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_domain(&integrand, &x, &[0.5, 1.0, 1.5, 2.0], 1e-7, "∫x·ln(x) dx");
}
#[test]
fn integrate_ln_x_over_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x.ln() / &x;
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_domain(
&integrand,
&x,
&[0.5, 1.0, 1.5, 2.5],
1e-7,
"∫ln(x)/x dx = ln(x)²/2",
);
}
#[test]
fn diff_of_trig_identity_gives_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.sin().powi(2) + &x.cos().powi(2);
let df = f.diff(&x);
for &pt_val in &[1i64, 2, 3] {
let val = df
.subs_i64(&x, pt_val)
.eval()
.eval_f64()
.expect("df.subs_i64(&x, pt_val).eval().eval_f64() must evaluate");
assert!(
val.abs() < 1e-10,
"d/dx(sin²x+cos²x) should be 0, got {val} at x={pt_val}"
);
}
}
#[test]
fn diff_of_exp_ln_is_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.ln().exp();
let df = f.diff(&x);
for &pt_val in &[1i64, 2, 3] {
let val = df
.subs_i64(&x, pt_val)
.eval()
.eval_f64()
.expect("df.subs_i64(&x, pt_val).eval().eval_f64() must evaluate");
assert!(
(val - 1.0).abs() < 1e-10,
"d/dx(exp(ln(x))) should be 1, got {val} at x={pt_val}"
);
}
}
#[test]
fn diff_of_ln_exp_is_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.exp().ln();
let df = f.diff(&x);
for &pt_val in &[1i64, 2, 3] {
let val = df
.subs_i64(&x, pt_val)
.eval()
.eval_f64()
.expect("df.subs_i64(&x, pt_val).eval().eval_f64() must evaluate");
assert!(
(val - 1.0).abs() < 1e-10,
"d/dx(ln(exp(x))) should be 1, got {val} at x={pt_val}"
);
}
}
#[test]
fn definite_integral_x_exp_x_0_to_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x * &x.exp();
let result = integrand.integrate_definite(&x, &ctx.int(0), &ctx.int(1));
let val = result.eval().eval_f64().expect("should evaluate");
assert!(
(val - 1.0).abs() < 1e-10,
"∫₀¹ x·exp(x) dx should be 1, got {val}"
);
}
#[test]
fn definite_integral_x_sin_x_0_to_pi() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x * &x.sin();
let result = integrand.integrate_definite(&x, &ctx.int(0), &ctx.pi());
let val = result.eval().eval_f64().expect("should evaluate");
let expected = std::f64::consts::PI;
assert!(
(val - expected).abs() < 1e-8,
"∫₀^π x·sin(x) dx should be π ≈ {expected}, got {val}"
);
}
#[test]
fn definite_integral_exp_neg_x_0_to_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = (-&x).exp();
let result = integrand.integrate_definite(&x, &ctx.int(0), &ctx.int(1));
let val = result.eval().eval_f64().expect("should evaluate");
let expected = 1.0 - (-1.0_f64).exp();
assert!(
(val - expected).abs() < 1e-10,
"∫₀¹ exp(-x) dx should be 1-1/e ≈ {expected}, got {val}"
);
}
#[test]
fn diff_of_x_over_x_simplifies_to_zero() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x / &x;
let df = f.diff(&x);
for &pt_val in &[1i64, 2, 3, -1, -2] {
let val = df
.subs_i64(&x, pt_val)
.eval()
.eval_f64()
.expect("df.subs_i64(&x, pt_val).eval().eval_f64() must evaluate");
assert!(
val.abs() < 1e-10,
"d/dx(x/x) should be 0, got {val} at x={pt_val}"
);
}
}
#[test]
fn diff_of_exp_a_x_is_a_exp_a_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let f = (&a * &x).exp();
let df = f.diff(&x);
let expected = &a * &(&a * &x).exp();
let got = df
.subs(&a, &ctx.int(2))
.subs(&x, &ctx.int(1))
.eval()
.eval_f64();
let want = expected
.subs(&a, &ctx.int(2))
.subs(&x, &ctx.int(1))
.eval()
.eval_f64();
if let (Ok(g), Ok(w)) = (got, want) {
assert!(
(g - w).abs() < 1e-8,
"d/dx exp(ax) at (a=2,x=1): got {g}, want {w}"
);
}
}
#[test]
fn diff_of_sin_a_x_is_a_cos_a_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = ctx.symbol("a");
let f = (&a * &x).sin();
let df = f.diff(&x);
let expected = &a * &(&a * &x).cos();
let got = df
.subs(&a, &ctx.int(3))
.subs(&x, &ctx.int(1))
.eval()
.eval_f64();
let want = expected
.subs(&a, &ctx.int(3))
.subs(&x, &ctx.int(1))
.eval()
.eval_f64();
if let (Ok(g), Ok(w)) = (got, want) {
assert!(
(g - w).abs() < 1e-8,
"d/dx sin(ax) at (a=3,x=1): got {g}, want {w}"
);
}
}
#[test]
fn integrate_1_over_sqrt_x2_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &(&x.powi(2) + 1).sqrt();
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫1/√(x²+1) dx");
}
#[test]
fn integrate_sqrt_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.sqrt();
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_domain(
&integrand,
&x,
&[0.25, 0.5, 1.0, 2.0, 4.0],
1e-7,
"∫√x dx = (2/3)x^(3/2)",
);
}
#[test]
fn integrate_x_over_sqrt_x2_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x / &(&x.powi(2) + 1).sqrt();
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫x/√(x²+1) dx");
}
#[test]
fn series_1_over_1_plus_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &ctx.int(1) / &(&x.powi(2) + 1);
let series = f.try_maclaurin(&x, 5);
let s = series.expect("series must evaluate");
let expanded = s.expand().eval();
let val = eval_series_at(&expanded, &x, 3, 10);
let exact = 1.0 / (1.0 + 0.09);
assert!(
(val - exact).abs() < 0.01,
"1/(1+x²) series at x=0.3: got {val}, expected {exact}"
);
}
#[test]
fn series_sqrt_1_plus_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (&x + 1).sqrt();
let series = f.try_maclaurin(&x, 5);
let s = series.expect("series must evaluate");
let expanded = s.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 2);
let exact = 1.5_f64.sqrt();
assert!(
(val - exact).abs() < 0.01,
"√(1+x) series at x=0.5: got {val}, expected {exact}"
);
}
#[test]
fn series_exp_of_negative_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (-&x.powi(2)).exp();
let series = f.try_maclaurin(&x, 6);
let s = series.expect("series must evaluate");
let expanded = s.expand().eval();
let val = eval_series_at(&expanded, &x, 1, 2);
let exact = (-0.25_f64).exp();
assert!(
(val - exact).abs() < 0.01,
"exp(-x²) series at x=0.5: got {val}, expected {exact}"
);
}
#[test]
fn diff_negative_exponent_chain() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.sin().powi(-1);
let df = f.diff(&x);
let expected = &(-&x.cos()) / &x.sin().powi(2);
let got = df.subs(&x, &ctx.int(1)).eval().eval_f64();
let want = expected.subs(&x, &ctx.int(1)).eval().eval_f64();
if let (Ok(g), Ok(w)) = (got, want) {
assert!(
(g - w).abs() < 1e-9,
"d/dx (1/sin(x)) at x=1: got {g}, want {w}"
);
}
}
#[test]
fn diff_cos_squared_chain_rule() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.cos().powi(2);
let df = f.diff(&x);
let expected = -&(&x * 2).sin();
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx cos²(x) = -sin(2x)");
}
#[test]
fn diff_sin_cubed_chain_rule() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.sin().powi(3);
let df = f.diff(&x);
let expected = &(&ctx.int(3) * &x.sin().powi(2)) * &x.cos();
assert_exprs_equal_in_domain(&df, &expected, &x, "d/dx sin³(x) = 3sin²(x)cos(x)");
}
#[test]
fn integrate_then_diff_roundtrip_for_tricky_rational() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &(&x + 1) / &(&x.powi(2) + 1);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
let deriv = antideriv.diff(&x);
let ctx2 = integrand.context();
let mut checked = 0;
for &(p, q) in &[(1, 4), (1, 2), (3, 4), (3, 2)] {
let pt = ctx2.rational(p, q);
let orig = integrand.subs(&x, &pt).eval().eval_f64();
let back = deriv.subs(&x, &pt).eval().eval_f64();
if let (Ok(o), Ok(b)) = (orig, back) {
checked += 1;
assert!(
(o - b).abs() < 1e-8 * o.abs().max(b.abs()).max(1.0),
"FTC roundtrip for (x+1)/(x²+1) at x={p}/{q}: orig={o}, back={b}",
);
}
}
assert!(checked > 0, "no points succeeded");
}
#[test]
fn integrate_cos_cubed() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.cos().powi(3);
assert_ftc_custom(&integrand, &x, "∫cos³(x) dx");
}
#[test]
fn integrate_sin_cubed() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = x.sin().powi(3);
assert_ftc_custom(&integrand, &x, "∫sin³(x) dx");
}
#[test]
fn limit_x_squared_over_exp_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(2) / &x.exp();
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
assert_eq!(format!("{r}"), "0", "lim(x→∞) x²/exp(x) should be 0");
}
#[test]
fn limit_x_cubed_over_exp_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(3) / &x.exp();
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
assert_eq!(format!("{r}"), "0", "lim(x→∞) x³/exp(x) should be 0");
}
#[test]
fn limit_exp_x_over_x_n_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.exp() / &x.powi(10);
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let s = format!("{r}");
assert!(
s.contains("∞") || s.contains("oo") || s.contains("Inf"),
"lim(x→∞) exp(x)/x^10 should be ∞, got: {s}"
);
}
#[test]
fn limit_at_neg_infinity_polynomial() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.powi(3).try_limit(&x, &ctx.neg_infinity());
let r = result.expect("result must evaluate");
let s = format!("{r}");
assert!(
s.contains("-∞") || s.contains("-oo") || s.contains("-Inf") || s.contains("NegInf"),
"lim(x→-∞) x³ should be -∞, got: {s}"
);
}
#[test]
fn definite_integral_sin_x_over_x_limit_check() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x
.cos()
.integrate_definite(&x, &ctx.int(0), &(&ctx.pi() / 2));
let val = result.eval().eval_f64().expect("should evaluate");
assert!(
(val - 1.0).abs() < 1e-10,
"∫₀^(π/2) cos(x) dx should be 1, got {val}"
);
}
#[test]
fn definite_integral_2x_exp_x2_0_to_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &(&ctx.int(2) * &x) * &x.powi(2).exp();
let result = integrand.integrate_definite(&x, &ctx.int(0), &ctx.int(1));
let val = result.eval().eval_f64().expect("should evaluate");
let expected = std::f64::consts::E - 1.0;
assert!(
(val - expected).abs() < 1e-8,
"∫₀¹ 2x·exp(x²) dx should be e-1 ≈ {expected}, got {val}"
);
}
#[test]
fn definite_integral_1_over_x_1_to_2() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &x;
let result = integrand.integrate_definite(&x, &ctx.int(1), &ctx.int(2));
let val = result.eval().eval_f64().expect("should evaluate");
let expected = 2.0_f64.ln();
assert!(
(val - expected).abs() < 1e-10,
"∫₁² 1/x dx should be ln(2) ≈ {expected}, got {val}"
);
}
#[test]
fn integrate_negative_sin() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = -&x.sin();
assert_ftc_custom(&integrand, &x, "∫-sin(x) dx = cos(x)");
}
#[test]
fn integrate_3_sin_2x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(3) * &(&x * 2).sin();
assert_ftc_custom(&integrand, &x, "∫3·sin(2x) dx");
}
#[test]
fn integrate_exp_5x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = (&x * 5).exp();
assert_ftc_custom(&integrand, &x, "∫exp(5x) dx");
}
#[test]
fn diff_and_integrate_cancel_for_x_sin_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x * &x.sin();
let df = f.diff(&x);
let anti = df.integrate(&x);
let diff_at_1 = (&anti - &f).subs(&x, &ctx.int(1)).eval().eval_f64();
let diff_at_2 = (&anti - &f).subs(&x, &ctx.int(2)).eval().eval_f64();
if let (Ok(d1), Ok(d2)) = (diff_at_1, diff_at_2) {
assert!(
(d1 - d2).abs() < 1e-8,
"∫(d/dx(x·sin(x)))dx should differ from x·sin(x) by constant: \
diff@1={d1}, diff@2={d2}"
);
}
}
#[test]
fn diff_of_definite_integral_polynomial() {
let ctx = Context::new();
let x = ctx.symbol("x");
let t = ctx.symbol("t");
let integrand_t = &(&ctx.int(3) * &t.powi(2)) + 1;
let anti_t = integrand_t.integrate(&t);
let fx = &anti_t.subs(&t, &x) - &anti_t.subs(&t, &ctx.int(0));
let dfx = fx.diff(&x);
let expected = &(&ctx.int(3) * &x.powi(2)) + 1;
assert_exprs_equal_in_domain(&dfx, &expected, &x, "d/dx ∫₀ˣ(3t²+1)dt = 3x²+1");
}
#[test]
fn diff_implicit_x_cubed_y_cubed() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let f = &x.powi(3) * &y.powi(3);
let df = f.diff(&x);
let expected = &(&ctx.int(3) * &x.powi(2)) * &y.powi(3);
let val = df
.subs(&x, &ctx.int(2))
.subs(&y, &ctx.int(3))
.eval()
.eval_f64()
.expect("should eval");
let want = expected
.subs(&x, &ctx.int(2))
.subs(&y, &ctx.int(3))
.eval()
.eval_f64()
.expect("should eval");
assert!(
(val - want).abs() < 1e-8,
"∂/∂x(x³y³) at (2,3): got {val}, want {want}"
);
assert!(
(val - 324.0).abs() < 1e-8,
"∂/∂x(x³y³) at (2,3) should be 324, got {val}"
);
}
#[test]
fn diff_laplacian_of_x2y2() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let f = &x.powi(2) * &y.powi(2);
let fxx = f.diff(&x).diff(&x);
let fyy = f.diff(&y).diff(&y);
let laplacian = &fxx + &fyy;
let val = laplacian
.subs(&x, &ctx.int(3))
.subs(&y, &ctx.int(4))
.eval()
.eval_f64()
.expect("should eval");
assert!(
(val - 50.0).abs() < 1e-8,
"∇²(x²y²) at (3,4) should be 50, got {val}"
);
}
#[test]
fn diff_third_mixed_partial() {
let ctx = Context::new();
symplex::syms!(ctx; x, y, z);
let f = &(&x.powi(2) * &y.powi(3)) * &z;
let d3 = f.diff(&z).diff(&y).diff(&x);
let val = d3
.subs(&x, &ctx.int(2))
.subs(&y, &ctx.int(3))
.subs(&z, &ctx.int(1))
.eval()
.eval_f64()
.expect("should eval");
assert!(
(val - 108.0).abs() < 1e-8,
"∂³(x²y³z)/∂x∂y∂z at (2,3,1) should be 108, got {val}"
);
}
#[test]
fn integrate_1_over_x_minus_1_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &(&x - 1).powi(2);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_domain(
&integrand,
&x,
&[2.0, 3.0, 4.0, 5.0],
1e-7,
"∫1/(x-1)² dx = -1/(x-1)",
);
}
#[test]
fn integrate_x_over_x_plus_1_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x / &(&x + 1).powi(2);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_domain(&integrand, &x, &[0.5, 1.0, 2.0, 3.0], 1e-7, "∫x/(x+1)² dx");
}
#[test]
fn limit_x_squared_to_the_1_over_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.powi(2).pow(&(&ctx.int(1) / &x));
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
(val - 1.0).abs() < 1e-8,
"lim(x→∞) (x²)^(1/x) should be 1, got {val}"
);
}
#[test]
fn limit_exp_x_to_the_1_over_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.exp().pow(&(&ctx.int(1) / &x));
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
if let Ok(val) = r.eval_f64() {
let e = std::f64::consts::E;
assert!(
(val - e).abs() < 1e-6,
"lim(x→∞) exp(x)^(1/x) should be e ≈ {e}, got {val} (symbolic: {r})"
);
}
}
#[test]
fn limit_1_plus_1_over_x_to_2x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let base = &ctx.int(1) + &(&ctx.int(1) / &x);
let expr = base.pow(&(&ctx.int(2) * &x));
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
let expected = std::f64::consts::E.powi(2);
assert!(
(val - expected).abs() < 1e-12,
"lim(x→∞) (1+1/x)^(2x) should be e² ≈ {expected}, got {r} = {val}"
);
}
#[test]
fn limit_x_plus_1_over_x_to_the_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let base = &(&x + 1) / &x;
let expr = base.pow(&x);
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
(val - std::f64::consts::E).abs() < 1e-12,
"lim(x→∞) ((x+1)/x)^x should be e, got {r} = {val}"
);
}
#[test]
fn diff_of_x_to_negative_fraction() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.pow(&ctx.rational(-1, 2));
let df = f.diff(&x);
let val = df
.subs(&x, &ctx.int(4))
.eval()
.eval_f64()
.expect("should eval");
assert!(
(val - (-0.0625)).abs() < 1e-10,
"d/dx x^(-1/2) at x=4 should be -0.0625, got {val}"
);
}
#[test]
fn diff_of_rational_power() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.pow(&ctx.rational(2, 3));
let df = f.diff(&x);
let val = df
.subs(&x, &ctx.int(8))
.eval()
.eval_f64()
.expect("should eval");
assert!(
(val - 1.0 / 3.0).abs() < 1e-9,
"d/dx x^(2/3) at x=8 should be 1/3, got {val}"
);
}
#[test]
fn diff_second_derivative_of_arctan() {
let ctx = Context::new();
let x = ctx.symbol("x");
let d2 = x.atan().diff_n(&x, 2);
let expected = &(&ctx.int(-2) * &x) / &(&x.powi(2) + 1).powi(2);
let got = d2
.subs(&x, &ctx.int(1))
.eval()
.eval_f64()
.expect("should eval");
let want = expected
.subs(&x, &ctx.int(1))
.eval()
.eval_f64()
.expect("should eval");
assert!(
(got - want).abs() < 1e-9,
"d²/dx²(arctan(x)) at x=1: got {got}, want {want}"
);
assert!(
(got - (-0.5)).abs() < 1e-9,
"d²/dx²(arctan(x)) at x=1 should be -0.5, got {got}"
);
}
#[test]
fn integrate_minus_exp_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = -&x.exp();
assert_ftc_custom(&integrand, &x, "∫-exp(x) dx = -exp(x)");
}
#[test]
fn integrate_1_over_2x_plus_1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &ctx.int(1) / &(&(&x * 2) + 1);
let antideriv = integrand.integrate(&x);
let s = format!("{antideriv}");
if s.contains("Integral") {
return;
}
assert_ftc_custom(&integrand, &x, "∫1/(2x+1) dx");
}
#[test]
fn definite_integral_reversal() {
let ctx = Context::new();
let x = ctx.symbol("x");
let forward = x.powi(2).integrate_definite(&x, &ctx.int(0), &ctx.int(1));
let backward = x.powi(2).integrate_definite(&x, &ctx.int(1), &ctx.int(0));
let v_fwd = forward.eval().eval_f64().expect("should eval");
let v_bwd = backward.eval().eval_f64().expect("should eval");
assert!(
(v_fwd + v_bwd).abs() < 1e-10,
"∫₀¹ x² dx + ∫₁⁰ x² dx should be 0: got {} + {} = {}",
v_fwd,
v_bwd,
v_fwd + v_bwd
);
}
#[test]
fn definite_integral_additivity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let full = x.clone().integrate_definite(&x, &ctx.int(0), &ctx.int(2));
let part1 = x.clone().integrate_definite(&x, &ctx.int(0), &ctx.int(1));
let part2 = x.clone().integrate_definite(&x, &ctx.int(1), &ctx.int(2));
let v_full = full.eval().eval_f64().expect("should eval");
let v_parts = part1.eval().eval_f64().expect("p1") + part2.eval().eval_f64().expect("p2");
assert!(
(v_full - v_parts).abs() < 1e-10,
"∫₀² x dx should equal ∫₀¹ x dx + ∫₁² x dx: {v_full} vs {v_parts}"
);
}
#[test]
fn series_sin_has_no_even_power_terms() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.sin().maclaurin(&x, 8).expand().eval();
let val_pos = eval_series_at(&series, &x, 1, 2);
let val_neg = eval_series_at(&series, &x, -1, 2);
assert!(
(val_pos + val_neg).abs() < 1e-12,
"sin(x) series should be odd: s(0.5)={val_pos}, s(-0.5)={val_neg}, \
sum={}",
val_pos + val_neg
);
}
#[test]
fn series_cos_has_no_odd_power_terms() {
let ctx = Context::new();
let x = ctx.symbol("x");
let series = x.cos().maclaurin(&x, 8).expand().eval();
let val_pos = eval_series_at(&series, &x, 1, 2);
let val_neg = eval_series_at(&series, &x, -1, 2);
assert!(
(val_pos - val_neg).abs() < 1e-12,
"cos(x) series should be even: s(0.5)={val_pos}, s(-0.5)={val_neg}, \
diff={}",
val_pos - val_neg
);
}
#[test]
fn series_exp_differentiates_to_itself() {
let ctx = Context::new();
let x = ctx.symbol("x");
let exp_series = x.exp().maclaurin(&x, 8).expand().eval();
let deriv = exp_series.diff(&x);
let val_series = eval_series_at(&exp_series, &x, 1, 2);
let val_deriv = eval_series_at(&deriv, &x, 1, 2);
assert!(
(val_series - val_deriv).abs() < 1e-4,
"d/dx(exp series) should ≈ exp series at x=0.5: series={val_series}, deriv={val_deriv}"
);
}
#[test]
fn limit_exp_x_at_neg_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.exp().try_limit(&x, &ctx.neg_infinity());
let r = result.expect("result must evaluate");
assert_eq!(format!("{r}"), "0", "lim(x→-∞) exp(x) should be 0");
}
#[test]
fn limit_1_over_1_plus_exp_neg_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &ctx.int(1) / &(&ctx.int(1) + &(-&x).exp());
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
(val - 1.0).abs() < 1e-8,
"lim(x→∞) sigmoid should be 1, got {val}"
);
}
#[test]
fn limit_1_over_1_plus_exp_neg_x_at_neg_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &ctx.int(1) / &(&ctx.int(1) + &(-&x).exp());
let result = expr.try_limit(&x, &ctx.neg_infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(val.abs() < 1e-8, "lim(x→-∞) sigmoid should be 0, got {val}");
}
#[test]
fn integrate_x_cubed_exp_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x.powi(3) * &x.exp();
assert_ftc_custom(&integrand, &x, "∫x³·exp(x) dx");
}
#[test]
fn integrate_x_squared_sin_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x.powi(2) * &x.sin();
assert_ftc_custom(&integrand, &x, "∫x²·sin(x) dx");
}
#[test]
fn integrate_x_squared_cos_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = &x.powi(2) * &x.cos();
assert_ftc_custom(&integrand, &x, "∫x²·cos(x) dx");
}
#[test]
fn roundtrip_diff_integrate_exp_sin() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.exp() * &x.sin();
let df = f.diff(&x);
let anti = df.integrate(&x);
let diff_at_1 = (&anti - &f).subs(&x, &ctx.int(1)).eval().eval_f64();
let diff_at_2 = (&anti - &f).subs(&x, &ctx.int(2)).eval().eval_f64();
if let (Ok(d1), Ok(d2)) = (diff_at_1, diff_at_2) {
assert!(
(d1 - d2).abs() < 1e-8,
"∫(d/dx(exp·sin))dx - exp·sin should be constant: @1={d1}, @2={d2}"
);
}
}
#[test]
fn roundtrip_diff_integrate_x_ln_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x * &x.ln();
let df = f.diff(&x); let anti = df.integrate(&x);
let diff_at_2 = (&anti - &f).subs(&x, &ctx.int(2)).eval().eval_f64();
let diff_at_3 = (&anti - &f).subs(&x, &ctx.int(3)).eval().eval_f64();
if let (Ok(d2), Ok(d3)) = (diff_at_2, diff_at_3) {
assert!(
(d2 - d3).abs() < 1e-8,
"∫(d/dx(x·ln(x)))dx - x·ln(x) should be constant: @2={d2}, @3={d3}"
);
}
}
#[test]
fn limit_x_to_the_1_over_ln_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.pow(&(&ctx.int(1) / &x.ln()));
let result = expr.try_limit(&x, &ctx.infinity());
let r = result.expect("result must evaluate");
let val = r.eval_f64().expect("limit should evaluate");
assert!(
(val - std::f64::consts::E).abs() < 1e-6,
"lim(x→∞) x^(1/ln(x)) should be e, got {val}"
);
}
#[test]
fn limit_n_to_the_1_over_n_discrete_style() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.pow(&(&ctx.int(1) / &x));
for &n in &[5i64, 10, 20] {
let val = f.subs_i64(&x, n).eval().eval_f64().expect("should eval");
assert!(
(val - 1.0).abs() < 0.5,
"x^(1/x) at x={n} should be close to 1, got {val}"
);
}
}