From 1347645ef0acf1c58e68d4d70ca91099a03ae715 Mon Sep 17 00:00:00 2001 From: polesye Date: Fri, 20 Dec 2013 21:40:10 +0200 Subject: [PATCH] BLD-434: Add fix for negative exponents. Set default tolerance to 1e-3% for all responsetypes. --- CHANGELOG.rst | 2 ++ common/lib/capa/capa/responsetypes.py | 11 ++++---- .../capa/capa/tests/response_xml_factory.py | 5 +++- .../lib/capa/capa/tests/test_responsetypes.py | 27 ++++++++++++++++--- common/lib/capa/capa/util.py | 22 ++++++++++++--- .../xmodule/js/src/problem/edit.coffee | 2 +- 6 files changed, 54 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 645984cb97..d65131f6cd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes, in roughly chronological order, most recent first. Add your entries at or near the top. Include a label indicating the component affected. +Blades: Fix comparison of float numbers. BLD-434. + Blades: Allow regexp strings as the correct answer to a string response question. BLD-475. Common: Add feature flags to allow developer use of pure XBlocks diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 971f89674b..ac19fd1907 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -35,7 +35,8 @@ from calc import evaluator, UndefinedVariable from . import correctmap from datetime import datetime from pytz import UTC -from .util import compare_with_tolerance, contextualize_text, convert_files_to_filenames, is_list_of_files, find_with_default +from .util import (compare_with_tolerance, contextualize_text, convert_files_to_filenames, + is_list_of_files, find_with_default, default_tolerance) from lxml import etree from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME? import capa.xqueue_interface as xqueue_interface @@ -832,7 +833,7 @@ class NumericalResponse(LoncapaResponse): def __init__(self, *args, **kwargs): self.correct_answer = '' - self.tolerance = '0' # Default value + self.tolerance = default_tolerance super(NumericalResponse, self).__init__(*args, **kwargs) def setup_response(self): @@ -1877,7 +1878,7 @@ class FormulaResponse(LoncapaResponse): def __init__(self, *args, **kwargs): self.correct_answer = '' self.samples = '' - self.tolerance = '1e-5' # Default value + self.tolerance = default_tolerance self.case_sensitive = False super(FormulaResponse, self).__init__(*args, **kwargs) @@ -2433,7 +2434,7 @@ class ChoiceTextResponse(LoncapaResponse): input_name = child.get('name') # Contextualize the tolerance to value. tolerance = contextualize_text( - child.get('tolerance', '0'), + child.get('tolerance', default_tolerance), context ) # Add the answer and tolerance information for the current @@ -2662,7 +2663,7 @@ class ChoiceTextResponse(LoncapaResponse): correct_ans = params['answer'] # Set the tolerance to '0' if it was not specified in the xml - tolerance = params.get('tolerance', '0') + tolerance = params.get('tolerance', default_tolerance) # Make sure that the staff answer is a valid number try: correct_ans = complex(correct_ans) diff --git a/common/lib/capa/capa/tests/response_xml_factory.py b/common/lib/capa/capa/tests/response_xml_factory.py index 735fa20288..dab1af32c7 100644 --- a/common/lib/capa/capa/tests/response_xml_factory.py +++ b/common/lib/capa/capa/tests/response_xml_factory.py @@ -182,7 +182,10 @@ class NumericalResponseXMLFactory(ResponseXMLFactory): response_element = etree.Element('numericalresponse') if answer: - response_element.set('answer', str(answer)) + if isinstance(answer, float): + response_element.set('answer', repr(answer)) + else: + response_element.set('answer', str(answer)) if tolerance: responseparam_element = etree.SubElement(response_element, 'responseparam') diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index dec3b7bad7..8a654025a4 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -56,13 +56,11 @@ class ResponseTest(unittest.TestCase): def assert_multiple_grade(self, problem, correct_answers, incorrect_answers): for input_str in correct_answers: result = problem.grade_answers({'1_2_1': input_str}).get_correctness('1_2_1') - self.assertEqual(result, 'correct', - msg="%s should be marked correct" % str(input_str)) + self.assertEqual(result, 'correct') for input_str in incorrect_answers: result = problem.grade_answers({'1_2_1': input_str}).get_correctness('1_2_1') - self.assertEqual(result, 'incorrect', - msg="%s should be marked incorrect" % str(input_str)) + self.assertEqual(result, 'incorrect') def _get_random_number_code(self): """Returns code to be used to generate a random result.""" @@ -1070,6 +1068,27 @@ class NumericalResponseTest(ResponseTest): incorrect_responses = ["", "4.5", "3.5", "0"] self.assert_multiple_grade(problem, correct_responses, incorrect_responses) + def test_floats(self): + """ + Default tolerance for all responsetypes is 1e-3%. + """ + problem_setup = [ + #[given_asnwer, [list of correct responses], [list of incorrect responses]] + [1, ["1"], ["1.1"],], + [2.0, ["2.0"], ["1.0"],], + [4, ["4.0", "4.00004"], ["4.00005"]], + [0.00016, ["1.6*10^-4"], [""]], + [0.000016, ["1.6*10^-5"], ["0.000165"]], + [1.9e24, ["1.9*10^24"], ["1.9001*10^24"]], + [2e-15, ["2*10^-15"], [""]], + [3141592653589793238., ["3141592653589793115."], [""]], + [0.1234567, ["0.123456", "0.1234561"], ["0.123451"]], + [1e-5, ["1e-5", "1.0e-5"], ["-1e-5", "2*1e-5"]], + ] + for given_answer, correct_responses, incorrect_responses in problem_setup: + problem = self.build_problem(answer=given_answer) + self.assert_multiple_grade(problem, correct_responses, incorrect_responses) + def test_grade_with_script(self): script_text = "computed_response = math.sqrt(4)" problem = self.build_problem(answer="$computed_response", script=script_text) diff --git a/common/lib/capa/capa/util.py b/common/lib/capa/capa/util.py index 433e99171d..77ab4caae4 100644 --- a/common/lib/capa/capa/util.py +++ b/common/lib/capa/capa/util.py @@ -4,16 +4,28 @@ from cmath import isinf #----------------------------------------------------------------------------- # # Utility functions used in CAPA responsetypes +default_tolerance = '0.001%' -def compare_with_tolerance(v1, v2, tol): - ''' Compare v1 to v2 with maximum tolerance tol +def compare_with_tolerance(v1, v2, tol=default_tolerance): + ''' + Compare v1 to v2 with maximum tolerance tol. + tol is relative if it ends in %; otherwise, it is absolute - - v1 : student result (number) - - v2 : instructor result (number) + - v1 : student result (float complex number) + - v2 : instructor result (float complex number) - tol : tolerance (string representing a number) + Default tolerance of 1e-3% is added to compare two floats for near-equality + (to handle machine representation errors). + It is relative, as the acceptable difference between two floats depends on the magnitude of the floats. + (http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/) + Examples: + In [183]: 0.000016 - 1.6*10**-5 + Out[183]: -3.3881317890172014e-21 + In [212]: 1.9e24 - 1.9*10**24 + Out[212]: 268435456.0 ''' relative = tol.endswith('%') if relative: @@ -28,6 +40,8 @@ def compare_with_tolerance(v1, v2, tol): # `inf <= inf` which is a fail. Instead, compare directly. return v1 == v2 else: + # v1 and v2 are, in general, complex numbers: + # there are some notes about backward compatibility issue: see responsetypes.get_staff_ans()). return abs(v1 - v2) <= tolerance diff --git a/common/lib/xmodule/xmodule/js/src/problem/edit.coffee b/common/lib/xmodule/xmodule/js/src/problem/edit.coffee index bc29bacec5..a1c008d5ca 100644 --- a/common/lib/xmodule/xmodule/js/src/problem/edit.coffee +++ b/common/lib/xmodule/xmodule/js/src/problem/edit.coffee @@ -3,7 +3,7 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor @multipleChoiceTemplate : "( ) incorrect\n( ) incorrect\n(x) correct\n" @checkboxChoiceTemplate: "[x] correct\n[ ] incorrect\n[x] correct\n" @stringInputTemplate: "= answer\n" - @numberInputTemplate: "= answer +- x%\n" + @numberInputTemplate: "= answer +- 0.001%\n" @selectTemplate: "[[incorrect, (correct), incorrect]]\n" @headerTemplate: "Header\n=====\n" @explanationTemplate: "[explanation]\nShort explanation\n[explanation]\n"