Commit 1347645e by polesye Committed by Alexander Kryklia

BLD-434: Add fix for negative exponents.

Set default tolerance to 1e-3% for all responsetypes.
parent cbe97d6f
...@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes, ...@@ -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 in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected. 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. 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 Common: Add feature flags to allow developer use of pure XBlocks
......
...@@ -35,7 +35,8 @@ from calc import evaluator, UndefinedVariable ...@@ -35,7 +35,8 @@ from calc import evaluator, UndefinedVariable
from . import correctmap from . import correctmap
from datetime import datetime from datetime import datetime
from pytz import UTC 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 import etree
from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME? from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME?
import capa.xqueue_interface as xqueue_interface import capa.xqueue_interface as xqueue_interface
...@@ -832,7 +833,7 @@ class NumericalResponse(LoncapaResponse): ...@@ -832,7 +833,7 @@ class NumericalResponse(LoncapaResponse):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.correct_answer = '' self.correct_answer = ''
self.tolerance = '0' # Default value self.tolerance = default_tolerance
super(NumericalResponse, self).__init__(*args, **kwargs) super(NumericalResponse, self).__init__(*args, **kwargs)
def setup_response(self): def setup_response(self):
...@@ -1877,7 +1878,7 @@ class FormulaResponse(LoncapaResponse): ...@@ -1877,7 +1878,7 @@ class FormulaResponse(LoncapaResponse):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.correct_answer = '' self.correct_answer = ''
self.samples = '' self.samples = ''
self.tolerance = '1e-5' # Default value self.tolerance = default_tolerance
self.case_sensitive = False self.case_sensitive = False
super(FormulaResponse, self).__init__(*args, **kwargs) super(FormulaResponse, self).__init__(*args, **kwargs)
...@@ -2433,7 +2434,7 @@ class ChoiceTextResponse(LoncapaResponse): ...@@ -2433,7 +2434,7 @@ class ChoiceTextResponse(LoncapaResponse):
input_name = child.get('name') input_name = child.get('name')
# Contextualize the tolerance to value. # Contextualize the tolerance to value.
tolerance = contextualize_text( tolerance = contextualize_text(
child.get('tolerance', '0'), child.get('tolerance', default_tolerance),
context context
) )
# Add the answer and tolerance information for the current # Add the answer and tolerance information for the current
...@@ -2662,7 +2663,7 @@ class ChoiceTextResponse(LoncapaResponse): ...@@ -2662,7 +2663,7 @@ class ChoiceTextResponse(LoncapaResponse):
correct_ans = params['answer'] correct_ans = params['answer']
# Set the tolerance to '0' if it was not specified in the xml # 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 # Make sure that the staff answer is a valid number
try: try:
correct_ans = complex(correct_ans) correct_ans = complex(correct_ans)
......
...@@ -182,6 +182,9 @@ class NumericalResponseXMLFactory(ResponseXMLFactory): ...@@ -182,6 +182,9 @@ class NumericalResponseXMLFactory(ResponseXMLFactory):
response_element = etree.Element('numericalresponse') response_element = etree.Element('numericalresponse')
if answer: if answer:
if isinstance(answer, float):
response_element.set('answer', repr(answer))
else:
response_element.set('answer', str(answer)) response_element.set('answer', str(answer))
if tolerance: if tolerance:
......
...@@ -56,13 +56,11 @@ class ResponseTest(unittest.TestCase): ...@@ -56,13 +56,11 @@ class ResponseTest(unittest.TestCase):
def assert_multiple_grade(self, problem, correct_answers, incorrect_answers): def assert_multiple_grade(self, problem, correct_answers, incorrect_answers):
for input_str in correct_answers: for input_str in correct_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, 'correct', self.assertEqual(result, 'correct')
msg="%s should be marked correct" % str(input_str))
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))
def _get_random_number_code(self): def _get_random_number_code(self):
"""Returns code to be used to generate a random result.""" """Returns code to be used to generate a random result."""
...@@ -1070,6 +1068,27 @@ class NumericalResponseTest(ResponseTest): ...@@ -1070,6 +1068,27 @@ class NumericalResponseTest(ResponseTest):
incorrect_responses = ["", "4.5", "3.5", "0"] incorrect_responses = ["", "4.5", "3.5", "0"]
self.assert_multiple_grade(problem, correct_responses, incorrect_responses) 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): def test_grade_with_script(self):
script_text = "computed_response = math.sqrt(4)" script_text = "computed_response = math.sqrt(4)"
problem = self.build_problem(answer="$computed_response", script=script_text) problem = self.build_problem(answer="$computed_response", script=script_text)
......
...@@ -4,16 +4,28 @@ from cmath import isinf ...@@ -4,16 +4,28 @@ from cmath import isinf
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# #
# Utility functions used in CAPA responsetypes # Utility functions used in CAPA responsetypes
default_tolerance = '0.001%'
def compare_with_tolerance(v1, v2, tol): def compare_with_tolerance(v1, v2, tol=default_tolerance):
''' Compare v1 to v2 with maximum tolerance tol '''
Compare v1 to v2 with maximum tolerance tol.
tol is relative if it ends in %; otherwise, it is absolute tol is relative if it ends in %; otherwise, it is absolute
- v1 : student result (number) - v1 : student result (float complex number)
- v2 : instructor result (number) - v2 : instructor result (float complex number)
- tol : tolerance (string representing a 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('%') relative = tol.endswith('%')
if relative: if relative:
...@@ -28,6 +40,8 @@ def compare_with_tolerance(v1, v2, tol): ...@@ -28,6 +40,8 @@ def compare_with_tolerance(v1, v2, tol):
# `inf <= inf` which is a fail. Instead, compare directly. # `inf <= inf` which is a fail. Instead, compare directly.
return v1 == v2 return v1 == v2
else: 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 return abs(v1 - v2) <= tolerance
......
...@@ -3,7 +3,7 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor ...@@ -3,7 +3,7 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
@multipleChoiceTemplate : "( ) incorrect\n( ) incorrect\n(x) correct\n" @multipleChoiceTemplate : "( ) incorrect\n( ) incorrect\n(x) correct\n"
@checkboxChoiceTemplate: "[x] correct\n[ ] incorrect\n[x] correct\n" @checkboxChoiceTemplate: "[x] correct\n[ ] incorrect\n[x] correct\n"
@stringInputTemplate: "= answer\n" @stringInputTemplate: "= answer\n"
@numberInputTemplate: "= answer +- x%\n" @numberInputTemplate: "= answer +- 0.001%\n"
@selectTemplate: "[[incorrect, (correct), incorrect]]\n" @selectTemplate: "[[incorrect, (correct), incorrect]]\n"
@headerTemplate: "Header\n=====\n" @headerTemplate: "Header\n=====\n"
@explanationTemplate: "[explanation]\nShort explanation\n[explanation]\n" @explanationTemplate: "[explanation]\nShort explanation\n[explanation]\n"
......
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