mcp-calculator/tests/test_calculus.py
2026-09-15 20:38:16 +02:00

104 lines
4.6 KiB
Python

from mcp_calculator.ops.calculus import analyze_function, calculus, solve_ode
from mcp_calculator.ops.context import MathError
from tests.support import MathTestCase
class CalculusTest(MathTestCase):
def test_derivative(self):
self.assertEqual(calculus("derivative", "x^3")["result"], "3*x**2")
def test_higher_derivative(self):
self.assertEqual(calculus("derivative", "x^5", order=3)["result"], "60*x**2")
def test_mixed_partial(self):
self.assertEqual(calculus("derivative", "x^2*y^3", variable="x, y")["result"], "6*x*y**2")
def test_indefinite_integral(self):
r = calculus("integral", "x*e^x")
self.assertEqual(r["result"], "(x - 1)*exp(x)")
self.assertIn("constant", r["note"])
def test_definite_integral(self):
self.assertEqual(calculus("integral", "e^(-x^2)", lower="-oo", upper="oo")["result"], "sqrt(pi)")
def test_numeric_integral_fallback(self):
r = calculus("integral", "sin(sin(x))", lower="0", upper="1")
self.assertTrue(r["method"].startswith("numeric"))
self.assertTrue(r["numeric"].startswith("0.4306061031"))
def test_integral_needs_both_bounds(self):
with self.assertRaises(MathError):
calculus("integral", "x", lower="0")
def test_limit(self):
self.assertEqual(calculus("limit", "sin(x)/x", point="0")["result"], "1")
self.assertEqual(calculus("limit", "(1 + 1/n)^n", variable="n", point="oo")["result"], "E")
def test_one_sided_limit(self):
self.assertEqual(calculus("limit", "1/x", point="0", direction="+")["result"], "oo")
self.assertEqual(calculus("limit", "1/x", point="0", direction="-")["result"], "-oo")
def test_series(self):
self.assertEqual(calculus("series", "exp(x)", terms=4)["polynomial"], "x**3/6 + x**2/2 + x + 1")
def test_infinite_sum(self):
r = calculus("sum", "1/n^2", variable="n", lower="1", upper="oo")
self.assertEqual(r["result"], "pi**2/6")
self.assertIs(r["converges"], True)
def test_finite_sum_and_product(self):
self.assertEqual(calculus("sum", "k", variable="k", lower="1", upper="100")["result"], "5050")
self.assertEqual(calculus("product", "k", variable="k", lower="1", upper="5")["result"], "120")
def test_gradient_hessian_jacobian(self):
self.assertEqual(calculus("gradient", "x^2*y", variable="x,y")["result"], ["2*x*y", "x**2"])
self.assertEqual(calculus("hessian", "x^2*y", variable="x,y")["result"], [["2*y", "2*x"], ["2*x", "0"]])
self.assertEqual(calculus("jacobian", "x*y, x + y", variable="x,y")["result"], [["y", "x"], ["1", "1"]])
class AnalyzeFunctionTest(MathTestCase):
def test_cubic(self):
r = analyze_function("x^3 - 3x")
self.assertEqual({z["x"] for z in r["zeros"]}, {"0", "-sqrt(3)", "sqrt(3)"})
self.assertEqual({(e["x"], e["type"]) for e in r["extrema"]}, {("-1", "maximum"), ("1", "minimum")})
self.assertEqual(r["inflection_points"][0]["x"], "0")
self.assertTrue(r["symmetry"].startswith("odd"))
self.assertEqual(r["limit_at_plus_infinity"], "oo")
def test_rational_function(self):
r = analyze_function("1/(x^2 - 1)")
self.assertEqual(r["vertical_asymptotes"], ["-1", "1"])
self.assertTrue(r["symmetry"].startswith("even"))
self.assertEqual(r["limit_at_plus_infinity"], "0")
def test_saddle_point(self):
self.assertTrue(analyze_function("x^3")["extrema"][0]["type"].startswith("saddle"))
def test_rejects_parameters(self):
with self.assertRaisesRegex(MathError, "parameter"):
analyze_function("a*x^2")
class OdeTest(MathTestCase):
def test_general_solution(self):
r = solve_ode("y'' + y = 0")
self.assertEqual(r["solutions"], ["C1*sin(x) + C2*cos(x)"])
self.assertIn("C1", r["note"])
def test_initial_value_problem(self):
self.assertEqual(solve_ode("y' = 2y", initial_conditions={"y(0)": 3})["solutions"], ["3*exp(2*x)"])
def test_second_order_ivp(self):
self.assertEqual(solve_ode("y'' + y = 0", initial_conditions={"y(0)": 0, "y'(0)": 1})["solutions"], ["sin(x)"])
def test_custom_names(self):
r = solve_ode("N' = -k*N", function="N", variable="t", initial_conditions={"N(0)": "N0"})
self.assertEqual(r["solutions"], ["N0*exp(-k*t)"])
def test_requires_derivative(self):
with self.assertRaisesRegex(MathError, "no derivative"):
solve_ode("y = x")
def test_bad_initial_condition_key(self):
with self.assertRaisesRegex(MathError, "initial condition"):
solve_ode("y' = y", initial_conditions={"z(0)": 1})