Skip to content

Commit dcb4dbb

Browse files
Merge pull request #203 from lambda-feedback/tr170-basic-generic-syntactical-comparison
Improved syntax check to make it easier to check for variations of ex…
2 parents 1ff3362 + 04259de commit dcb4dbb

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)