def grade(element_html, element_index, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers_name') # Get weight weight = pl.get_integer_attrib(element, 'weight', 1) # Get true answer (if it does not exist, create no grade - leave it # up to the question code) a_tru = data['correct_answers'].get(name, None) if a_tru is None: return data # Get submitted answer (if it does not exist, score is zero) a_sub = data['submitted_answers'].get(name, None) if a_sub is None: data['partial_scores'][name] = {'score': 0, 'weight': weight} return data # Get method of comparison, with relabs as default comparison = pl.get_string_attrib(element, 'comparison', 'relabs') # Compare submitted answer with true answer if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-5) atol = pl.get_float_attrib(element, 'atol', 1e-8) correct = pl.is_correct_scalar_ra(a_sub, a_tru, rtol, atol) elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) eps_digits = pl.get_integer_attrib(element, 'eps_digits', 3) correct = pl.is_correct_scalar_sf(a_sub, a_tru, digits, eps_digits) elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) eps_digits = pl.get_integer_attrib(element, 'eps_digits', 3) correct = pl.is_correct_scalar_dd(a_sub, a_tru, digits, eps_digits) else: raise ValueError('method of comparison "%s" is not valid' % comparison) if correct: data['partial_scores'][name] = {'score': 1, 'weight': weight} else: data['partial_scores'][name] = {'score': 0, 'weight': weight} return data
def grade(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') # Get weight weight = pl.get_integer_attrib(element, 'weight', WEIGHT_DEFAULT) # Get true answer (if it does not exist, create no grade - leave it # up to the question code) a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: return # Get submitted answer (if it does not exist, score is zero) a_sub = data['submitted_answers'].get(name, None) if a_sub is None: data['partial_scores'][name] = {'score': 0, 'weight': weight} return # If submitted answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) # Cast both submitted and true answers as np.float64, because... # # If the method of comparison is relabs (i.e., using relative and # absolute tolerance) then np.allclose is applied to check if the # submitted and true answers are the same. If either answer is an # integer outside the range of int64... # # https://docs.scipy.org/doc/numpy-1.13.0/user/basics.types.html # # ...then numpy throws this error: # # TypeError: ufunc 'isfinite' not supported for the input types, and # the inputs could not be safely coerced to any supported types # according to the casting rule ''safe'' # # Casting as np.float64 avoids this error. This is reasonable in any case, # because <pl-number-input> accepts double-precision floats, not ints. # if np.iscomplexobj(a_sub) or np.iscomplexobj(a_tru): a_sub = np.complex128(a_sub) a_tru = np.complex128(a_tru) else: a_sub = np.float64(a_sub) a_tru = np.float64(a_tru) # Get method of comparison, with relabs as default comparison = pl.get_string_attrib(element, 'comparison', COMPARISON_DEFAULT) # Compare submitted answer with true answer if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', RTOL_DEFAULT) atol = pl.get_float_attrib(element, 'atol', ATOL_DEFAULT) correct = pl.is_correct_scalar_ra(a_sub, a_tru, rtol, atol) elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', DIGITS_DEFAULT) correct = pl.is_correct_scalar_sf(a_sub, a_tru, digits) elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', DIGITS_DEFAULT) correct = pl.is_correct_scalar_dd(a_sub, a_tru, digits) else: raise ValueError('method of comparison "%s" is not valid' % comparison) if correct: data['partial_scores'][name] = {'score': 1, 'weight': weight} else: data['partial_scores'][name] = {'score': 0, 'weight': weight}
def grade(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') allow_partial_credit = pl.get_boolean_attrib(element, 'allow-partial-credit', False) # Get weight weight = pl.get_integer_attrib(element, 'weight', 1) # Get method of comparison, with relabs as default comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) else: raise ValueError('method of comparison "%s" is not valid' % comparison) # Get true answer (if it does not exist, create no grade - leave it # up to the question code) a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: return # Wrap true answer in ndarray (if it already is one, this does nothing) a_tru = np.array(a_tru) # Throw an error if true answer is not a 2D numpy array if a_tru.ndim != 2: raise ValueError('true answer must be a 2D array') else: m, n = np.shape(a_tru) number_of_correct = 0 feedback = {} for i in range(m): for j in range(n): each_entry_name = name + str(n * i + j + 1) a_sub = data['submitted_answers'].get(each_entry_name, None) # Get submitted answer (if it does not exist, score is zero) if a_sub is None: data['partial_scores'][name] = {'score': 0, 'weight': weight} return # If submitted answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) # Compare submitted answer with true answer if comparison == 'relabs': correct = pl.is_correct_scalar_ra(a_sub, a_tru[i, j], rtol, atol) elif comparison == 'sigfig': correct = pl.is_correct_scalar_sf(a_sub, a_tru[i, j], digits) elif comparison == 'decdig': correct = pl.is_correct_scalar_dd(a_sub, a_tru[i, j], digits) if correct: number_of_correct += 1 feedback.update({each_entry_name: 'correct'}) else: feedback.update({each_entry_name: 'incorrect'}) if number_of_correct == m * n: data['partial_scores'][name] = {'score': 1, 'weight': weight} else: if not allow_partial_credit: score_value = 0 else: score_value = number_of_correct / (m * n) data['partial_scores'][name] = { 'score': score_value, 'weight': weight, 'feedback': feedback }
def grade(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') allow_partial_credit = pl.get_boolean_attrib(element, 'allow-partial-credit', False) # Get weight weight = pl.get_integer_attrib(element, 'weight', 1) # Get method of comparison, with relabs as default comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) else: raise ValueError('method of comparison "%s" is not valid' % comparison) # Get true answer (if it does not exist, create no grade - leave it # up to the question code) a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: return # Wrap true answer in ndarray (if it already is one, this does nothing) a_tru = np.array(a_tru) # Throw an error if true answer is not a 2D numpy array if a_tru.ndim != 2: raise ValueError('true answer must be a 2D array') else: m, n = np.shape(a_tru) number_of_correct = 0 feedback = {} for i in range(m): for j in range(n): each_entry_name = name + str(n * i + j + 1) a_sub = data['submitted_answers'].get(each_entry_name, None) # Get submitted answer (if it does not exist, score is zero) if a_sub is None: data['partial_scores'][name] = {'score': 0, 'weight': weight} return # If submitted answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) # Compare submitted answer with true answer if comparison == 'relabs': correct = pl.is_correct_scalar_ra(a_sub, a_tru[i, j], rtol, atol) elif comparison == 'sigfig': correct = pl.is_correct_scalar_sf(a_sub, a_tru[i, j], digits) elif comparison == 'decdig': correct = pl.is_correct_scalar_dd(a_sub, a_tru[i, j], digits) if correct: number_of_correct += 1 feedback.update({each_entry_name: 'correct'}) else: feedback.update({each_entry_name: 'incorrect'}) if number_of_correct == m * n: data['partial_scores'][name] = {'score': 1, 'weight': weight} else: if not allow_partial_credit: score_value = 0 else: score_value = number_of_correct / (m * n) data['partial_scores'][name] = {'score': score_value, 'weight': weight, 'feedback': feedback}