Commit 5f95c684 by Carlos Andrés Rocha

Make numeric response handle plus sign exponential correctly

NumericResponse was failing to accept as correct an answer with a plus
sign in the exponential. For example: 5.0e+1 was marked as incorrect
if the answer was 50. However, 5.0e1 was marked as correct. This
caused confusion, since the answer shown to students included the plus
sign.

LMS Lighthouse [#242]
parent 5b0849d4
...@@ -183,7 +183,7 @@ def evaluator(variables, functions, string, cs=False): ...@@ -183,7 +183,7 @@ def evaluator(variables, functions, string, cs=False):
# 0.33k or -17 # 0.33k or -17
number = (Optional(minus | plus) + inner_number number = (Optional(minus | plus) + inner_number
+ Optional(CaselessLiteral("E") + Optional("-") + number_part) + Optional(CaselessLiteral("E") + Optional((plus | minus)) + number_part)
+ Optional(number_suffix)) + Optional(number_suffix))
number = number.setParseAction(number_parse_action) # Convert to number number = number.setParseAction(number_parse_action) # Convert to number
......
...@@ -19,7 +19,7 @@ from capa.xqueue_interface import dateformat ...@@ -19,7 +19,7 @@ from capa.xqueue_interface import dateformat
class ResponseTest(unittest.TestCase): class ResponseTest(unittest.TestCase):
""" Base class for tests of capa responses.""" """ Base class for tests of capa responses."""
xml_factory_class = None xml_factory_class = None
def setUp(self): def setUp(self):
...@@ -43,7 +43,7 @@ class ResponseTest(unittest.TestCase): ...@@ -43,7 +43,7 @@ class ResponseTest(unittest.TestCase):
for input_str in incorrect_answers: for input_str in incorrect_answers:
result = problem.grade_answers({'1_2_1': input_str}).get_correctness('1_2_1') result = problem.grade_answers({'1_2_1': input_str}).get_correctness('1_2_1')
self.assertEqual(result, 'incorrect', self.assertEqual(result, 'incorrect',
msg="%s should be marked incorrect" % str(input_str)) msg="%s should be marked incorrect" % str(input_str))
class MultiChoiceResponseTest(ResponseTest): class MultiChoiceResponseTest(ResponseTest):
...@@ -61,7 +61,7 @@ class MultiChoiceResponseTest(ResponseTest): ...@@ -61,7 +61,7 @@ class MultiChoiceResponseTest(ResponseTest):
def test_named_multiple_choice_grade(self): def test_named_multiple_choice_grade(self):
problem = self.build_problem(choices=[False, True, False], problem = self.build_problem(choices=[False, True, False],
choice_names=["foil_1", "foil_2", "foil_3"]) choice_names=["foil_1", "foil_2", "foil_3"])
# Ensure that we get the expected grades # Ensure that we get the expected grades
self.assert_grade(problem, 'choice_foil_1', 'incorrect') self.assert_grade(problem, 'choice_foil_1', 'incorrect')
self.assert_grade(problem, 'choice_foil_2', 'correct') self.assert_grade(problem, 'choice_foil_2', 'correct')
...@@ -117,7 +117,7 @@ class ImageResponseTest(ResponseTest): ...@@ -117,7 +117,7 @@ class ImageResponseTest(ResponseTest):
# Anything inside the rectangle (and along the borders) is correct # Anything inside the rectangle (and along the borders) is correct
# Everything else is incorrect # Everything else is incorrect
correct_inputs = ["[12,19]", "[10,10]", "[20,20]", correct_inputs = ["[12,19]", "[10,10]", "[20,20]",
"[10,15]", "[20,15]", "[15,10]", "[15,20]"] "[10,15]", "[20,15]", "[15,10]", "[15,20]"]
incorrect_inputs = ["[4,6]", "[25,15]", "[15,40]", "[15,4]"] incorrect_inputs = ["[4,6]", "[25,15]", "[15,40]", "[15,4]"]
self.assert_multiple_grade(problem, correct_inputs, incorrect_inputs) self.assert_multiple_grade(problem, correct_inputs, incorrect_inputs)
...@@ -259,7 +259,7 @@ class OptionResponseTest(ResponseTest): ...@@ -259,7 +259,7 @@ class OptionResponseTest(ResponseTest):
xml_factory_class = OptionResponseXMLFactory xml_factory_class = OptionResponseXMLFactory
def test_grade(self): def test_grade(self):
problem = self.build_problem(options=["first", "second", "third"], problem = self.build_problem(options=["first", "second", "third"],
correct_option="second") correct_option="second")
# Assert that we get the expected grades # Assert that we get the expected grades
...@@ -374,8 +374,8 @@ class StringResponseTest(ResponseTest): ...@@ -374,8 +374,8 @@ class StringResponseTest(ResponseTest):
hints = [("wisconsin", "wisc", "The state capital of Wisconsin is Madison"), hints = [("wisconsin", "wisc", "The state capital of Wisconsin is Madison"),
("minnesota", "minn", "The state capital of Minnesota is St. Paul")] ("minnesota", "minn", "The state capital of Minnesota is St. Paul")]
problem = self.build_problem(answer="Michigan", problem = self.build_problem(answer="Michigan",
case_sensitive=False, case_sensitive=False,
hints=hints) hints=hints)
# We should get a hint for Wisconsin # We should get a hint for Wisconsin
...@@ -543,7 +543,7 @@ class ChoiceResponseTest(ResponseTest): ...@@ -543,7 +543,7 @@ class ChoiceResponseTest(ResponseTest):
xml_factory_class = ChoiceResponseXMLFactory xml_factory_class = ChoiceResponseXMLFactory
def test_radio_group_grade(self): def test_radio_group_grade(self):
problem = self.build_problem(choice_type='radio', problem = self.build_problem(choice_type='radio',
choices=[False, True, False]) choices=[False, True, False])
# Check that we get the expected results # Check that we get the expected results
...@@ -601,17 +601,17 @@ class NumericalResponseTest(ResponseTest): ...@@ -601,17 +601,17 @@ class NumericalResponseTest(ResponseTest):
correct_responses = ["4", "4.0", "4.00"] correct_responses = ["4", "4.0", "4.00"]
incorrect_responses = ["", "3.9", "4.1", "0"] incorrect_responses = ["", "3.9", "4.1", "0"]
self.assert_multiple_grade(problem, correct_responses, incorrect_responses) self.assert_multiple_grade(problem, correct_responses, incorrect_responses)
def test_grade_decimal_tolerance(self): def test_grade_decimal_tolerance(self):
problem = self.build_problem(question_text="What is 2 + 2 approximately?", problem = self.build_problem(question_text="What is 2 + 2 approximately?",
explanation="The answer is 4", explanation="The answer is 4",
answer=4, answer=4,
tolerance=0.1) tolerance=0.1)
correct_responses = ["4.0", "4.00", "4.09", "3.91"] correct_responses = ["4.0", "4.00", "4.09", "3.91"]
incorrect_responses = ["", "4.11", "3.89", "0"] incorrect_responses = ["", "4.11", "3.89", "0"]
self.assert_multiple_grade(problem, correct_responses, incorrect_responses) self.assert_multiple_grade(problem, correct_responses, incorrect_responses)
def test_grade_percent_tolerance(self): def test_grade_percent_tolerance(self):
problem = self.build_problem(question_text="What is 2 + 2 approximately?", problem = self.build_problem(question_text="What is 2 + 2 approximately?",
explanation="The answer is 4", explanation="The answer is 4",
...@@ -642,6 +642,15 @@ class NumericalResponseTest(ResponseTest): ...@@ -642,6 +642,15 @@ class NumericalResponseTest(ResponseTest):
incorrect_responses = ["", "2.11", "1.89", "0"] incorrect_responses = ["", "2.11", "1.89", "0"]
self.assert_multiple_grade(problem, correct_responses, incorrect_responses) self.assert_multiple_grade(problem, correct_responses, incorrect_responses)
def test_exponential_answer(self):
problem = self.build_problem(question_text="What 5 * 10?",
explanation="The answer is 50",
answer="5e+1")
correct_responses = ["50", "50.0", "5e1", "5e+1", "50e0", "500e-1"]
incorrect_responses = ["", "3.9", "4.1", "0", "5.01e1"]
self.assert_multiple_grade(problem, correct_responses, incorrect_responses)
class CustomResponseTest(ResponseTest): class CustomResponseTest(ResponseTest):
from response_xml_factory import CustomResponseXMLFactory from response_xml_factory import CustomResponseXMLFactory
...@@ -667,7 +676,7 @@ class CustomResponseTest(ResponseTest): ...@@ -667,7 +676,7 @@ class CustomResponseTest(ResponseTest):
# The code can also set the global overall_message (str) # The code can also set the global overall_message (str)
# to pass a message that applies to the whole response # to pass a message that applies to the whole response
inline_script = textwrap.dedent(""" inline_script = textwrap.dedent("""
messages[0] = "Test Message" messages[0] = "Test Message"
overall_message = "Overall message" overall_message = "Overall message"
""") """)
problem = self.build_problem(answer=inline_script) problem = self.build_problem(answer=inline_script)
...@@ -687,14 +696,14 @@ class CustomResponseTest(ResponseTest): ...@@ -687,14 +696,14 @@ class CustomResponseTest(ResponseTest):
def test_function_code_single_input(self): def test_function_code_single_input(self):
# For function code, we pass in these arguments: # For function code, we pass in these arguments:
# #
# 'expect' is the expect attribute of the <customresponse> # 'expect' is the expect attribute of the <customresponse>
# #
# 'answer_given' is the answer the student gave (if there is just one input) # 'answer_given' is the answer the student gave (if there is just one input)
# or an ordered list of answers (if there are multiple inputs) # or an ordered list of answers (if there are multiple inputs)
#
# #
# The function should return a dict of the form #
# The function should return a dict of the form
# { 'ok': BOOL, 'msg': STRING } # { 'ok': BOOL, 'msg': STRING }
# #
script = textwrap.dedent(""" script = textwrap.dedent("""
...@@ -727,7 +736,7 @@ class CustomResponseTest(ResponseTest): ...@@ -727,7 +736,7 @@ class CustomResponseTest(ResponseTest):
def test_function_code_multiple_input_no_msg(self): def test_function_code_multiple_input_no_msg(self):
# Check functions also have the option of returning # Check functions also have the option of returning
# a single boolean value # a single boolean value
# If true, mark all the inputs correct # If true, mark all the inputs correct
# If false, mark all the inputs incorrect # If false, mark all the inputs incorrect
script = textwrap.dedent(""" script = textwrap.dedent("""
...@@ -736,7 +745,7 @@ class CustomResponseTest(ResponseTest): ...@@ -736,7 +745,7 @@ class CustomResponseTest(ResponseTest):
answer_given[1] == expect) answer_given[1] == expect)
""") """)
problem = self.build_problem(script=script, cfn="check_func", problem = self.build_problem(script=script, cfn="check_func",
expect="42", num_inputs=2) expect="42", num_inputs=2)
# Correct answer -- expect both inputs marked correct # Correct answer -- expect both inputs marked correct
...@@ -764,10 +773,10 @@ class CustomResponseTest(ResponseTest): ...@@ -764,10 +773,10 @@ class CustomResponseTest(ResponseTest):
# If the <customresponse> has multiple inputs associated with it, # If the <customresponse> has multiple inputs associated with it,
# the check function can return a dict of the form: # the check function can return a dict of the form:
# #
# {'overall_message': STRING, # {'overall_message': STRING,
# 'input_list': [{'ok': BOOL, 'msg': STRING}, ...] } # 'input_list': [{'ok': BOOL, 'msg': STRING}, ...] }
# #
# 'overall_message' is displayed at the end of the response # 'overall_message' is displayed at the end of the response
# #
# 'input_list' contains dictionaries representing the correctness # 'input_list' contains dictionaries representing the correctness
...@@ -784,7 +793,7 @@ class CustomResponseTest(ResponseTest): ...@@ -784,7 +793,7 @@ class CustomResponseTest(ResponseTest):
{'ok': check3, 'msg': 'Feedback 3'} ] } {'ok': check3, 'msg': 'Feedback 3'} ] }
""") """)
problem = self.build_problem(script=script, problem = self.build_problem(script=script,
cfn="check_func", num_inputs=3) cfn="check_func", num_inputs=3)
# Grade the inputs (one input incorrect) # Grade the inputs (one input incorrect)
...@@ -821,11 +830,11 @@ class CustomResponseTest(ResponseTest): ...@@ -821,11 +830,11 @@ class CustomResponseTest(ResponseTest):
check1 = (int(answer_given[0]) == 1) check1 = (int(answer_given[0]) == 1)
check2 = (int(answer_given[1]) == 2) check2 = (int(answer_given[1]) == 2)
check3 = (int(answer_given[2]) == 3) check3 = (int(answer_given[2]) == 3)
return {'ok': (check1 and check2 and check3), return {'ok': (check1 and check2 and check3),
'msg': 'Message text'} 'msg': 'Message text'}
""") """)
problem = self.build_problem(script=script, problem = self.build_problem(script=script,
cfn="check_func", num_inputs=3) cfn="check_func", num_inputs=3)
# Grade the inputs (one input incorrect) # Grade the inputs (one input incorrect)
...@@ -862,7 +871,7 @@ class CustomResponseTest(ResponseTest): ...@@ -862,7 +871,7 @@ class CustomResponseTest(ResponseTest):
# Expect that an exception gets raised when we check the answer # Expect that an exception gets raised when we check the answer
with self.assertRaises(Exception): with self.assertRaises(Exception):
problem.grade_answers({'1_2_1': '42'}) problem.grade_answers({'1_2_1': '42'})
def test_invalid_dict_exception(self): def test_invalid_dict_exception(self):
# Construct a script that passes back an invalid dict format # Construct a script that passes back an invalid dict format
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment