use symplex::prelude::*;
#[test]
fn factorial_of_5() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let five = arena.int(5);
let expr = arena.factorial(five);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "120");
});
}
#[test]
fn factorial_of_zero() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let zero = arena.int(0);
let expr = arena.factorial(zero);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "1");
});
}
#[test]
fn factorial_of_one() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let one = arena.int(1);
let expr = arena.factorial(one);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "1");
});
}
#[test]
fn factorial_of_2() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let two = arena.int(2);
let expr = arena.factorial(two);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "2");
});
}
#[test]
fn factorial_of_3() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let three = arena.int(3);
let expr = arena.factorial(three);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "6");
});
}
#[test]
fn factorial_of_6() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let six = arena.int(6);
let expr = arena.factorial(six);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "720");
});
}
#[test]
fn factorial_of_10() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let ten = arena.int(10);
let expr = arena.factorial(ten);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "3628800");
});
}
#[test]
fn factorial_of_20() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let twenty = arena.int(20);
let expr = arena.factorial(twenty);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "2432902008176640000");
});
}
#[test]
fn factorial_of_symbolic_stays_unevaluated() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let x = arena.symbol("x");
let expr = arena.factorial(x);
let result = arena.eval_expr(expr);
let s = arena.display(result).to_string();
assert!(
s.contains("!"),
"symbolic factorial should display with !: {s}"
);
assert!(s.contains("x"), "should still contain x: {s}");
});
}
#[test]
fn factorial_display_atom() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let five = arena.int(5);
let expr = arena.factorial(five);
let s = arena.display(expr).to_string();
assert_eq!(s, "5!");
});
}
#[test]
fn factorial_display_symbol() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let n = arena.symbol("n");
let expr = arena.factorial(n);
let s = arena.display(expr).to_string();
assert_eq!(s, "n!");
});
}
#[test]
fn factorial_display_compound_gets_parens() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let x = arena.symbol("x");
let one = arena.int(1);
let sum = arena.add(&[x, one]);
let expr = arena.factorial(sum);
let s = arena.display(expr).to_string();
assert!(
s.contains("(") && s.contains(")") && s.contains("!"),
"compound factorial should have parens: {s}"
);
});
}
#[test]
fn factorial_21_evaluates() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let twenty_one = arena.int(21);
let expr = arena.factorial(twenty_one);
let result = arena.eval_expr(expr);
let s = arena.display(result).to_string();
assert_eq!(s, "51090942171709440000", "21! should evaluate: {s}");
});
}
#[test]
fn factorial_negative_stays_unevaluated() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let neg = arena.int(-3);
let expr = arena.factorial(neg);
let result = arena.eval_expr(expr);
let s = arena.display(result).to_string();
assert!(
s.contains("!"),
"factorial(-3) should stay unevaluated: {s}"
);
});
}
#[test]
fn factorial_rational_stays_unevaluated() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let half = arena.rational(1, 2);
let expr = arena.factorial(half);
let result = arena.eval_expr(expr);
let s = arena.display(result).to_string();
assert!(
s.contains("!"),
"factorial(1/2) should stay unevaluated: {s}"
);
});
}
#[test]
fn binomial_5_choose_2() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let five = arena.int(5);
let two = arena.int(2);
let expr = arena.binomial(five, two);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "10");
});
}
#[test]
fn binomial_10_choose_3() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let ten = arena.int(10);
let three = arena.int(3);
let expr = arena.binomial(ten, three);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "120");
});
}
#[test]
fn binomial_n_choose_0() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let seven = arena.int(7);
let zero = arena.int(0);
let expr = arena.binomial(seven, zero);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "1");
});
}
#[test]
fn binomial_n_choose_n() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let five = arena.int(5);
let expr = arena.binomial(five, five);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "1");
});
}
#[test]
fn binomial_n_choose_1() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let eight = arena.int(8);
let one = arena.int(1);
let expr = arena.binomial(eight, one);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "8");
});
}
#[test]
fn binomial_symmetry() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let seven = arena.int(7);
let two = arena.int(2);
let five = arena.int(5);
let c72 = arena.binomial(seven, two);
let c75 = arena.binomial(seven, five);
let r72 = arena.eval_expr(c72);
let r75 = arena.eval_expr(c75);
assert_eq!(
arena.display(r72).to_string(),
arena.display(r75).to_string(),
"C(7,2) should equal C(7,5)"
);
assert_eq!(arena.display(r72).to_string(), "21");
});
}
#[test]
fn binomial_0_choose_0() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let zero = arena.int(0);
let expr = arena.binomial(zero, zero);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "1");
});
}
#[test]
fn binomial_20_choose_10() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let twenty = arena.int(20);
let ten = arena.int(10);
let expr = arena.binomial(twenty, ten);
let result = arena.eval_expr(expr);
assert_eq!(arena.display(result).to_string(), "184756");
});
}
#[test]
fn binomial_display_symbolic() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let n = arena.symbol("n");
let k = arena.symbol("k");
let expr = arena.binomial(n, k);
let s = arena.display(expr).to_string();
assert!(
s.contains("C(") && s.contains("n") && s.contains("k"),
"binomial display should be C(n, k): got {s}"
);
});
}
#[test]
fn binomial_display_numeric() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let five = arena.int(5);
let two = arena.int(2);
let expr = arena.binomial(five, two);
let s = arena.display(expr).to_string();
assert!(
s.contains("C(") && s.contains("5") && s.contains("2"),
"unevaluated binomial display: got {s}"
);
});
}
#[test]
fn binomial_k_greater_than_n_stays_unevaluated() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let three = arena.int(3);
let five = arena.int(5);
let expr = arena.binomial(three, five);
let result = arena.eval_expr(expr);
let s = arena.display(result).to_string();
assert!(s.contains("C("), "C(3,5) should stay unevaluated: {s}");
});
}
#[test]
fn binomial_negative_stays_unevaluated() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let neg = arena.int(-1);
let two = arena.int(2);
let expr = arena.binomial(neg, two);
let result = arena.eval_expr(expr);
let s = arena.display(result).to_string();
assert!(s.contains("C("), "C(-1,2) should stay unevaluated: {s}");
});
}
#[test]
fn binomial_symbolic_stays_unevaluated() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let n = arena.symbol("n");
let two = arena.int(2);
let expr = arena.binomial(n, two);
let result = arena.eval_expr(expr);
let s = arena.display(result).to_string();
assert!(
s.contains("C(") && s.contains("n"),
"C(n,2) should stay unevaluated: {s}"
);
});
}
#[test]
fn factorial_in_addition() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let five = arena.int(5);
let three = arena.int(3);
let fact = arena.factorial(five);
let sum = arena.add(&[fact, three]);
let result = arena.eval_expr(sum);
assert_eq!(arena.display(result).to_string(), "123");
});
}
#[test]
fn factorial_in_multiplication() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let five = arena.int(5);
let two = arena.int(2);
let fact = arena.factorial(five);
let product = arena.mul(&[fact, two]);
let result = arena.eval_expr(product);
assert_eq!(arena.display(result).to_string(), "240");
});
}
#[test]
fn binomial_in_expression() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let five = arena.int(5);
let two = arena.int(2);
let one = arena.int(1);
let binom = arena.binomial(five, two);
let sum = arena.add(&[binom, one]);
let result = arena.eval_expr(sum);
assert_eq!(arena.display(result).to_string(), "11");
});
}
#[test]
fn factorial_identity_n_choose_k_via_factorials() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let five = arena.int(5);
let two = arena.int(2);
let binom = arena.binomial(five, two);
let binom_val = arena.eval_expr(binom);
let three = arena.int(3);
let fact5 = arena.factorial(five);
let fact2 = arena.factorial(two);
let fact3 = arena.factorial(three);
let denom = arena.mul(&[fact2, fact3]);
let ratio = arena.div(fact5, denom);
let ratio_val = arena.eval_expr(ratio);
assert_eq!(
arena.display(binom_val).to_string(),
arena.display(ratio_val).to_string(),
"C(5,2) should equal 5!/(2!*3!)"
);
});
}
#[test]
fn factorial_eval_is_idempotent() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let five = arena.int(5);
let expr = arena.factorial(five);
let once = arena.eval_expr(expr);
let twice = arena.eval_expr(once);
assert_eq!(
arena.display(once).to_string(),
arena.display(twice).to_string(),
"eval of factorial should be idempotent"
);
});
}
#[test]
fn binomial_eval_is_idempotent() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let ten = arena.int(10);
let three = arena.int(3);
let expr = arena.binomial(ten, three);
let once = arena.eval_expr(expr);
let twice = arena.eval_expr(once);
assert_eq!(
arena.display(once).to_string(),
arena.display(twice).to_string(),
"eval of binomial should be idempotent"
);
});
}
#[test]
fn pascal_triangle_row_4() {
let expected = [1, 4, 6, 4, 1];
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let four = arena.int(4);
for (k, &exp_val) in expected.iter().enumerate() {
let k_id = arena.int(k as i64);
let binom = arena.binomial(four, k_id);
let result = arena.eval_expr(binom);
assert_eq!(
arena.display(result).to_string(),
exp_val.to_string(),
"C(4, {k}) should be {exp_val}"
);
}
});
}
#[test]
fn factorial_table_0_through_10() {
let expected: [(i64, &str); 11] = [
(0, "1"),
(1, "1"),
(2, "2"),
(3, "6"),
(4, "24"),
(5, "120"),
(6, "720"),
(7, "5040"),
(8, "40320"),
(9, "362880"),
(10, "3628800"),
];
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
for (n, exp_str) in &expected {
let n_id = arena.int(*n);
let expr = arena.factorial(n_id);
let result = arena.eval_expr(expr);
assert_eq!(
arena.display(result).to_string(),
*exp_str,
"{n}! should be {exp_str}"
);
}
});
}
#[test]
fn factorial_node_is_not_atom() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let x = arena.symbol("x");
let expr = arena.factorial(x);
let node = arena.node(expr);
assert!(!node.is_atom(), "Factorial should not be an atom");
assert_eq!(
node.children().len(),
1,
"Factorial should have exactly 1 child"
);
});
}
#[test]
fn binomial_node_is_not_atom() {
let ctx = Context::new();
ctx.with_arena_mut(|arena| {
let n = arena.symbol("n");
let k = arena.symbol("k");
let expr = arena.binomial(n, k);
let node = arena.node(expr);
assert!(!node.is_atom(), "Binomial should not be an atom");
assert_eq!(
node.children().len(),
2,
"Binomial should have exactly 2 children"
);
});
}
#[test]
fn solve_linear_equation_via_expr() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x + 1 - 5;
let roots = expr.solve_or_empty(&x);
assert_eq!(roots.len(), 1);
assert_eq!(format!("{}", roots[0]), "4");
}
#[test]
fn solve_linear_2x_eq_10() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x * 2 - 10;
let roots = expr.solve_or_empty(&x);
assert_eq!(roots.len(), 1);
assert_eq!(format!("{}", roots[0]), "5");
}
#[test]
fn solve_quadratic_x2_eq_9() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.powi(2) - 9;
let roots = expr.solve_or_empty(&x);
assert_eq!(roots.len(), 2, "x²-9=0 should have 2 roots");
let mut strs: Vec<String> = roots.iter().map(|r| format!("{r}")).collect();
strs.sort();
assert_eq!(
strs,
vec!["-3", "3"],
"roots of x²-9 should be exactly -3 and 3"
);
}
#[test]
fn solve_quadratic_x2_eq_neg1_complex() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.powi(2) + 1;
let roots = expr.solve_or_empty(&x);
assert_eq!(roots.len(), 2, "x²+1=0 should have 2 complex roots");
let mut strs: Vec<String> = roots.iter().map(|r| format!("{r}")).collect();
strs.sort();
assert_eq!(
strs,
vec!["-I", "I"],
"roots of x²+1 should be exactly I and -I"
);
}
#[test]
fn substitution_verifies_solution() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = &x + 1 - 5;
let roots = expr.solve_or_empty(&x);
assert_eq!(roots.len(), 1);
let at_root = expr.subs(&x, &roots[0]);
let simplified = at_root.simplify();
assert_eq!(
format!("{simplified}"),
"0",
"substituting root should give 0"
);
}
#[test]
fn pythagorean_identity_simplifies() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.sin().powi(2) + x.cos().powi(2);
let simplified = expr.simplify();
assert_eq!(format!("{simplified}"), "1");
}
#[test]
fn expand_square_binomial() {
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = (&x + 1).powi(2);
let expanded = expr.expand();
let s = format!("{expanded}");
assert_eq!(s, "x^2 + 2*x + 1", "expand (x+1)² should be x^2 + 2*x + 1");
}
#[test]
fn to_expr_pattern_lhs_minus_rhs() {
let ctx = Context::new();
let x = ctx.symbol("x");
let three = ctx.int(3);
let to_expr = &x - &three;
let s = format!("{to_expr}");
assert_eq!(s, "x - 3", "x - 3 canonical form");
}
#[test]
fn multi_variable_equation_workflow() {
let ctx = Context::new();
let x = ctx.symbol("x");
let y = ctx.symbol("y");
let three = ctx.int(3);
let expr = &x + &y - 10;
let after_subs = expr.subs(&y, &three);
let roots = after_subs.solve_or_empty(&x);
assert!(!roots.is_empty(), "x + 3 - 10 = 0 should solve");
assert_eq!(format!("{}", roots[0]), "7");
}