Commit c34a81a8 by Felix Sun

Refactored formula response grader to expose formula-evaluating to outside world.

In preparation for adding hints to formula response.
parent 69380756
...@@ -1778,46 +1778,24 @@ class FormulaResponse(LoncapaResponse): ...@@ -1778,46 +1778,24 @@ class FormulaResponse(LoncapaResponse):
self.correct_answer, given, self.samples) self.correct_answer, given, self.samples)
return CorrectMap(self.answer_id, correctness) return CorrectMap(self.answer_id, correctness)
def check_formula(self, expected, given, samples): def hash_answers(self, answer, var_dict_list):
variables = samples.split('@')[0].split(',') """
numsamples = int(samples.split('@')[1].split('#')[1]) Takes in an answer and a list of dictionaries mapping variables to values.
sranges = zip(*map(lambda x: map(float, x.split(",")), Each dictionary represents a test case for the answer.
samples.split('@')[1].split('#')[0].split(':'))) Returns a tuple of formula evaluation results.
"""
ranges = dict(zip(variables, sranges)) out = []
for _ in range(numsamples): for var_dict in var_dict_list:
instructor_variables = self.strip_dict(dict(self.context))
student_variables = {}
# ranges give numerical ranges for testing
for var in ranges:
# TODO: allow specified ranges (i.e. integers and complex numbers) for random variables
value = random.uniform(*ranges[var])
instructor_variables[str(var)] = value
student_variables[str(var)] = value
# log.debug('formula: instructor_vars=%s, expected=%s' %
# (instructor_variables,expected))
# Call `evaluator` on the instructor's answer and get a number
instructor_result = evaluator(
instructor_variables, {},
expected, case_sensitive=self.case_sensitive
)
try: try:
# log.debug('formula: student_vars=%s, given=%s' % out.append(evaluator(
# (student_variables,given)) var_dict,
dict(),
# Call `evaluator` on the student's answer; look for exceptions answer,
student_result = evaluator( cs=self.case_sensitive,
student_variables, ))
{},
given,
case_sensitive=self.case_sensitive
)
except UndefinedVariable as uv: except UndefinedVariable as uv:
log.debug( log.debug(
'formularesponse: undefined variable in given=%s', 'formularesponse: undefined variable in formula=%s' % answer)
given
)
raise StudentInputError( raise StudentInputError(
"Invalid input: " + uv.message + " not permitted in answer" "Invalid input: " + uv.message + " not permitted in answer"
) )
...@@ -1840,15 +1818,43 @@ class FormulaResponse(LoncapaResponse): ...@@ -1840,15 +1818,43 @@ class FormulaResponse(LoncapaResponse):
# If non-factorial related ValueError thrown, handle it the same as any other Exception # If non-factorial related ValueError thrown, handle it the same as any other Exception
log.debug('formularesponse: error {0} in formula'.format(ve)) log.debug('formularesponse: error {0} in formula'.format(ve))
raise StudentInputError("Invalid input: Could not parse '%s' as a formula" % raise StudentInputError("Invalid input: Could not parse '%s' as a formula" %
cgi.escape(given)) cgi.escape(answer))
except Exception as err: except Exception as err:
# traceback.print_exc() # traceback.print_exc()
log.debug('formularesponse: error %s in formula', err) log.debug('formularesponse: error %s in formula', err)
raise StudentInputError("Invalid input: Could not parse '%s' as a formula" % raise StudentInputError("Invalid input: Could not parse '%s' as a formula" %
cgi.escape(given)) cgi.escape(answer))
return tuple(out)
def randomize_variables(self, samples):
"""
Returns a list of dictionaries mapping variables to random values in range,
as expected by hash_answers.
"""
variables = samples.split('@')[0].split(',')
numsamples = int(samples.split('@')[1].split('#')[1])
sranges = zip(*map(lambda x: map(float, x.split(",")),
samples.split('@')[1].split('#')[0].split(':')))
ranges = dict(zip(variables, sranges))
out = []
for i in range(numsamples):
var_dict = {}
# ranges give numerical ranges for testing
for var in ranges:
# TODO: allow specified ranges (i.e. integers and complex numbers) for random variables
value = random.uniform(*ranges[var])
var_dict[str(var)] = value
out.append(var_dict)
return out
def check_formula(self, expected, given, samples):
var_dict_list = self.randomize_variables(samples)
student_result = self.hash_answers(given, var_dict_list)
instructor_result = self.hash_answers(expected, var_dict_list)
# No errors in student's response--actually test for correctness for i in xrange(len(instructor_result)):
if not compare_with_tolerance(student_result, instructor_result, self.tolerance): if not compare_with_tolerance(student_result[i], instructor_result[i], self.tolerance):
return "incorrect" return "incorrect"
return "correct" return "correct"
......
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