Skip to content

Commit 04259de

Browse files
Improved syntax check to make it easier to check for variations of expressions using cases
If the use the criteria "response written as answer" it only checks the syntax, unlike the built in check from "syntactical_comparison". The generated feedback is worse though.
1 parent d9cde00 commit 04259de

3 files changed

+79
-9
lines changed

app/symbolic_comparison_evaluation.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from .syntactical_comparison_utilities import is_number as syntactical_is_number
2727
from .syntactical_comparison_utilities import response_and_answer_on_same_form
2828
from .syntactical_comparison_utilities import written_as_answer
29+
from .syntactical_comparison_utilities import written_as
2930
from .syntactical_comparison_utilities import attach_form_criteria
3031

3132
from .criteria_graph_utilities import CriteriaGraph
@@ -54,6 +55,7 @@ def generate_criteria_parser():
5455
(" *; *", "SEPARATOR"),
5556
("response", "EXPR"),
5657
(" *where *", "WHERE"),
58+
(" *written +as *","WRITTENAS"),
5759
("answer", "EXPR"),
5860
("EQUAL", "EQUAL"),
5961
("EQUALS", "EQUALS"),
@@ -69,6 +71,7 @@ def generate_criteria_parser():
6971
("EQUAL", "EQUAL where EQUALS", infix),
7072
("EQUALS", "EQUAL;EQUAL", infix),
7173
("EQUALS", "EQUALS;EQUAL", append_last),
74+
("BOOL", "EXPR written as EXPR", infix),
7275
("EQUAL", "EXPR=EXPR", infix),
7376
("EXPR", "-EXPR", join),
7477
("EXPR", "EXPR-EXPR", infix),
@@ -116,12 +119,13 @@ def check_criterion(criterion, parameters_dict, generate_feedback=True):
116119
expr = parse_expression(sub.children[1].content_string(), parsing_params)
117120
local_subs.append((name, expr))
118121
result = check_criterion(crit, {**parameters_dict, **{"local_substitutions": local_subs}}, generate_feedback)
122+
elif label == "WRITTENAS":
123+
result = written_as(criterion.children[0].content, criterion.children[1].content, parameters_dict)
119124
elif label in criteria_operations.keys():
120125
result = criteria_operations[label](criterion.children, parameters_dict)
121126
return result
122127

123128
def check_equality(criterion, parameters_dict):
124-
125129
reserved_expressions = list(parameters_dict["reserved_expressions"].items())
126130
local_substitutions = parameters_dict.get("local_substitutions",[])
127131
parsing_params = {key: value for (key,value) in parameters_dict["parsing_params"].items()}
@@ -842,7 +846,9 @@ def symbolic_comparison(response, answer, params, eval_response) -> dict:
842846

843847
# Parse criteria
844848
criteria_parser = generate_criteria_parser()
845-
parsing_params["unsplittable_symbols"] += ("response", "answer", "where")
849+
reserved_keywords = ("response", "answer", "where", "written as")
850+
parsing_params["unsplittable_symbols"] += reserved_keywords
851+
params["reserved_keywords"] += reserved_keywords
846852
reserved_expressions = {
847853
"response": res,
848854
"answer": ans,

app/symbolic_comparison_evaluation_tests.py

+6
Original file line numberDiff line numberDiff line change
@@ -1234,9 +1234,15 @@ def test_disabled_evaluation_nodes(self, response, answer, criteria, value, disa
12341234
("2*e**(2*I)", "2*e^(2*I)", "answer=response", True, ["answer=response_TRUE", "answer=response_SAME_SYMBOLS_TRUE", "answer=response_SYNTACTICAL_EQUIVALENCE_FALSE", "answer=response_SAME_FORM_EXPONENTIAL"], {}),
12351235
("e**(2*I)", "1*e^(2*I)", "answer=response", True, ["answer=response_TRUE", "answer=response_SAME_SYMBOLS_TRUE", "answer=response_SYNTACTICAL_EQUIVALENCE_FALSE", "answer=response_SAME_FORM_EXPONENTIAL"], {}),
12361236
("0.48+0.88*I", "1*e^(0.5*I)", "answer=response", False, ["answer=response_FALSE", "answer=response_SAME_FORM_UNKNOWN"], {}),
1237+
("(x-4)^2-5", "(x-4)^2-5", "response written as answer", True, ["response written as answer_TRUE"], {"detailed_feedback": True}),
1238+
("(x-5)^2-6", "(x-4)^2-5", "response written as answer", True, ["response written as answer_TRUE"], {"detailed_feedback": True}),
12371239
("(x-4)^2-5", "(x-4)^2-5", "answer=response", True, ["answer=response_TRUE", "answer=response_SAME_SYMBOLS_TRUE", "answer=response_WRITTEN_AS_ANSWER_TRUE"], {"detailed_feedback": True}),
1240+
("(x-4)^2 - 5", "(x-4)^2-5", "answer=response", True, ["answer=response_TRUE", "answer=response_SAME_SYMBOLS_TRUE", "answer=response_WRITTEN_AS_ANSWER_TRUE"], {"detailed_feedback": True}),
12381241
("x^2-8x+11", "(x-4)^2-5", "answer=response", False, ["answer=response_TRUE", "answer=response_SAME_SYMBOLS_TRUE", "answer=response_WRITTEN_AS_ANSWER_FALSE"], {"detailed_feedback": True}),
12391242
("(x-3)^2-3", "(x-4)^2-5", "answer=response", False, ["answer=response_FALSE", "answer=response_WRITTEN_AS_ANSWER_TRUE"], {"detailed_feedback": True}),
1243+
("(x+4)^2-5", "(x+(-4))^2-5", "response written as answer", True, ["response written as answer_TRUE"], {"detailed_feedback": True}),
1244+
("(x-4)^2+5", "(x-4)^2+(-5)", "response written as answer", True, ["response written as answer_TRUE"], {"detailed_feedback": True}),
1245+
("(x+4)^2+5", "(x+(-4))^2+(-5)", "response written as answer", True, ["response written as answer_TRUE"], {"detailed_feedback": True}),
12401246
]
12411247
)
12421248
def test_syntactical_comparison(self, response, answer, criteria, value, feedback_tags, additional_params):

