diff --git a/app/context/symbolic.py b/app/context/symbolic.py index 960c989c..7918bb3b 100644 --- a/app/context/symbolic.py +++ b/app/context/symbolic.py @@ -1,6 +1,8 @@ from copy import deepcopy from sympy import Add, Pow, Mul, Equality, pi, im, I, N, oo from sympy import re as real_part +from sympy.core.relational import Relational +from sympy.logic.boolalg import BooleanAtom from ..utility.expression_utilities import ( default_parameters, @@ -115,12 +117,21 @@ def do_comparison(comparison_symbol, expression): return result +def is_inequality_or_boolean(expr): + # Inequalities (e.g. 8 < 9) and booleans (e.g. 5 < 1.43 evaluated to False) are not supported + # and cannot be subtracted, so they cannot be compared like other expressions. + # NOTE: Not checking for Boolean, since Symbol is a subclass of Boolean. + return isinstance(expr, (Relational, BooleanAtom)) and not isinstance(expr, Equality) + + def check_equality(criterion, parameters_dict, local_substitutions=[]): lhs_expr, rhs_expr = create_expressions_for_comparison(criterion, parameters_dict, local_substitutions) if isinstance(lhs_expr, Equality) and not isinstance(rhs_expr, Equality): result = False elif not isinstance(lhs_expr, Equality) and isinstance(rhs_expr, Equality): result = False + elif is_inequality_or_boolean(lhs_expr) or is_inequality_or_boolean(rhs_expr): + result = False else: try: either_is_infinite = lhs_expr.is_infinite or rhs_expr.is_infinite diff --git a/app/evaluation.py b/app/evaluation.py index 817e3283..5d8f1b78 100644 --- a/app/evaluation.py +++ b/app/evaluation.py @@ -9,6 +9,7 @@ from .utility.evaluation_result_utilities import EvaluationResult from .utility.preview_utilities import parse_latex from .context.symbolic import context as symbolic_context +from .context.symbolic import is_inequality_or_boolean from .context.physical_quantity import context as quantity_context from .feedback.symbolic import feedback_generators as symbolic_feedback_string_generators @@ -33,7 +34,10 @@ class EvaluationError(Exception): messages = { - "RESERVED_EXPRESSION_MISSING": lambda label: f"Reserved expression `{label}` is not defined." + "RESERVED_EXPRESSION_MISSING": lambda label: f"Reserved expression `{label}` is not defined.", + "RESERVED_EXPRESSION_INEQUALITY": lambda label: ( + f"Reserved expression `{label}` is an inequality or evaluates to true/false, which is not supported." + ) } @@ -174,6 +178,11 @@ def parse_reserved_expressions(reserved_expressions, parameters, result): ) else: raise TaskConfigurationError(symbolic_comparison_internal_messages("PARSE_ERROR")({'x': expr})) from e + if key == "task" and is_inequality_or_boolean(expr_parsed): + raise TaskConfigurationError( + messages["RESERVED_EXPRESSION_INEQUALITY"](label), + f"RESERVED_EXPRESSION_INEQUALITY_{label}" + ) reserved_expressions_dict[key].update({label: expr_parsed}) return success, reserved_expressions_dict diff --git a/app/tests/symbolic_evaluation_test.py b/app/tests/symbolic_evaluation_test.py index 89243cb6..78e37a36 100644 --- a/app/tests/symbolic_evaluation_test.py +++ b/app/tests/symbolic_evaluation_test.py @@ -1,7 +1,7 @@ import pytest import os -from ..evaluation import evaluation_function +from ..evaluation import TaskConfigurationError, evaluation_function from ..utility.expression_utilities import elementary_functions_names, substitute # REMARK: If a case is marked with an alternative output, this means that it is difficult in this case to prevent sympy from simplifying for that particular case @@ -2144,7 +2144,29 @@ def test_equality_with_both_sides_equal_in_response(self): } result = evaluation_function(response, answer, params) assert result["is_correct"] is False - + + @pytest.mark.parametrize("response", ["8<9", "x<8", "8>=8", "8<5"]) + def test_inequality_response_is_incorrect(self, response): + answer = "8" + params = { + "strict_syntax": False, + "elementary_functions": True, + "relative_tolerance": 0.0005, + } + result = evaluation_function(response, answer, params) + assert result["is_correct"] is False + + @pytest.mark.parametrize("answer", ["5<1.43", "x<8"]) + def test_inequality_answer_is_task_configuration_error(self, answer): + response = "5<1.43" + params = { + "strict_syntax": False, + "elementary_functions": True, + } + with pytest.raises(TaskConfigurationError) as e: + evaluation_function(response, answer, params) + assert "`answer` is an inequality" in str(e.value) + def test_integral(self): response = "6y" answer = "int(3*x*y, (x, 0, 2))"