#![allow(clippy::excessive_precision)]
use symplex::prelude::*;
const POINTS: [(i64, i64); 4] = [(1, 3), (-5, 7), (13, 11), (-17, 5)];
fn close(actual: f64, expected: f64, rel: f64, label: &str) {
if expected.abs() < 1e-15 && actual.abs() < 1e-15 {
return;
}
let scale = expected.abs().max(1e-300);
assert!(
(actual - expected).abs() <= rel * scale,
"{label}: got {actual:e}, expected {expected:e} (relative error {:e})",
(actual - expected).abs() / scale
);
}
fn assert_antiderivative(f: &Ex, x: &Ex, f_ref: &[f64; 4], label: &str) -> Ex {
let big_f = f.integrate(x);
assert!(
!big_f.has_unevaluated(),
"{label}: expected a closed form, got {big_f}"
);
let d_big_f = big_f.diff(x);
let ctx = x.context();
for (&(n, d), &expected) in POINTS.iter().zip(f_ref) {
let p = ctx.rational(n, d);
let f_at = f
.subs(x, &p)
.eval_f64()
.unwrap_or_else(|e| panic!("{label}: f({n}/{d}) did not evaluate: {e}"));
close(
f_at,
expected,
1e-12,
&format!("{label}: f({n}/{d}) vs sympy"),
);
let df_at = d_big_f
.subs(x, &p)
.eval_f64()
.unwrap_or_else(|e| panic!("{label}: F'({n}/{d}) did not evaluate: {e} (F = {big_f})"));
close(
df_at,
expected,
1e-10,
&format!("{label}: F'({n}/{d}) vs f, F = {big_f}"),
);
}
big_f
}
fn assert_derivative_simplifies_to_f(f: &Ex, big_f: &Ex, x: &Ex, label: &str) {
let residual = (big_f.diff(x) - f).simplify();
assert_eq!(
residual.to_string(),
"0",
"{label}: (F' - f).simplify() should be 0, F = {big_f}"
);
}
#[test]
fn sqrt_of_x_squared_integrates_as_abs_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.pow(&ctx.int(2)).sqrt();
let big_f = assert_antiderivative(
&f,
&x,
&[
0.33333333333333333,
0.71428571428571429,
1.1818181818181818,
3.4000000000000000,
],
"∫ sqrt(x^2)",
);
assert_ne!(big_f.to_string(), "1/2*x^2", "∫ sqrt(x^2) must not be ∫ x");
}
#[test]
fn neg_sqrt_of_x_squared_integrates_as_neg_abs_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = -x.pow(&ctx.int(2)).sqrt();
assert_antiderivative(
&f,
&x,
&[
-0.33333333333333333,
-0.71428571428571429,
-1.1818181818181818,
-3.4000000000000000,
],
"∫ -sqrt(x^2)",
);
}
#[test]
fn x_squared_to_three_halves_is_abs_x_cubed() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.pow(&ctx.int(2)).pow(&ctx.rational(3, 2));
assert_antiderivative(
&f,
&x,
&[
0.037037037037037037,
0.36443148688046647,
1.6506386175807663,
39.304000000000000,
],
"∫ (x^2)^(3/2)",
);
}
#[test]
fn sqrt_of_x_fourth_flattens_to_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.pow(&ctx.int(4)).sqrt();
let big_f = assert_antiderivative(
&f,
&x,
&[
0.11111111111111111,
0.51020408163265306,
1.3966942148760331,
11.560000000000000,
],
"∫ sqrt(x^4)",
);
assert_eq!(big_f.to_string(), "1/3*x^3");
}
#[test]
fn reciprocal_sqrt_flattening_still_reaches_asinh() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ctx.int(1) / (x.pow(&ctx.int(2)) + 1).sqrt();
let big_f = assert_antiderivative(
&f,
&x,
&[
0.94868329805051380,
0.81373347120673496,
0.64594224146617384,
0.28216632399155017,
],
"∫ 1/sqrt(x^2+1)",
);
assert_eq!(big_f.to_string(), "asinh(x)");
}
#[test]
fn neg_of_product_sin_plus_one_times_cos() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = -((x.sin() + 1) * x.cos());
let big_f = assert_antiderivative(
&f,
&x,
&[
-1.2541418478496062,
-0.26060980851463465,
-0.73015561900524185,
1.2138548681487652,
],
"∫ -(sin x + 1) cos x",
);
assert_derivative_simplifies_to_f(&f, &big_f, &x, "∫ -(sin x + 1) cos x");
}
#[test]
fn three_times_cos_minus_four_times_sin() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (x.cos() - 4) * x.sin() * 3;
let big_f = assert_antiderivative(
&f,
&x,
&[
-2.9987816569492214,
6.3760801515840370,
-10.050827316564941,
-3.8076632510298883,
],
"∫ 3 (cos x - 4) sin x",
);
assert_derivative_simplifies_to_f(&f, &big_f, &x, "∫ 3 (cos x - 4) sin x");
}
#[test]
fn minus_two_times_cos_minus_four_times_sin() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (x.cos() - 4) * x.sin() * -2;
let big_f = assert_antiderivative(
&f,
&x,
&[
1.9991877712994809,
-4.2507201010560247,
6.7005515443766276,
2.5384421673532589,
],
"∫ -2 (cos x - 4) sin x",
);
assert_derivative_simplifies_to_f(&f, &big_f, &x, "∫ -2 (cos x - 4) sin x");
}
#[test]
fn three_cos_over_sin_squared_plus_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.cos() * 3 / (x.sin().pow(&ctx.int(2)) + 1);
let big_f = assert_antiderivative(
&f,
&x,
&[
2.5607285380951193,
1.5860619515432745,
0.61294300386480045,
-2.7226050514833956,
],
"∫ 3 cos x/(sin²x + 1)",
);
assert_eq!(big_f.to_string(), "3*atan(sin(x))");
}
#[test]
fn cos_over_sin_squared_plus_one_is_atan_sin() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.cos() / (x.sin().pow(&ctx.int(2)) + 1);
let big_f = assert_antiderivative(
&f,
&x,
&[
0.85357617936503976,
0.52868731718109150,
0.20431433462160015,
-0.90753501716113186,
],
"∫ cos x/(sin²x + 1)",
);
assert_eq!(big_f.to_string(), "atan(sin(x))");
assert_derivative_simplifies_to_f(&f, &big_f, &x, "∫ cos x/(sin²x + 1)");
}
#[test]
fn minus_two_cos_over_sin_squared_plus_four() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.cos() * -2 / (x.sin().pow(&ctx.int(2)) + 4);
let big_f = assert_antiderivative(
&f,
&x,
&[
-0.46016263779896368,
-0.34117844800441081,
-0.15619005457761024,
0.47563421846387038,
],
"∫ -2 cos x/(sin²x + 4)",
);
assert_eq!(big_f.to_string(), "-atan(1/2*sin(x))");
}
#[test]
fn three_sin_over_scaled_cos_squared_plus_four() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.sin() * 3 / (x.cos().pow(&ctx.int(2)) * ctx.rational(9, 4) + 4);
let big_f = assert_antiderivative(
&f,
&x,
&[
0.16334897170264714,
-0.37188892601949924,
0.64203134933413098,
0.12561268880521519,
],
"∫ 3 sin x/(9/4 cos²x + 4)",
);
assert_eq!(big_f.to_string(), "-atan(3/4*cos(x))");
}
#[test]
fn one_over_sin_squared_plus_four() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ctx.int(1) / (x.sin().pow(&ctx.int(2)) + 4);
assert_antiderivative(
&f,
&x,
&[
0.24348338810226434,
0.22577812476394662,
0.20592339995226665,
0.24598423027398144,
],
"∫ 1/(sin²x + 4)",
);
}
#[test]
fn sin_over_cos_squared_plus_one_unchanged() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.sin() / (x.cos().pow(&ctx.int(2)) + 1);
let big_f = assert_antiderivative(
&f,
&x,
&[
0.17284967790034225,
-0.41701520021118986,
0.80894963134186558,
0.13208314868872689,
],
"∫ sin x/(cos²x + 1)",
);
assert_eq!(big_f.to_string(), "-atan(cos(x))");
}
#[test]
fn one_over_x_squared_minus_four_squared_plus_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ctx.int(1) / ((x.pow(&ctx.int(2)) - 4).pow(&ctx.int(2)) + 1);
let big_f = assert_antiderivative(
&f,
&x,
&[
0.062021439509954058,
0.075880159281967006,
0.12858096358877979,
0.017195839982391460,
],
"∫ 1/((x²-4)²+1)",
);
let s = big_f.to_string();
assert!(
!s.contains("re(") && !s.contains("im("),
"closed form must not contain opaque re/im constants: {s}"
);
}
#[test]
fn ln_of_x_squared_minus_four_squared_plus_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ((x.pow(&ctx.int(2)) - 4).pow(&ctx.int(2)) + 1).ln();
let big_f = assert_antiderivative(
&f,
&x,
&[
2.7802751551639376,
2.5786000347877560,
2.0511965062168838,
4.0630877859048051,
],
"∫ ln((x²-4)²+1)",
);
let s = big_f.to_string();
assert!(
!s.contains("re(") && !s.contains("im("),
"closed form must not contain opaque re/im constants: {s}"
);
}
#[test]
fn biquadratic_with_real_negative_roots_in_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ctx.int(1) / (x.pow(&ctx.int(4)) + x.pow(&ctx.int(2)) * 6 + 1);
assert_antiderivative(
&f,
&x,
&[
0.59558823529411765,
0.23139938319198150,
0.088254086897815499,
0.0049021145761435653,
],
"∫ 1/(x⁴+6x²+1)",
);
}
#[test]
fn biquadratic_with_cubic_numerator() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (x.pow(&ctx.int(3)) + 1) / (x.pow(&ctx.int(4)) + x.pow(&ctx.int(2)) * 3 + 1);
assert_antiderivative(
&f,
&x,
&[
0.77064220183486239,
0.22772720489479182,
0.37119436819099178,
-0.22623108834730347,
],
"∫ (x³+1)/(x⁴+3x²+1)",
);
}
#[test]
fn one_over_x_fourth_plus_one_unchanged() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ctx.int(1) / (x.pow(&ctx.int(4)) + 1);
let big_f = assert_antiderivative(
&f,
&x,
&[
0.98780487804878049,
0.79345670852610707,
0.33889634739132448,
0.0074275663727331067,
],
"∫ 1/(x⁴+1)",
);
let s = big_f.to_string();
assert!(
s.contains("atan") && s.contains("ln"),
"expected atan/ln form: {s}"
);
}
#[test]
fn x_cubed_over_shifted_biquadratic() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = x.pow(&ctx.int(3)) / (x.pow(&ctx.int(4)) - x.pow(&ctx.int(2)) * 8 + 17);
assert_antiderivative(
&f,
&x,
&[
0.0022970903522205207,
-0.027653119271853865,
0.21224070398538633,
-0.67586529466791394,
],
"∫ x³/(x⁴-8x²+17)",
);
}
#[test]
fn general_quartic_is_never_silently_wrong() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ctx.int(1) / (x.pow(&ctx.int(4)) + &x + 1);
let big_f = f.integrate(&x);
if big_f.has_unevaluated() {
return;
}
let d_big_f = big_f.diff(&x);
let f_ref = [
0.74311926605504587,
1.8314263920671243,
0.24198000165275597,
0.0076199997561600078,
];
for (&(n, d), &expected) in POINTS.iter().zip(&f_ref) {
let p = ctx.rational(n, d);
let df_at = d_big_f
.subs(&x, &p)
.eval_f64()
.unwrap_or_else(|e| panic!("F'({n}/{d}) did not evaluate: {e} (F = {big_f})"));
close(
df_at,
expected,
1e-10,
&format!("∫ 1/(x⁴+x+1): F'({n}/{d}), F = {big_f}"),
);
}
}
#[test]
fn u_substitution_rejects_factor_with_bare_var() {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = (&x + x.sin().pow(&ctx.int(2))) * x.cos();
let big_f = f.integrate(&x);
if big_f.has_unevaluated() {
return;
}
let d_big_f = big_f.diff(&x);
let f_ref = [
0.41614930888322871,
-0.21545486337458458,
0.77289471651060822,
3.2239807196321021,
];
for (&(n, d), &expected) in POINTS.iter().zip(&f_ref) {
let p = ctx.rational(n, d);
let df_at = d_big_f
.subs(&x, &p)
.eval_f64()
.unwrap_or_else(|e| panic!("F'({n}/{d}) did not evaluate: {e} (F = {big_f})"));
close(
df_at,
expected,
1e-10,
&format!("∫ (x + sin²x) cos x: F'({n}/{d}), F = {big_f}"),
);
}
}