app/syntactical_comparison_utilities.py

+65-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .feedback.symbolic_comparison import feedback_generators as symbolic_feedback_generators
33
from .criteria_graph_utilities import CriteriaGraph
44

5+
is_nonnegative_number_regex = '((0|[1-9]\d*)?(\.\d+)?(?<=\d)(e-?(0|[1-9]\d*))?)'
56
is_number_regex = '(-?(0|[1-9]\d*)?(\.\d+)?(?<=\d)(e-?(0|[1-9]\d*))?)'
67

78
def is_number(string):
@@ -25,22 +26,67 @@ def escape_regex_reserved_characters(string):
2526
string = string.replace(s,'\\'+s)
2627
return string
2728

29+
#def generate_arbitrary_number_pattern_matcher(string):
30+
# non_numbers = []
31+
# number = re.search(is_number_regex, string)
32+
# start = 0
33+
# end = 0
34+
# offset = 0
35+
# while number is not None:
36+
# start, end = number.span()
37+
# start += offset
38+
# end += offset
39+
# non_number = escape_regex_reserved_characters(string[offset:start])
40+
# non_number = ''.join(non_number.split())
41+
# non_numbers.append(non_number)
42+
# offset = end
43+
# number = re.search(is_number_regex, string[offset:])
44+
# non_numbers.append(string[offset:])
45+
# pattern = is_number_regex.join(non_numbers)
46+
# def matcher(comp_string):
47+
# comp_string = ''.join(comp_string.split())
48+
# result = re.fullmatch(pattern, comp_string)
49+
# return result is not None
50+
# return matcher
51+
2852
def generate_arbitrary_number_pattern_matcher(string):
2953
non_numbers = []
30-
number = re.search(is_number_regex, string)
54+
number_pattern = '(\\('+is_number_regex+'\\))'
55+
nonneg_number_pattern = is_nonnegative_number_regex
56+
#number_pattern = '('+'(\\\\('+is_number_regex+'\\\\))'+'|'+is_nonnegative_number_regex+')'
57+
full_pattern = '('+number_pattern+'|'+nonneg_number_pattern+')'
58+
number = re.search(number_pattern, string)
59+
nonneg_number = re.search(nonneg_number_pattern, string)
3160
start = 0
3261
end = 0
3362
offset = 0
34-
while number is not None:
35-
start, end = number.span()
63+
while (number is not None) or (nonneg_number is not None):
64+
start_number = len(string)
65+
end_number = len(string)
66+
start_nonneg_number = len(string)
67+
end_nonneg_number = len(string)
68+
if number is not None:
69+
start_number, end_number = number.span()
70+
if nonneg_number is not None:
71+
start_nonneg_number, end_nonneg_number = nonneg_number.span()
72+
if start_number < start_nonneg_number:
73+
start, end = number.span()
74+
else:
75+
start, end = nonneg_number.span()
3676
start += offset
3777
end += offset
38-
non_numbers.append(escape_regex_reserved_characters(string[offset:start]))
78+
non_number = escape_regex_reserved_characters(string[offset:start])
79+
if len(non_number) > 0:
80+
non_number = '('+non_number+')'
81+
non_number = ''.join(non_number.split())
82+
non_numbers.append(non_number)
3983
offset = end
40-
number = re.search(is_number_regex, string[offset:])
84+
number = re.search(number_pattern, string[offset:])
85+
nonneg_number = re.search(nonneg_number_pattern, string[offset:])
4186
non_numbers.append(string[offset:])
42-
pattern = is_number_regex.join(non_numbers)
87+
pattern = full_pattern.join(non_numbers)
4388
def matcher(comp_string):
89+
comp_string = ''.join(comp_string.split())
4490
result = re.fullmatch(pattern, comp_string)
4591
return result is not None
4692
return matcher
@@ -97,4 +143,16 @@ def inner(unused_input):
97143
else:
98144
matches_found.add(label+"_FALSE")
99145
return matches_found
100-
return inner
146+
return inner
147+
148+
def written_as(comp, ref, parameters_dict):
149+
if ref == "answer":
150+
ref = parameters_dict["original_input"]["answer"]
151+
elif ref == "response":
152+
ref = parameters_dict["original_input"]["response"]
153+
if comp == "answer":
154+
comp = parameters_dict["original_input"]["answer"]
155+
elif comp == "response":
156+
comp = parameters_dict["original_input"]["response"]
157+
matcher = generate_arbitrary_number_pattern_matcher(ref)
158+
return matcher(comp)

0 commit comments

Comments
 (0)