Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions app/preview_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,100 @@ def test_laplace_transforms(self, response, is_latex, latex, sympy):
assert result["preview"]["latex"] == latex
assert result["preview"]["sympy"] == sympy

@pytest.mark.parametrize(
"response, is_latex, latex, sympy", [
("(x+y)*(x+z)", False, "\\left(x + y\\right) \\cdot \\left(x + z\\right)","(x+y)*(x+z)"),
("(x+y)*x+z", False, "x \\cdot \\left(x + y\\right) + z", "(x+y)*x+z"),
("x+y*(x+z)", False, "x + y \\cdot \\left(x + z\\right)", "x+y*(x+z)"),
("[x+y]*[x+z]", False, "\\left(x + y\\right) \\cdot \\left(x + z\\right)", "(x+y)*(x+z)"),
("[x+y]*x+z", False, "x \\cdot \\left(x + y\\right) + z", "(x+y)*x+z"),
("x+y*[x+z]", False, "x + y \\cdot \\left(x + z\\right)", "x+y*(x+z)"),
("{x+y}*{x+z}", False, "\\left(x + y\\right) \\cdot \\left(x + z\\right)", "(x+y)*(x+z)"),
("{x+y}*x+z", False, "x \\cdot \\left(x + y\\right) + z", "(x+y)*x+z"),
("x+y*{x+z}", False, "x + y \\cdot \\left(x + z\\right)", "x+y*(x+z)"),
]
)
def test_brackets(self, response, is_latex, latex, sympy):
params = {
"is_latex": is_latex,
"strict_syntax": False,
"elementary_functions": True,
"convention": "implicit_higher_precedence",
}

result = preview_function(response, params)
assert result["preview"]["latex"] == latex
assert result["preview"]["sympy"] == sympy

@pytest.mark.parametrize(
"response, latex, sympy", [
("(x+y)*(x+z)", "\\left(x + y\\right) \\cdot \\left(x + z\\right)", "(x+y)*(x+z)"),
("(x+y)*x+z", "x \\cdot \\left(x + y\\right) + z", "(x+y)*x+z"),
("x+y*(x+z)", "x + y \\cdot \\left(x + z\\right)", "x+y*(x+z)"),
]
)
def test_brackets_strict_syntax_parentheses_still_work(self, response, latex, sympy):
params = {
"is_latex": False,
"strict_syntax": True,
"elementary_functions": True,
"convention": "implicit_higher_precedence",
}

result = preview_function(response, params)
assert result["preview"]["latex"] == latex
assert result["preview"]["sympy"] == sympy

@pytest.mark.parametrize(
"response", [
"[x+y]*[x+z]",
"[x+y]*x+z",
"x+y*[x+z]",
]
)
def test_brackets_rejected_with_strict_syntax(self, response):
params = {
"is_latex": False,
"strict_syntax": True,
"elementary_functions": True,
"convention": "implicit_higher_precedence",
}

with pytest.raises(ValueError):
preview_function(response, params)

@pytest.mark.parametrize(
"response, latex, sympy", [
("{x+y}*{x+z}", "\\left\\{x + y\\right\\} \\cdot \\left\\{x + z\\right\\}", "{x+y}*{x+z}"),
("{x+y}*x+z", "\\left\\{x + y\\right\\} \\cdot x + z", "{x+y}*x+z"),
("x+y*{x+z}", "x + y \\cdot \\left\\{x + z\\right\\}", "x+y*{x+z}"),
]
)
def test_curly_braces_not_converted_with_strict_syntax(self, response, latex, sympy):
params = {
"is_latex": False,
"strict_syntax": True,
"elementary_functions": True,
"convention": "implicit_higher_precedence",
}

result = preview_function(response, params)
assert result["preview"]["latex"] == latex
assert result["preview"]["sympy"] == sympy

@pytest.mark.parametrize("strict_syntax", [False, True])
def test_curly_brace_multiple_answers_still_works(self, strict_syntax):
params = {
"is_latex": False,
"strict_syntax": strict_syntax,
"elementary_functions": True,
"convention": "implicit_higher_precedence",
}

result = preview_function("{1, 2}", params)
assert result["preview"]["latex"] == "\\left\\{1,~2\\right\\}"
assert result["preview"]["sympy"] == "{1, 2}"


