use mathhook_core::prelude::*;
fn verify_solution(equation: &Expression, var: &Symbol, solution: &Expression) -> bool {
match equation {
Expression::Relation(relation_data) => {
let left_substituted = substitute_and_simplify(&relation_data.left, var, solution);
let right_substituted = substitute_and_simplify(&relation_data.right, var, solution);
left_substituted == right_substituted
}
_ => panic!("verify_solution requires an equation (Relation variant)"),
}
}
fn substitute_and_simplify(expr: &Expression, var: &Symbol, value: &Expression) -> Expression {
match expr {
Expression::Symbol(s) if s == var => value.clone(),
Expression::Add(terms) => Expression::add(
terms
.iter()
.map(|t| substitute_and_simplify(t, var, value))
.collect(),
)
.simplify(),
Expression::Mul(factors) => Expression::mul(
factors
.iter()
.map(|f| substitute_and_simplify(f, var, value))
.collect(),
)
.simplify(),
Expression::Pow(base, exp) => Expression::pow(
substitute_and_simplify(base, var, value),
substitute_and_simplify(exp, var, value),
)
.simplify(),
_ => expr.clone(),
}
}
#[test]
fn test_solve_simple_linear_x_equals_constant() {
let x = symbol!(x);
let equation = Expression::equation(Expression::symbol(x.clone()), Expression::integer(5));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::Single(solution) => {
assert_eq!(
solution,
Expression::integer(5),
"x = 5 should solve to x = 5"
);
assert!(
verify_solution(&equation, &x, &solution),
"Solution must satisfy equation"
);
}
_ => panic!("Simple equation x = 5 should have single solution"),
}
}
#[test]
fn test_solve_linear_with_coefficient() {
let x = symbol!(x);
let left = Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]);
let equation = Expression::equation(left, Expression::integer(6));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::Single(solution) => {
assert_eq!(
solution,
Expression::integer(3),
"2x = 6 should solve to x = 3"
);
assert!(
verify_solution(&equation, &x, &solution),
"Solution must satisfy equation"
);
}
_ => panic!("Linear equation 2x = 6 should have single solution"),
}
}
#[test]
fn test_solve_linear_with_addition() {
let x = symbol!(x);
let left = Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(2)]);
let equation = Expression::equation(left, Expression::integer(5));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::Single(solution) => {
assert_eq!(
solution,
Expression::integer(3),
"x + 2 = 5 should solve to x = 3"
);
assert!(
verify_solution(&equation, &x, &solution),
"Solution must satisfy equation"
);
}
_ => panic!("Linear equation x + 2 = 5 should have single solution"),
}
}
#[test]
fn test_solve_general_linear() {
let x = symbol!(x);
let left = Expression::add(vec![
Expression::mul(vec![Expression::integer(3), Expression::symbol(x.clone())]),
Expression::integer(2),
]);
let equation = Expression::equation(left, Expression::integer(11));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::Single(solution) => {
assert_eq!(
solution,
Expression::integer(3),
"3x + 2 = 11 should solve to x = 3"
);
assert!(
verify_solution(&equation, &x, &solution),
"Solution must satisfy equation"
);
}
_ => panic!("Linear equation 3x + 2 = 11 should have single solution"),
}
}
#[test]
fn test_solve_linear_negative_coefficient() {
let x = symbol!(x);
let left = Expression::add(vec![
Expression::mul(vec![Expression::integer(-1), Expression::symbol(x.clone())]),
Expression::integer(5),
]);
let equation = Expression::equation(left, Expression::integer(2));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::Single(solution) => {
assert_eq!(
solution,
Expression::integer(3),
"-x + 5 = 2 should solve to x = 3"
);
assert!(
verify_solution(&equation, &x, &solution),
"Solution must satisfy equation"
);
}
_ => panic!("Linear equation -x + 5 = 2 should have single solution"),
}
}
#[test]
fn test_solve_linear_rational_solution() {
let x = symbol!(x);
let left = Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]);
let equation = Expression::equation(left, Expression::integer(1));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::Single(solution) => {
let expected = Expression::rational(1, 2);
assert_eq!(solution, expected, "2x = 1 should solve to x = 1/2");
assert!(
verify_solution(&equation, &x, &solution),
"Solution must satisfy equation"
);
}
_ => panic!("Linear equation 2x = 1 should have single solution"),
}
}
#[test]
fn test_solve_simple_quadratic() {
let x = symbol!(x);
let left = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
let equation = Expression::equation(left, Expression::integer(4));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::Multiple(solutions) => {
assert_eq!(solutions.len(), 2, "x² = 4 should have two solutions");
for solution in &solutions {
assert!(
verify_solution(&equation, &x, solution),
"Solution {:?} must satisfy equation x² = 4",
solution
);
}
}
_ => panic!("Quadratic equation x² = 4 should have multiple solutions"),
}
}
#[test]
fn test_solve_factored_quadratic() {
let x = symbol!(x);
let left = Expression::add(vec![
Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
Expression::mul(vec![Expression::integer(-5), Expression::symbol(x.clone())]),
Expression::integer(6),
]);
let equation = Expression::equation(left, Expression::integer(0));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::Multiple(solutions) => {
assert_eq!(
solutions.len(),
2,
"x² - 5x + 6 = 0 should have two solutions"
);
for solution in &solutions {
assert!(
verify_solution(&equation, &x, solution),
"Solution {:?} must satisfy equation",
solution
);
}
}
SolverResult::Single(solution) => {
assert!(
verify_solution(&equation, &x, &solution),
"Solution must satisfy equation"
);
}
_ => {
}
}
}
#[test]
fn test_solve_quadratic_no_real_solutions() {
let x = symbol!(x);
let left = Expression::add(vec![
Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
Expression::integer(1),
]);
let equation = Expression::equation(left, Expression::integer(0));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::NoSolution => {
}
SolverResult::Multiple(solutions) if solutions.is_empty() => {
}
_ => {
}
}
}
#[test]
fn test_solve_contradictory_equation() {
let x = symbol!(x);
let left = Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(1)]);
let right = Expression::symbol(x.clone());
let equation = Expression::equation(left, right);
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::NoSolution => {
}
_ => {
}
}
}
#[test]
fn test_solve_identity_equation() {
let x = symbol!(x);
let left = Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]);
let right = Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]);
let equation = Expression::equation(left, right);
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::InfiniteSolutions => {
}
_ => {
}
}
}
#[test]
fn test_solve_zero_coefficient() {
let x = symbol!(x);
let left = Expression::add(vec![
Expression::mul(vec![Expression::integer(0), Expression::symbol(x.clone())]),
Expression::integer(1),
]);
let equation = Expression::equation(left, Expression::integer(0));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::NoSolution => {
}
_ => {
}
}
}
#[test]
fn test_solve_zero_both_sides() {
let x = symbol!(x);
let left = Expression::add(vec![
Expression::mul(vec![Expression::integer(0), Expression::symbol(x.clone())]),
Expression::integer(0),
]);
let equation = Expression::equation(left, Expression::integer(0));
let solver = MathSolver::new();
let result = solver.solve(&equation, &x);
match result {
SolverResult::InfiniteSolutions => {
}
_ => {
}
}
}
#[test]
fn test_verify_solution_correctness_linear() {
let x = symbol!(x);
let test_cases = vec![
(1, 2, 5, 3, true), (2, 3, 7, 2, true), (3, -1, 8, 3, true), (1, 2, 5, 5, false), (2, 0, 6, 4, false), ];
for (a, b, c, solution_val, should_satisfy) in test_cases {
let left = Expression::add(vec![
Expression::mul(vec![Expression::integer(a), Expression::symbol(x.clone())]),
Expression::integer(b),
]);
let equation = Expression::equation(left, Expression::integer(c));
let solution = Expression::integer(solution_val);
let satisfies = verify_solution(&equation, &x, &solution);
if should_satisfy {
assert!(
satisfies,
"x = {} should satisfy {}x + {} = {}",
solution_val, a, b, c
);
} else {
assert!(
!satisfies,
"x = {} should NOT satisfy {}x + {} = {}",
solution_val, a, b, c
);
}
}
}
#[test]
fn test_verify_solution_correctness_quadratic() {
let x = symbol!(x);
let test_cases = vec![
(1, -5, 6, 2, true), (1, -5, 6, 3, true), (1, -5, 6, 1, false), (1, 0, -4, 2, true), (1, 0, -4, -2, true), ];
for (a, b, c, solution_val, should_satisfy) in test_cases {
let left = Expression::add(vec![
Expression::mul(vec![
Expression::integer(a),
Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
]),
Expression::mul(vec![Expression::integer(b), Expression::symbol(x.clone())]),
Expression::integer(c),
]);
let equation = Expression::equation(left, Expression::integer(0));
let solution = Expression::integer(solution_val);
let satisfies = verify_solution(&equation, &x, &solution);
if should_satisfy {
assert!(
satisfies,
"x = {} should satisfy {}x² + {}x + {} = 0",
solution_val, a, b, c
);
} else {
assert!(
!satisfies,
"x = {} should NOT satisfy {}x² + {}x + {} = 0",
solution_val, a, b, c
);
}
}
}
#[test]
fn test_complete_solving_workflow() {
let x = symbol!(x);
let equations_and_expected_solutions = vec![
(Expression::equation(expr!(x), expr!(5)), vec![expr!(5)]),
(Expression::equation(expr!(2 * x), expr!(6)), vec![expr!(3)]),
(Expression::equation(expr!(x + 1), expr!(4)), vec![expr!(3)]),
];
let solver = MathSolver::new();
for (equation, expected_solutions) in equations_and_expected_solutions {
let result = solver.solve(&equation, &x);
let solutions = match result {
SolverResult::Single(sol) => vec![sol],
SolverResult::Multiple(sols) => sols,
SolverResult::NoSolution => vec![],
SolverResult::InfiniteSolutions => continue, };
if !solutions.is_empty() {
assert_eq!(
solutions.len(),
expected_solutions.len(),
"Expected {} solution(s), got {}",
expected_solutions.len(),
solutions.len()
);
for solution in &solutions {
assert!(
verify_solution(&equation, &x, solution),
"Solution {:?} must satisfy equation",
solution
);
}
}
}
}