use symplex::prelude::*;
#[test]
fn ln_exp_simplifies_when_real() {
let ctx = Context::new();
let x = ctx.symbol("x").assume(Assumption::Real);
let result = x.exp().ln().simplify();
assert_eq!(format!("{result}"), "x");
}
#[test]
fn ln_exp_simplifies_when_real_via_context() {
let ctx = Context::new();
let x = ctx.symbol_with("x", &[Assumption::Real]);
let result = x.exp().ln().simplify();
assert_eq!(format!("{result}"), "x");
}
#[test]
fn ln_exp_simplifies_for_integer() {
let ctx = Context::new();
let three = ctx.int(3);
let result = three.exp().ln().simplify();
assert_eq!(format!("{result}"), "3");
}
#[test]
fn ln_exp_simplifies_for_pi() {
let ctx = Context::new();
let pi = ctx.pi();
let result = pi.exp().ln().simplify();
assert_eq!(format!("{result}"), "pi");
}
#[test]
fn ln_exp_simplifies_for_zero() {
let ctx = Context::new();
let zero = ctx.int(0);
let result = zero.exp().ln().simplify();
assert_eq!(format!("{result}"), "0");
}
#[test]
fn ln_exp_simplifies_when_positive() {
let ctx = Context::new();
let x = ctx.symbol("t").assume(Assumption::Positive);
let result = x.exp().ln().simplify();
assert_eq!(format!("{result}"), "t");
}
#[test]
fn ln_exp_simplifies_when_negative() {
let ctx = Context::new();
let x = ctx.symbol("u").assume(Assumption::Negative);
let result = x.exp().ln().simplify();
assert_eq!(format!("{result}"), "u");
}
#[test]
fn ln_exp_simplifies_when_integer() {
let ctx = Context::new();
let n = ctx.symbol("n").assume(Assumption::Integer);
let result = n.exp().ln().simplify();
assert_eq!(format!("{result}"), "n");
}
#[test]
fn ln_exp_simplifies_unconditionally() {
let ctx = Context::new();
let x = ctx.symbol("z_unknown");
let expr = x.exp().ln();
let result = expr.simplify();
let s = format!("{result}");
assert!(
s == "z_unknown" || s.contains("ln") || s.contains("exp"),
"unexpected simplification result for ln(exp(z_unknown)): got `{s}`"
);
}
#[test]
fn exp_ln_always_simplifies() {
let ctx = Context::new();
let x = ctx.symbol("w"); let result = x.ln().exp().simplify();
assert_eq!(format!("{result}"), "w");
}
#[test]
fn exp_ln_simplifies_with_real_assumption() {
let ctx = Context::new();
let x = ctx.symbol("v").assume(Assumption::Real);
let result = x.ln().exp().simplify();
assert_eq!(format!("{result}"), "v");
}
#[test]
fn exp_ln_simplifies_for_integer() {
let ctx = Context::new();
let five = ctx.int(5);
let result = five.ln().exp().simplify();
assert_eq!(format!("{result}"), "5");
}
#[test]
fn parse_basic() {
let ctx = Context::new();
let expr = ctx.parse("x^2 + 1").unwrap();
assert_eq!(format!("{expr}"), "x^2 + 1");
}
#[test]
fn parse_with_functions() {
let ctx = Context::new();
let expr = ctx.parse("sin(x)").unwrap();
assert_eq!(format!("{expr}"), "sin(x)");
}
#[test]
fn parse_error_on_garbage() {
let ctx = Context::new();
let result = ctx.parse("!!!garbage");
assert!(result.is_err(), "parsing garbage should produce an error");
}
#[test]
fn parse_roundtrip() {
let ctx = Context::new();
let original = ctx.symbol("x").powi(2) + ctx.int(1);
let text = format!("{original}");
let parsed = ctx.parse(&text).unwrap();
assert_eq!(
format!("{parsed}"),
format!("{original}"),
"Display → parse round-trip should preserve representation"
);
}
#[test]
fn parse_constants() {
let ctx = Context::new();
let expr = ctx.parse("pi").unwrap();
assert_eq!(format!("{expr}"), "pi");
}
#[test]
fn parse_nested_functions() {
let ctx = Context::new();
let expr = ctx.parse("sin(cos(x))").unwrap();
assert_eq!(format!("{expr}"), "sin(cos(x))");
}
#[test]
fn parse_negative_integer() {
let ctx = Context::new();
let expr = ctx.parse("-7").unwrap();
assert_eq!(format!("{expr}"), "-7");
}
#[test]
fn parse_addition() {
let ctx = Context::new();
let expr = ctx.parse("a + b").unwrap();
let s = format!("{expr}");
assert!(s.contains('a') && s.contains('b'), "got: {s}");
}
#[test]
fn parse_multiplication() {
let ctx = Context::new();
let expr = ctx.parse("2*x").unwrap();
assert_eq!(format!("{expr}"), "2*x");
}
#[test]
#[should_panic(expected = "cannot combine expressions from different contexts")]
fn cross_context_add_panics_in_debug() {
let ctx1 = Context::new();
let ctx2 = Context::new();
let x = ctx1.symbol("x");
let y = ctx2.symbol("y");
let _ = &x + &y;
}
#[test]
#[should_panic(expected = "cannot combine expressions from different contexts")]
fn cross_context_sub_panics_in_debug() {
let ctx1 = Context::new();
let ctx2 = Context::new();
let x = ctx1.symbol("x");
let y = ctx2.symbol("y");
let _ = &x - &y;
}
#[test]
#[should_panic(expected = "cannot combine expressions from different contexts")]
fn cross_context_mul_panics_in_debug() {
let ctx1 = Context::new();
let ctx2 = Context::new();
let x = ctx1.symbol("x");
let y = ctx2.symbol("y");
let _ = &x * &y;
}
#[test]
#[should_panic(expected = "cannot combine expressions from different contexts")]
fn cross_context_div_panics_in_debug() {
let ctx1 = Context::new();
let ctx2 = Context::new();
let x = ctx1.symbol("x");
let y = ctx2.symbol("y");
let _ = &x / &y;
}
#[test]
fn same_context_operations_succeed() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let _ = &x + &y;
let _ = &x - &y;
let _ = &x * &y;
let _ = &x / &y;
}
#[test]
fn global_context_operations_succeed() {
let ctx = Context::new();
let a = ctx.symbol("a");
let b = ctx.symbol("b");
let _ = &a + &b;
let _ = &a - &b;
let _ = &a * &b;
let _ = &a / &b;
}
#[test]
fn query_methods_return_values_for_unconstrained_symbol() {
let ctx = Context::new();
let x = ctx.symbol("q");
let _ = x.is_positive();
let _ = x.is_negative();
let _ = x.is_zero();
let _ = x.is_nonzero();
let _ = x.is_real();
let _ = x.is_integer();
let _ = x.is_finite();
let _ = x.is_nonnegative();
let _ = x.is_nonpositive();
let _ = x.is_imaginary();
let _ = x.is_complex();
let _ = x.is_rational();
}
#[test]
fn query_reflects_positive_assumption() {
let ctx = Context::new();
let x = ctx.symbol("xp").assume(Assumption::Positive);
assert_eq!(x.is_positive(), Some(true), "should be positive");
assert_eq!(x.is_real(), Some(true), "positive ⇒ real");
assert_eq!(x.is_negative(), Some(false), "positive ⇒ ¬negative");
assert_eq!(x.is_nonnegative(), Some(true), "positive ⇒ nonnegative");
}
#[test]
fn query_reflects_real_assumption() {
let ctx = Context::new();
let x = ctx.symbol("xr").assume(Assumption::Real);
assert_eq!(x.is_real(), Some(true));
assert_eq!(x.is_complex(), Some(true), "real ⇒ complex");
}
#[test]
fn query_reflects_integer_assumption() {
let ctx = Context::new();
let n = ctx.symbol("n_int").assume(Assumption::Integer);
assert_eq!(n.is_integer(), Some(true));
assert_eq!(n.is_rational(), Some(true), "integer ⇒ rational");
assert_eq!(n.is_real(), Some(true), "integer ⇒ real");
assert_eq!(n.is_complex(), Some(true), "integer ⇒ complex");
assert_eq!(n.is_finite(), Some(true), "integer ⇒ finite");
}
#[test]
fn query_for_integer_literal() {
let ctx = Context::new();
let five = ctx.int(5);
assert_eq!(five.is_positive(), Some(true));
assert_eq!(five.is_integer(), Some(true));
assert_eq!(five.is_real(), Some(true));
assert_eq!(five.is_zero(), Some(false));
assert_eq!(five.is_nonzero(), Some(true));
}
#[test]
fn query_for_zero() {
let ctx = Context::new();
let z = ctx.int(0);
assert_eq!(z.is_zero(), Some(true));
}
#[test]
fn query_generic_agrees_with_named() {
let ctx = Context::new();
let x = ctx.symbol("xg").assume(Assumption::Positive);
assert_eq!(x.query(Props::POSITIVE), x.is_positive());
assert_eq!(x.query(Props::REAL), x.is_real());
assert_eq!(x.query(Props::NEGATIVE), x.is_negative());
}
#[test]
fn partial_eq_structural_identity() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = &x + 1;
let b = 1 + &x;
assert_eq!(a, b, "canonically equal expressions should be ==");
}
#[test]
fn partial_eq_not_mathematical() {
let ctx = Context::new();
let x = ctx.symbol("x");
let factored = (&x + 1).powi(2);
let expanded = &x.powi(2) + &x * 2 + 1;
assert_ne!(
expanded, factored,
"structural != should hold for non-canonical forms"
);
}
#[test]
fn equals_detects_mathematical_equality() {
let ctx = Context::new();
let x = ctx.symbol("x");
let factored = (&x + 1).powi(2);
let expanded = &x.powi(2) + &x * 2 + 1;
assert_eq!(
factored.equals(&expanded),
Some(true),
"equals() should detect mathematical equality"
);
}
#[test]
fn partial_eq_reflexive() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.powi(2) + 1;
assert_eq!(expr, expr, "expression should equal itself");
}
#[test]
fn partial_eq_same_construction_twice() {
let ctx = Context::new();
let x = ctx.symbol("x");
let a = &x + 1;
let b = &x + 1;
assert_eq!(a, b, "identical constructions in same context should be ==");
}
#[test]
fn partial_eq_different_contexts_are_unequal() {
let ctx1 = Context::new();
let ctx2 = Context::new();
let a = ctx1.symbol("x");
let b = ctx2.symbol("x");
assert_ne!(a, b, "same name in different contexts should be !=");
}
#[test]
fn equals_self_is_some_true() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.powi(3) + 1;
assert_eq!(expr.equals(&expr), Some(true));
}
#[test]
fn partial_eq_commutative_add() {
let ctx = Context::new();
let a = ctx.symbol("a");
let b = ctx.symbol("b");
let lhs = &a + &b;
let rhs = &b + &a;
assert_eq!(lhs, rhs, "a + b should canonically equal b + a");
}
#[test]
fn partial_eq_commutative_mul() {
let ctx = Context::new();
let a = ctx.symbol("a");
let b = ctx.symbol("b");
let lhs = &a * &b;
let rhs = &b * &a;
assert_eq!(lhs, rhs, "a * b should canonically equal b * a");
}