Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 6 additions & 5 deletions common/lib/capa/capa/responsetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -2433,7 +2434,7 @@ def setup_response(self):
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
Expand Down Expand Up @@ -2662,7 +2663,7 @@ def _check_student_inputs(self, numtolerance_inputs):

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)
Expand Down
5 changes: 4 additions & 1 deletion common/lib/capa/capa/tests/response_xml_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ def create_response_element(self, **kwargs):
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')
Expand Down
27 changes: 23 additions & 4 deletions common/lib/capa/capa/tests/test_responsetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,11 @@ def assert_answer_format(self, problem):
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."""
Expand Down Expand Up @@ -1070,6 +1068,27 @@ def test_grade_percent_tolerance(self):
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)
Expand Down
22 changes: 18 additions & 4 deletions common/lib/capa/capa/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,28 @@
#-----------------------------------------------------------------------------
#
# 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:
Expand All @@ -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


Expand Down
2 changes: 1 addition & 1 deletion common/lib/xmodule/xmodule/js/src/problem/edit.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down