if __name__ == "__main__":
pytest.main(['-xk not slow', "--tb=line", os.path.abspath(__file__)])
21 changes: 21 additions & 0 deletions app/tests/expression_utilities_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
create_expression_set,
extract_latex,
find_matching_parenthesis,
is_multiple_answers_wrapper,
latex_symbols,
preprocess_expression,
protect_elementary_functions_substitutions,
Expand Down Expand Up @@ -356,6 +357,26 @@ def test_plus_minus_expands_to_two_expressions(self):
assert sorted(result) == sorted(["+x", "-x"]) or sorted(result) == sorted(["x", "-x"])
assert len(result) == 2

def test_curly_braces_used_for_grouping_are_not_split(self):
result = create_expression_set("{x+1}*{x-2}", {})
assert result == ["{x+1}*{x-2}"]


class TestIsMultipleAnswersWrapper:

@pytest.mark.parametrize(
"expr, expected", [
("{x, y}", True),
("{x+1}", True),
("{(x+1), (x-1)}", True),
("x+y", False),
("{x+1}*{x-2}", False),
("{x+1}*x", False),
]
)
def test_is_multiple_answers_wrapper(self, expr, expected):
assert is_multiple_answers_wrapper(expr) is expected


class TestPreprocessExpression:

Expand Down
6 changes: 3 additions & 3 deletions app/tests/symbolic_evaluation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1956,8 +1956,8 @@ def test_elementary_function_symbol_with_implicit_multiplication_on_both_sides(s
result = evaluation_function(response, answer, params)
assert result["is_correct"] is True

def test_response_for_which_correctness_cannot_be_determined(self):
response = "2 pi e^{-a |omega|}" # The expression in {...} is interpreted as elements in a set instead of a math expression
def test_curly_braces_used_for_grouping_in_response(self):
response = "2 pi e^{-a |omega|}" # {} is accepted as another way of writing () for grouping
answer = "2 pi e^(-a|omega|)"
params = {
'atol': 0,
Expand All @@ -1967,7 +1967,7 @@ def test_response_for_which_correctness_cannot_be_determined(self):
'elementary_functions': True,
}
result = evaluation_function(response, answer, params)
assert result["is_correct"] is False
assert result["is_correct"] is True

def test_unexpected_equalities_in_response_that_generates_set(self):
response = "z= plus_minus 1 + 2*i" # plus_minus generates a set of two equalities
Expand Down
33 changes: 32 additions & 1 deletion app/utility/expression_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,22 @@ def _print_log(self, expr, exp=None):


# -------- String Manipulation Utilities
def is_multiple_answers_wrapper(expr):
"""
True if expr, as a whole, is wrapped in a top-level {...} pair that
create_expression_set will treat as a set of multiple acceptable
answers, as opposed to {} that merely sit at the string's edges
incidentally (e.g. "{x+1}*{x-2}").
"""
stripped = expr.strip()
if not (stripped.startswith('{') and stripped.endswith('}')):
return False
return find_matching_parenthesis(stripped, 0, delimiters=('{', '}')) == len(stripped) - 1


def create_expression_set(exprs, params):
if isinstance(exprs, str):
if exprs.startswith('{') and exprs.endswith('}'):
if is_multiple_answers_wrapper(exprs):
exprs = [expr.strip() for expr in exprs[1:-1].split(',')]
else:
exprs = [exprs]
Expand All @@ -156,6 +169,20 @@ def create_expression_set(exprs, params):
return list(expr_set)


def convert_bracket_notation(expr):
"""
Accept [] and {} as other ways of writing (), SymPy only accepts ()
for grouping since [], {}, and () are otherwise reserved for lists,
sets, and tuples respectively. {} is left untouched when it spans the
entire expression, since that denotes a set of multiple acceptable
answers (see create_expression_set).
"""
expr = expr.replace("[", "(").replace("]", ")")
if is_multiple_answers_wrapper(expr):
return expr
return expr.replace("{", "(").replace("}", ")")


def convert_absolute_notation(expr, name):
"""
Accept || as another form of writing modulus of an expression.
Expand Down Expand Up @@ -735,6 +762,8 @@ def substitutions_sort_key(x):
def preprocess_expression(name, expr, parameters):
expr = substitute_input_symbols(expr.strip(), parameters)
expr = expr[0]
if not parameters.get("strict_syntax", False):
expr = convert_bracket_notation(expr)
expr, abs_feedback = convert_absolute_notation(expr, name)
success = True
if abs_feedback is not None:
Expand Down Expand Up @@ -763,6 +792,8 @@ def parse_expression(expr_string, parsing_params):
parsed_expr_set = set()

for expr in expr_set:
if not strict_syntax:
expr = convert_bracket_notation(expr)
expr = preprocess_according_to_chosen_convention(expr, parsing_params)

substitutions = list(set(substitutions))
Expand Down
Loading