From 7a83acf33e0ca102f7efa0c2c00812284601471f Mon Sep 17 00:00:00 2001 From: StableLlama Date: Sat, 7 Jun 2025 01:12:39 +0200 Subject: [PATCH] Add test cases for function node --- tests/test_math_formula_node.py | 237 ++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 tests/test_math_formula_node.py diff --git a/tests/test_math_formula_node.py b/tests/test_math_formula_node.py new file mode 100644 index 0000000..ba469bb --- /dev/null +++ b/tests/test_math_formula_node.py @@ -0,0 +1,237 @@ +import pytest +import math +import re +from src.basic_data_handling.math_formula_node import MathFormula + +def test_basic_formula_evaluation(): + """Test basic formula evaluation with simple operations.""" + node = MathFormula() + + # Simple arithmetic + assert node.evaluate("a + b", a=3, b=4)[0] == pytest.approx(7.0) + assert node.evaluate("a - b", a=10, b=6)[0] == pytest.approx(4.0) + assert node.evaluate("a * b", a=5, b=3)[0] == pytest.approx(15.0) + assert node.evaluate("a / b", a=10, b=2)[0] == pytest.approx(5.0) + + # Combined operations + assert node.evaluate("a + b * c", a=2, b=3, c=4)[0] == pytest.approx(14.0) + assert node.evaluate("(a + b) * c", a=2, b=3, c=4)[0] == pytest.approx(20.0) + assert node.evaluate("a ** b", a=2, b=3)[0] == pytest.approx(8.0) + assert node.evaluate("a // b", a=7, b=2)[0] == pytest.approx(3.0) + assert node.evaluate("a % b", a=7, b=4)[0] == pytest.approx(3.0) + + # Unary minus + assert node.evaluate("-a", a=5)[0] == pytest.approx(-5.0) + assert node.evaluate("a * -b", a=3, b=4)[0] == pytest.approx(-12.0) + +def test_function_calls(): + """Test formula evaluation with math functions.""" + node = MathFormula() + + # Single-argument functions + assert node.evaluate("sin(a)", a=math.pi/6)[0] == pytest.approx(0.5) + assert node.evaluate("cos(a)", a=0)[0] == pytest.approx(1.0) + assert node.evaluate("tan(a)", a=math.pi/4)[0] == pytest.approx(1.0) + assert node.evaluate("sqrt(a)", a=16)[0] == pytest.approx(4.0) + assert node.evaluate("log(a)", a=math.e)[0] == pytest.approx(1.0) + assert node.evaluate("exp(a)", a=2)[0] == pytest.approx(math.exp(2)) + + # Two-argument functions + assert node.evaluate("min(a, b)", a=5, b=10)[0] == pytest.approx(5.0) + assert node.evaluate("max(a, b)", a=5, b=10)[0] == pytest.approx(10.0) + assert node.evaluate("pow(a, b)", a=2, b=3)[0] == pytest.approx(8.0) + +def test_constants(): + """Test formula evaluation with constants.""" + node = MathFormula() + + # Constants called with empty parentheses + assert node.evaluate("pi()")[0] == pytest.approx(math.pi) + assert node.evaluate("e()")[0] == pytest.approx(math.e) + + # Using constants in expressions + assert node.evaluate("2 * pi()")[0] == pytest.approx(2 * math.pi) + assert node.evaluate("e() ** 2")[0] == pytest.approx(math.e ** 2) + assert node.evaluate("sin(pi() / 2)")[0] == pytest.approx(1.0) + assert node.evaluate("log(e())")[0] == pytest.approx(1.0) + + # More complex expressions with constants + assert node.evaluate("a * pi() + b", a=2, b=1)[0] == pytest.approx(2 * math.pi + 1) + assert node.evaluate("e() ** a", a=3)[0] == pytest.approx(math.e ** 3) + assert node.evaluate("sin(a * pi())", a=0.5)[0] == pytest.approx(1.0, abs=1e-10) + +def test_variable_constant_distinction(): + """Test that variables and constants can coexist without conflict.""" + node = MathFormula() + + # Using 'e' as a variable while also using 'e()' as a constant + assert node.evaluate("e + e()", e=5)[0] == pytest.approx(5 + math.e) + + # Using 'pi' as a variable while also using 'pi()' as a constant + with pytest.raises(ValueError, match=r"Function 'pi' must be called with parentheses, e.g., pi()"): + node.evaluate("pi * pi()", pi=3) + + # More complex expressions + assert node.evaluate("e * log(e())", e=2)[0] == pytest.approx(2.0) + assert node.evaluate("a * e() + b * e", a=2, b=3, e=4)[0] == pytest.approx(2 * math.e + 3 * 4) + +def test_error_conditions(): + """Test error conditions in formula evaluation.""" + node = MathFormula() + + # Constants without parentheses should raise ValueError + with pytest.raises(ValueError, match=r"Function 'pi' must be called with parentheses, e.g., pi()"): + node.evaluate("pi") + + with pytest.raises(ValueError, match=r"Variable 'e' was not provided."): + node.evaluate("2 * e") + + # Missing variables + with pytest.raises(ValueError, match=r"Variable 'x' was not provided"): + node.evaluate("x + y", y=5) + + # Invalid function calls + with pytest.raises(ValueError): + node.evaluate("unknown_func(x)", x=5) + + # Division by zero + with pytest.raises(ZeroDivisionError): + node.evaluate("a / b", a=5, b=0) + + # Function domain errors + with pytest.raises(ValueError): + node.evaluate("sqrt(x)", x=-1) # Negative square root + + with pytest.raises(ValueError): + node.evaluate("log(x)", x=0) # Log of zero + + with pytest.raises(ValueError): + node.evaluate("asin(x)", x=2) # Out of domain [-1, 1] + +def test_complex_expressions(): + """Test evaluation of complex mathematical expressions.""" + node = MathFormula() + + # Complex expression with variables, functions, and constants + formula = "a * sin(b * pi()) + c * sqrt(d) + e() ** 2" + result = node.evaluate(formula, a=2, b=0.5, c=3, d=9)[0] + expected = 2 * math.sin(0.5 * math.pi) + 3 * math.sqrt(9) + math.e ** 2 + assert result == pytest.approx(expected) + + # Expression with nested functions + formula = "sqrt(pow(a, 2) + pow(b, 2))" + assert node.evaluate(formula, a=3, b=4)[0] == pytest.approx(5.0) + + # Expression with multiple operations and precedence + formula = "a + b * c - d / e + f ** g" + result = node.evaluate(formula, a=1, b=2, c=3, d=8, e=4, f=2, g=3)[0] + expected = 1 + 2 * 3 - 8 / 4 + 2 ** 3 + assert result == pytest.approx(expected) + +def test_formula_with_e_variable(): + """Test formulas with 'e' as a variable to ensure it doesn't clash with the constant.""" + node = MathFormula() + + # Using 'e' as a variable + assert node.evaluate("e * 2", e=5)[0] == pytest.approx(10.0) + + # Using both 'e' as a variable and 'e()' as a constant in the same expression + assert node.evaluate("e + e()", e=3)[0] == pytest.approx(3 + math.e) + + # More complex formula with both uses + formula = "a * e + b * e()" + result = node.evaluate(formula, a=2, e=3, b=4)[0] + expected = 2 * 3 + 4 * math.e + assert result == pytest.approx(expected) + + # Formula using 'e' in different contexts + formula = "e * sin(e() * pi())" + result = node.evaluate(formula, e=2)[0] + expected = 2 * math.sin(math.e * math.pi) + assert result == pytest.approx(expected) + +def test_whitespace_handling(): + """Test that the parser correctly handles different whitespace patterns.""" + node = MathFormula() + + # No spaces + assert node.evaluate("a+b*c", a=1, b=2, c=3)[0] == pytest.approx(7.0) + + # Lots of spaces + assert node.evaluate("a + b * c", a=1, b=2, c=3)[0] == pytest.approx(7.0) + + # Mixed spacing around functions + assert node.evaluate("sin( a )+cos(b)", a=math.pi/2, b=0)[0] == pytest.approx(2.0) + + # Spacing around constants + assert node.evaluate("2*pi( )", a=1)[0] == pytest.approx(2 * math.pi) + assert node.evaluate("e( )**2")[0] == pytest.approx(math.e ** 2) + +def test_tokenizing(): + """Test the tokenization functionality.""" + node = MathFormula() + + # Test basic tokenizing + tokens = node.tokenize_formula("a + b * c") + assert "a" in tokens + assert "+" in tokens + assert "b" in tokens + assert "*" in tokens + assert "c" in tokens + + # Test tokenizing functions + tokens = node.tokenize_formula("sin(a) + cos(b)") + assert "sin" in tokens + assert "(" in tokens + assert "a" in tokens + assert ")" in tokens + assert "+" in tokens + assert "cos" in tokens + assert "b" in tokens + + # Test tokenizing constants with parentheses + tokens = node.tokenize_formula("pi() + e()") + assert "pi" in tokens + assert "(" in tokens + assert ")" in tokens + assert "+" in tokens + assert "e" in tokens + + # Test complex expression + tokens = node.tokenize_formula("a + b * (c - d) / e() ** 2") + assert all(token in tokens for token in ["a", "+", "b", "*", "(", "c", "-", "d", ")", "/", "e", "(", ")", "**", "2"]) + +def test_infix_to_postfix(): + """Test the infix to postfix conversion.""" + node = MathFormula() + + # Basic conversion + formula = "a + b" + tokens = node.tokenize_formula(formula) + postfix = node.infix_to_postfix(tokens) + + # Test operator precedence + formula = "a + b * c" + tokens = node.tokenize_formula(formula) + postfix = node.infix_to_postfix(tokens) + # We can't test exact postfix because it contains internal representations, + # but we can test the evaluation result + assert node.evaluate(formula, a=1, b=2, c=3)[0] == pytest.approx(7.0) + + # Test parentheses + formula = "(a + b) * c" + tokens = node.tokenize_formula(formula) + postfix = node.infix_to_postfix(tokens) + assert node.evaluate(formula, a=1, b=2, c=3)[0] == pytest.approx(9.0) + + # Test function calls + formula = "sin(a) + cos(b)" + tokens = node.tokenize_formula(formula) + postfix = node.infix_to_postfix(tokens) + assert node.evaluate(formula, a=math.pi/2, b=0)[0] == pytest.approx(2.0) + + # Test constant function calls + formula = "pi() + e()" + tokens = node.tokenize_formula(formula) + postfix = node.infix_to_postfix(tokens) + assert node.evaluate(formula)[0] == pytest.approx(math.pi + math.e)