use symplex::prelude::*;
fn assert_rationalize_preserves_value(expr: &Ex, label: &str) {
let ctx = expr.context();
let rationalized = expr.rationalize_denom();
let test_point = ctx.rational(3, 2);
let x = ctx.symbol("x");
let orig = expr.subs(&x, &test_point).eval_f64();
let rat = rationalized.subs(&x, &test_point).eval_f64();
if let (Ok(o), Ok(r)) = (orig, rat)
&& o.is_finite()
&& r.is_finite()
{
let diff = (o - r).abs();
let tol = 1e-10 * o.abs().max(1.0);
assert!(
diff < tol,
"{label}: value changed by rationalization — orig={o}, rationalized={r}"
);
}
}
#[test]
fn rationalize_one_over_sqrt2() {
let ctx = Context::new();
let expr = ctx.int(1) / &ctx.int(2).sqrt();
let result = expr.rationalize_denom();
let s = format!("{result}");
assert!(s.contains('2'), "should rationalize 1/√2: {s}");
assert_rationalize_preserves_value(&expr, "1/√2");
}
#[test]
fn rationalize_one_over_one_plus_sqrt2() {
let ctx = Context::new();
let sqrt2 = ctx.int(2).sqrt();
let expr = ctx.int(1) / &(&ctx.int(1) + &sqrt2);
assert_rationalize_preserves_value(&expr, "1/(1+√2)");
}
#[test]
fn rationalize_preserves_no_sqrt_denom() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x / &ctx.int(3);
let result = expr.rationalize_denom();
assert_eq!(
format!("{result}"),
format!("{expr}"),
"no-sqrt denom should be unchanged"
);
}
#[test]
fn rationalize_integer_unchanged() {
let ctx = Context::new();
let expr = ctx.int(5);
let result = expr.rationalize_denom();
assert_eq!(format!("{result}"), "5");
}
#[test]
fn rationalize_three_over_sqrt2() {
let ctx = Context::new();
let expr = &ctx.int(3) / &ctx.int(2).sqrt();
let result = expr.rationalize_denom();
let s = format!("{result}");
assert!(s.contains('3'), "numerator 3 should survive: {s}");
assert_rationalize_preserves_value(&expr, "3/√2");
}
#[test]
fn limit_constant_expression() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = ctx.int(5);
let result = expr.limit(&x, &ctx.int(0));
assert!(!result.has_unevaluated());
assert_eq!(format!("{result}"), "5");
}
#[test]
fn limit_polynomial_direct_sub() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &(x.powi(2) + &x) + &ctx.int(1);
let result = expr.limit(&x, &ctx.int(2));
assert_eq!(format!("{result}"), "7");
}
#[test]
fn limit_sin_x_over_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.sin() / &x;
let result = expr.limit(&x, &ctx.int(0));
assert!(!result.has_unevaluated());
assert_eq!(format!("{result}"), "1");
}
#[test]
fn limit_lhopital_x2_minus1_over_x_minus1() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = (x.powi(2) - 1) / (&x - 1);
let result = expr.limit(&x, &ctx.int(1));
assert!(!result.has_unevaluated());
assert_eq!(format!("{result}"), "2");
}
#[test]
fn limit_at_infinity_polynomial_ratio() {
let ctx = Context::new();
let x = ctx.symbol("x");
let numer = &x.powi(2) * 3 + &x;
let denom = x.powi(2) + 1;
let expr = &numer / &denom;
let result = expr.limit(&x, &ctx.infinity());
assert_eq!(format!("{result}"), "3");
}
#[test]
fn limit_exp_neg_x_at_infinity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = (-&x).exp();
let result = expr.limit(&x, &ctx.infinity());
assert_eq!(format!("{result}"), "0", "lim exp(-x) at ∞ should be 0");
}
#[test]
fn limit_fallback() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.sin();
let result = expr.limit(&x, &ctx.int(0));
let s = format!("{result}");
assert_eq!(s, "0", "sin(0) should be 0, got: {s}");
}