mod common;
use proptest::prelude::*;
use symplex::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn laplace_roundtrip(c in 1..5i64, a in 1..4i64) {
let ctx = Context::new();
let t = ctx.symbol("t");
let s = ctx.symbol("s");
let expr = (&t * a).exp() * c;
let mut bail = common::BailCounter::new("laplace_roundtrip");
let transformed = expr.laplace(&t, &s);
if transformed.has_unevaluated() {
bail.skip();
} else {
let recovered = transformed.inverse_laplace(&s, &t);
if recovered.has_unevaluated() {
bail.skip();
} else {
let test_point = ctx.rational(1, 2);
let orig_val = expr.subs(&t, &test_point).eval_f64();
let recov_val = recovered.subs(&t, &test_point).eval().simplify().eval_f64();
if let (Ok(o), Ok(r)) = (orig_val, recov_val) {
if o.is_finite() && r.is_finite() {
bail.check();
prop_assert!((o - r).abs() < 1e-6 * o.abs().max(1.0),
"Laplace roundtrip failed: orig={}, recovered={}, c={}, a={}", o, r, c, a);
} else {
bail.skip();
}
} else {
bail.skip();
}
}
}
bail.assert_not_vacuous();
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn inequality_solution_satisfies(a in -5..5i64, b in -5..5i64, c in 1..5i64) {
let ctx = Context::new();
let x = ctx.symbol("x");
let a_coeff = a.max(1);
let poly = &x.powi(2) * a_coeff + &x * b + c;
let solution = poly.solve_gt(&x);
let s = format!("{solution}");
prop_assert!(!s.is_empty(),
"solve_gt should produce non-empty display for {}*x^2 + {}*x + {}", a_coeff, b, c);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn compact_preserves_value(a in -10..10i64, b in -10..10i64, n in 1..5i64) {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(n) * a + b;
let _ = x.sin().cos().exp().ln();
let _ = x.powi(10).expand();
let original_display = format!("{expr}");
let (new_ctx, new_exprs) = ctx.compact(std::slice::from_ref(&expr));
let new_display = format!("{}", new_exprs[0]);
prop_assert_eq!(original_display, new_display);
let two = ctx.int(2);
let new_two = new_ctx.int(2);
let new_x = new_ctx.symbol("x");
let orig_val = expr.subs(&x, &two).eval_f64();
let new_val = new_exprs[0].subs(&new_x, &new_two).eval_f64();
let mut bail = common::BailCounter::new("compact_preserves_value");
if let (Ok(o), Ok(n_v)) = (orig_val, new_val) {
if o.is_finite() && n_v.is_finite() {
bail.check();
prop_assert!((o - n_v).abs() < 1e-10,
"compact changed value at x=2: {} → {} (a={}, b={}, n={})", o, n_v, a, b, n);
} else {
bail.skip();
}
} else {
bail.skip();
}
bail.assert_not_vacuous();
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(15))]
#[test]
fn multinomial_term_count(n in 2..8usize) {
let ctx = Context::new();
symplex::syms!(ctx; a, b);
let expanded = (&a + &b).powi(n as i64).expand();
prop_assert_eq!(expanded.term_count(), n + 1,
"(a+b)^{} should have {} terms, got {}", n, n + 1, expanded.term_count());
}
#[test]
fn trinomial_term_count(n in 2..6usize) {
let ctx = Context::new();
symplex::syms!(ctx; a, b, c);
let expanded = (&a + &b + &c).powi(n as i64).expand();
let expected = (n + 1) * (n + 2) / 2;
prop_assert_eq!(expanded.term_count(), expected,
"(a+b+c)^{} should have {} terms, got {}", n, expected, expanded.term_count());
}
}