from mcp_calculator.ops.algebra import evaluate, solve, transform from mcp_calculator.ops.context import MathError from tests.support import MathTestCase class EvaluateTest(MathTestCase): def test_exact_and_numeric(self): r = evaluate("sqrt(8) + 1/3", precision=20) self.assertEqual(r["exact"], "1/3 + 2*sqrt(2)") self.assertTrue(r["numeric"].startswith("3.161760458")) self.assertEqual(len(r["numeric"].replace(".", "")), 20) def test_decimal_arithmetic_is_exact(self): self.assertEqual(evaluate("0.1 + 0.2")["exact"], "3/10") def test_big_integer(self): r = evaluate("2^200") self.assertEqual(r["exact"], str(2**200)) self.assertEqual(r["digits"], 61) def test_variables(self): self.assertEqual(evaluate("a*x^2 + b", variables={"a": 2, "x": "3", "b": 0.5})["exact"], "37/2") def test_free_symbols_are_reported(self): r = evaluate("x + pi") self.assertEqual(r["free_symbols"], ["x"]) self.assertIn("note", r) def test_compound_interest(self): self.assertEqual(evaluate("1000 * (1 + 0.05)^10", precision=12)["numeric"], "1628.89462678") def test_precision_limit(self): with self.assertRaises(MathError): evaluate("pi", precision=5000) def test_complex(self): self.assertEqual(evaluate("sqrt(-4)")["exact"], "2*I") class TransformTest(MathTestCase): def test_operations(self): cases = [ ("sin(x)^2 + cos(x)^2", "simplify", "1"), ("(x + 1)^2", "expand", "x**2 + 2*x + 1"), ("x^2 - 5x + 6", "factor", "(x - 3)*(x - 2)"), ("(x^2 - 1)/(x - 1)", "cancel", "x + 1"), ("1/(x^2 - 1)", "apart", "-1/(2*(x + 1)) + 1/(2*(x - 1))"), ("1/sqrt(2)", "rationalize_denominator", "sqrt(2)/2"), ("1.4142135623730951", "nsimplify", "sqrt(2)"), ] for expression, operation, expected in cases: with self.subTest(operation=operation): self.assertEqual(transform(expression, operation)["result"], expected) def test_collect(self): self.assertEqual(transform("a*x + b*x + c", "collect", variable="x")["result"], "c + x*(a + b)") def test_substitute(self): self.assertEqual(transform("x^2 + y", "substitute", substitutions={"x": "2t", "y": 1})["result"], "4*t**2 + 1") def test_assumption_positive(self): self.assertEqual(transform("sqrt(x^2)", assume="positive")["result"], "x") self.assertEqual(transform("sqrt(x^2)")["result"], "sqrt(x**2)") def test_equivalence_flag(self): self.assertIs(transform("(x+1)^2", "expand")["equivalent_to_input"], True) def test_collect_needs_variable(self): with self.assertRaisesRegex(MathError, "variable"): transform("x + x*y", "collect") class SolveTest(MathTestCase): def test_quadratic(self): self.assertEqual(solve(["x^2 - 5x + 6 = 0"])["solutions"], [{"x": "2"}, {"x": "3"}]) def test_bare_expression_means_zero(self): self.assertEqual(solve(["x^2 - 4"])["count"], 2) def test_complex_vs_real(self): self.assertEqual(solve(["x^2 = -1"])["solutions"], [{"x": "-I"}, {"x": "I"}]) self.assertEqual(solve(["x^2 = -1"], domain="real")["count"], 0) def test_linear_system(self): self.assertEqual(solve(["x + y = 10", "x - y = 2"])["solutions"], [{"x": "6", "y": "4"}]) def test_underdetermined_system(self): self.assertIn("Infinitely many", solve(["x + y = 10"], variables=["x", "y"])["note"]) def test_nonlinear_system(self): r = solve(["x^2 + y^2 = 25", "x - y = 1"]) self.assertEqual({tuple(s.items()) for s in r["solutions"]}, {(("x", "-3"), ("y", "-4")), (("x", "4"), ("y", "3"))}) def test_periodic_solutions(self): r = solve(["sin(x) = 1/2"], domain="real") self.assertIn("ImageSet", r["solution_set"]) self.assertIn({"x": "pi/6"}, r["principal_solutions"]) def test_inequality(self): self.assertEqual(solve(["x^2 < 4"])["solution_set"], "Interval.open(-2, 2)") def test_inequality_system(self): self.assertEqual(solve(["x > 1", "x <= 5"])["solution_set"], "Interval.Lopen(1, 5)") def test_numeric_transcendental(self): self.assertTrue(solve(["cos(x) = x"], numeric_only=True)["solutions"][0]["x"].startswith("0.7390851332")) def test_numeric_polynomial_all_roots(self): self.assertEqual(solve(["x^5 - x + 1"], numeric_only=True)["count"], 5) def test_numeric_system_with_guess(self): r = solve(["x^2 + y^2 = 4", "x = y"], numeric_only=True, initial_guess=[1, 1]) self.assertTrue(r["solutions"][0]["x"].startswith("1.414213562")) def test_positive_domain(self): self.assertEqual(solve(["x^2 = 4"], domain="positive")["solutions"], [{"x": "2"}]) def test_no_variable(self): with self.assertRaises(MathError): solve(["1 = 1"])