use symplex::expr::ExprType;
use symplex::prelude::*;
#[test]
fn integrate_tan_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.tan().integrate(&x);
let s = format!("{result}");
assert!(
s.contains("ln") && s.contains("cos"),
"∫ tan(x) dx should be -ln|cos(x)|, got: {s}"
);
}
#[test]
fn integrate_tan_x_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.tan().integrate(&x);
assert_eq!(format!("{result}"), "-ln(abs(cos(x)))");
}
#[test]
fn integrate_ln_x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.ln().integrate(&x);
let s = format!("{result}");
assert!(
s.contains("ln") && s.contains("x"),
"∫ ln(x) dx should involve x*ln(x), got: {s}"
);
}
#[test]
fn integrate_ln_x_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let result = x.ln().integrate(&x);
assert_eq!(format!("{result}"), "-x + x*ln(x)");
}
#[test]
fn integrate_one_over_x_squared_plus_one() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = (&x.powi(2) + 1).powi(-1);
let result = integrand.integrate(&x);
let s = format!("{result}");
assert!(
s.contains("atan"),
"∫ 1/(x²+1) dx should be atan(x), got: {s}"
);
}
#[test]
fn integrate_one_over_x_squared_plus_one_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integrand = (&x.powi(2) + 1).powi(-1);
let result = integrand.integrate(&x);
assert_eq!(format!("{result}"), "atan(x)");
}
#[test]
fn integrate_one_over_sqrt_one_minus_x_squared() {
let ctx = Context::new();
let x = ctx.symbol("x");
let one = ctx.int(1);
let inner = &one - &x.powi(2);
let integrand = inner.pow(&ctx.rational(-1, 2));
let result = integrand.integrate(&x);
let s = format!("{result}");
assert!(
s.contains("asin"),
"∫ 1/√(1-x²) dx should be asin(x), got: {s}"
);
}
#[test]
fn integrate_one_over_sqrt_one_minus_x_squared_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let one = ctx.int(1);
let inner = &one - &x.powi(2);
let integrand = inner.pow(&ctx.rational(-1, 2));
let result = integrand.integrate(&x);
assert_eq!(format!("{result}"), "asin(x)");
}
#[test]
fn integrate_tan_2x() {
let ctx = Context::new();
let x = ctx.symbol("x");
let two_x = &x * 2;
let result = two_x.tan().integrate(&x);
let s = format!("{result}");
assert!(
s.contains("ln") && s.contains("cos"),
"∫ tan(2x) dx should involve ln and cos, got: {s}"
);
}
#[test]
fn integrate_tan_2x_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let two_x = &x * 2;
let result = two_x.tan().integrate(&x);
assert_eq!(format!("{result}"), "-1/2*ln(abs(cos(2*x)))");
}
#[test]
fn integrate_ln_roundtrip() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integral = x.ln().integrate(&x);
let deriv = integral.diff(&x);
let simplified = deriv.simplify();
let s = format!("{simplified}");
assert!(
s.contains("ln"),
"d/dx(∫ ln(x) dx) should simplify to ln(x), got: {s}"
);
}
#[test]
fn integrate_ln_roundtrip_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integral = x.ln().integrate(&x);
let deriv = integral.diff(&x);
let simplified = deriv.simplify();
assert_eq!(format!("{simplified}"), "ln(x)");
}
#[test]
fn integrate_tan_roundtrip() {
let ctx = Context::new();
let x = ctx.symbol("x");
let integral = x.tan().integrate(&x);
let deriv = integral.diff(&x);
let simplified = deriv.simplify();
let s = format!("{simplified}");
assert!(
s.contains("tan") || s.contains("sin") || s.contains("cos"),
"d/dx(∫ tan(x) dx) should simplify back to tan(x) or equivalent, got: {s}"
);
}
#[test]
fn simplify_sin_over_cos() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.sin() / &x.cos();
let simplified = expr.simplify();
assert_eq!(format!("{simplified}"), "tan(x)");
}
#[test]
fn simplify_sinh_over_cosh() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.sinh() / &x.cosh();
let simplified = expr.simplify();
assert_eq!(format!("{simplified}"), "tanh(x)");
}
#[test]
fn simplify_exp_product() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let expr = &x.exp() * &y.exp();
let simplified = expr.simplify();
let s = format!("{simplified}");
assert!(
s.contains("exp"),
"exp(x)*exp(y) should simplify to exp(x+y), got: {s}"
);
}
#[test]
fn simplify_exp_product_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let expr = &x.exp() * &y.exp();
let simplified = expr.simplify();
assert_eq!(format!("{simplified}"), "exp(x + y)");
}
#[test]
fn simplify_sin_over_cos_in_larger_product() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &(&x.sin() / &x.cos()) * 2;
let simplified = expr.simplify();
let s = format!("{simplified}");
assert!(
s.contains("tan"),
"2*sin(x)/cos(x) should simplify to 2*tan(x), got: {s}"
);
}
#[test]
fn simplify_sin_over_cos_in_larger_product_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &(&x.sin() / &x.cos()) * 2;
let simplified = expr.simplify();
assert_eq!(format!("{simplified}"), "2*tan(x)");
}
#[test]
fn logcombine_two_logs() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let expr = &x.ln() + &y.ln();
let combined = expr.log_combine();
let s = format!("{combined}");
assert!(
s.contains("ln"),
"ln(x)+ln(y) should combine to ln(x*y), got: {s}"
);
}
#[test]
fn logcombine_two_logs_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let expr = &x.ln() + &y.ln();
let combined = expr.log_combine();
assert_eq!(format!("{combined}"), "ln(x*y)");
}
#[test]
fn logcombine_then_expand_roundtrip() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let product = &x * &y;
let ln_product = product.ln();
let expanded = ln_product.expand_log();
let recombined = expanded.log_combine();
let s = format!("{recombined}");
assert!(
s.contains("ln"),
"roundtrip should produce ln(...), got: {s}"
);
}
#[test]
fn logcombine_roundtrip_exact() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let product = &x * &y;
let ln_product = product.ln();
assert_eq!(format!("{ln_product}"), "ln(x*y)");
let expanded = ln_product.expand_log();
assert_eq!(format!("{expanded}"), "ln(x) + ln(y)");
let recombined = expanded.log_combine();
assert_eq!(format!("{recombined}"), "ln(x*y)");
}
#[test]
fn eval_sin_2pi_over_3() {
let ctx = Context::new();
let angle = &ctx.rational(2, 3) * &ctx.pi();
let result = angle.sin().eval();
assert_eq!(format!("{result}"), "1/2*sqrt(3)");
}
#[test]
fn eval_sin_3pi_over_4() {
let ctx = Context::new();
let angle = &ctx.rational(3, 4) * &ctx.pi();
let result = angle.sin().eval();
assert_eq!(format!("{result}"), "1/2*sqrt(2)");
}
#[test]
fn eval_cos_5pi_over_6() {
let ctx = Context::new();
let angle = &ctx.rational(5, 6) * &ctx.pi();
let result = angle.cos().eval();
assert_eq!(format!("{result}"), "-1/2*sqrt(3)");
}
#[test]
fn eval_tan_pi_over_6() {
let ctx = Context::new();
let angle = &ctx.rational(1, 6) * &ctx.pi();
let result = angle.tan().eval();
let s = format!("{result}");
assert!(s.contains("3"), "tan(π/6) should involve √3, got: {s}");
}
#[test]
fn eval_tan_pi_over_6_exact() {
let ctx = Context::new();
let angle = &ctx.rational(1, 6) * &ctx.pi();
let result = angle.tan().eval();
assert_eq!(format!("{result}"), "1/3*sqrt(3)");
}
#[test]
fn eval_tan_pi_over_3() {
let ctx = Context::new();
let angle = &ctx.rational(1, 3) * &ctx.pi();
let result = angle.tan().eval();
assert_eq!(format!("{result}"), "sqrt(3)");
}
#[test]
fn eval_sin_pi_over_6_exact() {
let ctx = Context::new();
let expr = (&ctx.pi() / 6).sin();
let result = expr.eval();
assert_eq!(format!("{result}"), "1/2");
}
#[test]
fn eval_cos_pi_over_3_exact() {
let ctx = Context::new();
let expr = (&ctx.pi() / 3).cos();
let result = expr.eval();
assert_eq!(format!("{result}"), "1/2");
}
#[test]
fn eval_tan_pi_over_4_exact() {
let ctx = Context::new();
let expr = (&ctx.pi() / 4).tan();
let result = expr.eval();
assert_eq!(format!("{result}"), "1");
}
#[test]
fn ex_zero() {
let ctx = Context::new();
let z = ctx.zero();
assert_eq!(format!("{z}"), "0");
assert!(z.is_zero_structural());
}
#[test]
fn ex_one() {
let ctx = Context::new();
let o = ctx.one();
assert_eq!(format!("{o}"), "1");
assert!(o.is_one_structural());
}
#[test]
fn ex_zero_is_not_one() {
let ctx = Context::new();
let z = ctx.zero();
assert!(!z.is_one_structural());
}
#[test]
fn ex_one_is_not_zero() {
let ctx = Context::new();
let o = ctx.one();
assert!(!o.is_zero_structural());
}
#[test]
fn expr_type_classification() {
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.expr_type(), ExprType::Symbol);
assert_eq!(ctx.int(5).expr_type(), ExprType::Number);
assert_eq!((&x + 1).expr_type(), ExprType::Add);
assert_eq!((&x * 2).expr_type(), ExprType::Mul);
assert_eq!(x.powi(2).expr_type(), ExprType::Pow);
assert_eq!(x.sin().expr_type(), ExprType::Function);
assert_eq!(ctx.pi().expr_type(), ExprType::Constant);
}
#[test]
fn expr_type_all_trig_are_function() {
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!(x.sin().expr_type(), ExprType::Function);
assert_eq!(x.cos().expr_type(), ExprType::Function);
assert_eq!(x.tan().expr_type(), ExprType::Function);
assert_eq!(x.sinh().expr_type(), ExprType::Function);
assert_eq!(x.cosh().expr_type(), ExprType::Function);
assert_eq!(x.tanh().expr_type(), ExprType::Function);
assert_eq!(x.exp().expr_type(), ExprType::Function);
assert_eq!(x.ln().expr_type(), ExprType::Function);
}
#[test]
fn replace_variable() {
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = &x.powi(2) + &x + 1;
let replaced = expr.replace(|e| if e == x { Some(y.clone()) } else { None });
let s = format!("{replaced}");
assert!(
s.contains("y") && !s.contains("x"),
"should replace x with y, got: {s}"
);
}
#[test]
fn replace_variable_exact() {
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = &x.powi(2) + &x + 1;
let replaced = expr.replace(|e| if e == x { Some(y.clone()) } else { None });
assert_eq!(format!("{replaced}"), "y^2 + y + 1");
}
#[test]
fn replace_identity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.sin();
let replaced = expr.replace(|_| None);
assert_eq!(format!("{replaced}"), format!("{expr}"));
}
#[test]
fn replace_identity_complex_expr() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x.powi(2) + &x.sin() + 1;
let original = format!("{expr}");
let replaced = expr.replace(|_| None);
assert_eq!(format!("{replaced}"), original);
}
#[test]
fn integrate_then_simplify_sin_over_cos() {
let ctx = Context::new();
let x = ctx.symbol("x");
let ratio = &x.sin() / &x.cos();
let simplified = ratio.simplify();
assert_eq!(format!("{simplified}"), "tan(x)");
let integral = simplified.integrate(&x);
let s = format!("{integral}");
assert!(
s.contains("ln") && s.contains("cos"),
"∫ tan(x) dx should be -ln|cos(x)|, got: {s}"
);
}
#[test]
fn eval_then_check_expr_type() {
let ctx = Context::new();
let angle = &ctx.pi() / 6;
let raw = angle.sin();
assert_eq!(raw.expr_type(), ExprType::Function);
let evaled = raw.eval();
assert_eq!(evaled.expr_type(), ExprType::Number);
}
#[test]
fn replace_then_differentiate() {
let ctx = Context::new();
let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
let expr = x.powi(3);
let replaced = expr.replace(|e| if e == x { Some(y.clone()) } else { None });
let deriv = replaced.diff(&y);
assert_eq!(format!("{deriv}"), "3*y^2");
}
#[test]
fn logcombine_preserves_simplification() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let combined = (&x.ln() + &y.ln()).log_combine();
assert_eq!(format!("{combined}"), "ln(x*y)");
let deriv = combined.diff(&x);
let simplified = deriv.simplify();
let s = format!("{simplified}");
assert!(
s.contains("1/x") || s.contains("x*y") || s == "1/x",
"d/dx ln(x*y) should be equivalent to 1/x, got: {s}"
);
}