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', 1) # 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 integers. a_tru = int(a_tru) a_sub = int(a_sub) if a_tru == a_sub: data['partial_scores'][name] = {'score': 1, 'weight': weight} else: data['partial_scores'][name] = {'score': 0, 'weight': weight}
def test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', 1) result = random.choices(['correct', 'incorrect', 'invalid'], [5, 5, 1])[0] if result == 'correct': data['raw_submitted_answers'][name] = str(pl.from_json(data['correct_answers'][name])) data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = str(pl.from_json(data['correct_answers'][name])) + ' + {:d}'.format(random.randint(1, 100)) data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': invalid_type = random.choice(['float', 'complex', 'expression', 'function', 'variable', 'syntax', 'escape', 'comment']) if invalid_type == 'float': data['raw_submitted_answers'][name] = 'x + 1.234' s = 'Your answer contains the floating-point number ' + str(1.234) + '. ' s += 'All numbers must be expressed as integers (or ratios of integers). ' s += '<br><br><pre>' + phs.point_to_error('x + 1.234', 4) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'complex': data['raw_submitted_answers'][name] = 'x + (1+2j)' s = 'Your answer contains the complex number ' + str(2j) + '. ' s += 'All numbers must be expressed as integers (or ratios of integers). ' s += '<br><br><pre>' + phs.point_to_error('x + (1+2j)', 7) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'expression': data['raw_submitted_answers'][name] = '1 and 0' s = 'Your answer has an invalid expression. ' s += '<br><br><pre>' + phs.point_to_error('1 and 0', 0) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'function': data['raw_submitted_answers'][name] = 'atan(x)' s = 'Your answer calls an invalid function "' + 'atan' + '". ' s += '<br><br><pre>' + phs.point_to_error('atan(x)', 0) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'variable': data['raw_submitted_answers'][name] = 'x + y' s = 'Your answer refers to an invalid variable "' + 'y' + '". ' s += '<br><br><pre>' + phs.point_to_error('x + y', 4) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'syntax': data['raw_submitted_answers'][name] = 'x +* 1' s = 'Your answer has a syntax error. ' s += '<br><br><pre>' + phs.point_to_error('x +* 1', 4) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'escape': data['raw_submitted_answers'][name] = 'x + 1\\n' s = 'Your answer must not contain the character "\\". ' s += '<br><br><pre>' + phs.point_to_error('x + 1\\n', 5) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'comment': data['raw_submitted_answers'][name] = 'x # some text' s = 'Your answer must not contain the character "#". ' s += '<br><br><pre>' + phs.point_to_error('x # some text', 2) + '</pre>' data['format_errors'][name] = s else: raise Exception('invalid invalid_type: %s' % invalid_type) else: raise Exception('invalid result: %s' % result)
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', 1) # 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') # 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) # Wrap submitted answer in an ndarray (if it's already one, this does nothing) a_sub = np.array(a_sub) # If true and submitted answers have different shapes, score is zero if not (a_sub.shape == a_tru.shape): data['partial_scores'][name] = {'score': 0, 'weight': weight} return # 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-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) correct = pl.is_correct_ndarray2D_ra(a_sub, a_tru, rtol, atol) elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) correct = pl.is_correct_ndarray2D_sf(a_sub, a_tru, digits) elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) correct = pl.is_correct_ndarray2D_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 test(element_html, element_index, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', 1) allow_blank = pl.get_string_attrib(element, 'allow-blank', False) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) if allow_blank: # no invalid answer implemented when allow-blank="true" result = random.choices(['correct', 'incorrect'], [5, 5])[0] else: result = random.choices(['correct', 'incorrect', 'invalid'], [5, 5, 1])[0] if result == 'correct': data['raw_submitted_answers'][name] = a_tru data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = a_tru + str((random.randint(1, 11) * random.choice([-1, 1]))) data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': data['raw_submitted_answers'][name] = '' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result)
def test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', 1) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) result = random.choices(['correct', 'incorrect', 'invalid'], [5, 5, 1])[0] if result == 'correct': data['raw_submitted_answers'][name] = str(a_tru) data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = str(a_tru + (random.randint(1, 11) * random.choice([-1, 1]))) data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': # FIXME: add more invalid expressions, make text of format_errors # correct, and randomize if random.choice([True, False]): data['raw_submitted_answers'][name] = '1 + 2' else: data['raw_submitted_answers'][name] = '3.4' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result)
def test(element_html, element_index, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', 1) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) result = random.choices(['correct', 'incorrect', 'invalid'], [5, 5, 1])[0] if result == 'correct': data['raw_submitted_answers'][name] = str(a_tru) data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = str(a_tru + (random.randint(1, 11) * random.choice([-1, 1]))) data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': # FIXME: add more invalid expressions, make text of format_errors # correct, and randomize if random.choice([True, False]): data['raw_submitted_answers'][name] = '1 + 2' else: data['raw_submitted_answers'][name] = '3.4' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result)
def test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', 1) allow_blank = pl.get_string_attrib(element, 'allow-blank', False) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) if allow_blank: # no invalid answer implemented when allow-blank="true" result = random.choices(['correct', 'incorrect'], [5, 5])[0] else: result = random.choices(['correct', 'incorrect', 'invalid'], [5, 5, 1])[0] if result == 'correct': data['raw_submitted_answers'][name] = a_tru data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = a_tru + str((random.randint(1, 11) * random.choice([-1, 1]))) data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': data['raw_submitted_answers'][name] = '' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result)
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) # Get the number of digits to output digits = pl.get_integer_attrib(element, 'digits', 2) # Get the presentation type presentation_type = pl.get_string_attrib(element, 'presentation-type', 'f') var_name = pl.get_string_attrib(element, 'params-name') # Get value of variable, raising exception if variable does not exist var_data = data['params'].get(var_name, None) if var_data is None: raise Exception('No value in data["params"] for variable %s in pl-matrix-latex element' % var_name) # If the variable is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) var_data = pl.from_json(var_data) if not np.isscalar(var_data): var_data = np.array(var_data) # Check if numpy array type is numeric (integer, float or complex) if np.issubdtype(var_data.dtype, np.number): # Check shape of variable if var_data.ndim != 2: raise Exception('Value in data["params"] for variable %s in pl-matrix-latex element must be 2D array or scalar' % var_name) else: raise Exception('Value in data["params"] for variable %s in pl-matrix-latex element must be numeric' % var_name) # Create string for latex matrix format html = pl.latex_from_2darray(var_data, presentation_type=presentation_type, digits=digits) return html
def test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', WEIGHT_DEFAULT) allow_blank = pl.get_string_attrib(element, 'allow-blank', ALLOW_BLANK_DEFAULT) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) result = data['test_type'] if result == 'invalid' and allow_blank: # We can't have an invalid submission with allow_blank, so just test correct result = 'correct' if result == 'correct': data['raw_submitted_answers'][name] = a_tru data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = a_tru + str( (random.randint(1, 11) * random.choice([-1, 1]))) data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': data['raw_submitted_answers'][name] = '' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result)
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 = 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 # Parse both correct and submitted answer (will throw an error on fail). variables = get_variables_list( pl.get_string_attrib(element, 'variables', None)) if isinstance(a_tru, str): a_tru = phs.convert_string_to_sympy(a_tru, variables) a_sub = phs.convert_string_to_sympy(a_sub, variables) # Check equality correct = a_tru.equals(a_sub) if correct: data['partial_scores'][name] = {'score': 1, 'weight': weight} else: data['partial_scores'][name] = {'score': 0, 'weight': weight}
def parse(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') # Get true answer a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: return a_tru = np.array(a_tru) if a_tru.ndim != 2: raise ValueError('true answer must be a 2D array') else: m, n = np.shape(a_tru) A = np.empty([m, n]) # Create an array for the submitted answer to be stored in data['submitted_answer'][name] # used for display in the answer and submission panels # Also creates invalid error messages invalid_format = False 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) if a_sub is None: data['submitted_answers'][each_entry_name] = None data['format_errors'][ each_entry_name] = '(No submitted answer)' invalid_format = True elif not a_sub: data['submitted_answers'][each_entry_name] = None data['format_errors'][ each_entry_name] = '(Invalid blank entry)' invalid_format = True else: a_sub_parsed = pl.string_to_number(a_sub, allow_complex=False) if a_sub_parsed is None: data['submitted_answers'][each_entry_name] = None data['format_errors'][each_entry_name] = '(Invalid format)' invalid_format = True elif not np.isfinite(a_sub_parsed): data['submitted_answers'][each_entry_name] = None data['format_errors'][ each_entry_name] = '(Invalid format - not finite)' invalid_format = True else: data['submitted_answers'][each_entry_name] = pl.to_json( a_sub_parsed) A[i, j] = a_sub_parsed if invalid_format: with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: data['format_errors'][name] = chevron.render( f, { 'format_error': True }).strip() data['submitted_answers'][name] = None else: data['submitted_answers'][name] = pl.to_json(A)
def graphviz_from_inc_matrix(element, data): # Get attributes engine = pl.get_string_attrib(element, 'engine', pl_graph.ENGINE_DEFAULT) input_param = pl.get_string_attrib(element, 'params-name-matrix', pl_graph.PARAMS_NAME_MATRIX_DEFAULT) input_label = pl.get_string_attrib(element, 'params-name-labels', pl_graph.PARAMS_NAME_LABELS_DEFAULT) mat = np.array(pl.from_json(data['params'][input_param])) show_weights = pl.get_boolean_attrib( element, 'weights', pl_graph.WEIGHTS_DEFAULT ) # by default display weights for stochastic matrices digits = pl.get_integer_attrib( element, 'weights-digits', pl_graph.WEIGHTS_DIGITS_DEFAULT ) # if displaying weights how many digits to round to presentation_type = pl.get_string_attrib( element, 'weights-presentation-type', pl_graph.WEIGHTS_PRESENTATION_TYPE_DEFAULT).lower() label = None if input_label is not None: label = np.array(pl.from_json(data['params'][input_label])) # Sanity check if label is not None and label.shape[0] != mat.shape[0]: raise Exception( 'Dimensionality of the label is not consistent with the dimensionality of the matrix' ) if label is None: label = range(mat.shape[1]) G = pygraphviz.AGraph(directed=True) for node in label: G.add_node(node) edges, nodes = mat.shape for e in range(edges): out_node = np.where(mat[e] == -1)[0][0] in_node = np.where(mat[e] == 1)[0][0] G.add_edge(out_node, in_node) return G.string()
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 = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: return data # Convert true answer to numpy 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') # 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 # Convert submitted answer to numpy a_sub = np.array(a_sub) # If true and submitted answers have different shapes, score is zero if not (a_sub.shape == a_tru.shape): 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_ndarray2D_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_ndarray2D_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_ndarray2D_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 parse(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') # Get true answer a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: return a_tru = np.array(a_tru) if a_tru.ndim != 2: raise ValueError('true answer must be a 2D array') else: m, n = np.shape(a_tru) A = np.empty([m, n]) # Create an array for the submitted answer to be stored in data['submitted_answer'][name] # used for display in the answer and submission panels # Also creates invalid error messages invalid_format = False 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) if a_sub is None: data['submitted_answers'][each_entry_name] = None data['format_errors'][ each_entry_name] = '(No submitted answer)' invalid_format = True elif not a_sub: data['submitted_answers'][each_entry_name] = None data['format_errors'][ each_entry_name] = '(Invalid blank entry)' invalid_format = True else: a_sub_parsed = pl.string_to_number(a_sub, allow_complex=False) if a_sub_parsed is None: data['submitted_answers'][each_entry_name] = None data['format_errors'][each_entry_name] = '(Invalid format)' invalid_format = True elif not np.isfinite(a_sub_parsed): data['submitted_answers'][each_entry_name] = None data['format_errors'][ each_entry_name] = '(Invalid format - not finite)' invalid_format = True else: data['submitted_answers'][each_entry_name] = pl.to_json( a_sub_parsed) A[i, j] = a_sub_parsed if invalid_format: data['format_errors'][ name] = 'At least one of the entries has invalid format (empty entries or not a double precision floating point number)' data['submitted_answers'][name] = None else: data['submitted_answers'][name] = pl.to_json(A)
def test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', WEIGHT_DEFAULT) allow_partial_credit = pl.get_boolean_attrib(element, 'allow-partial-credit', ALLOW_PARTIAL_CREDIT_DEFAULT) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) # 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) result = data['test_type'] number_of_correct = 0 feedback = {} for i in range(m): for j in range(n): each_entry_name = name + str(n * i + j + 1) if result == 'correct': data['raw_submitted_answers'][each_entry_name] = str(a_tru[i, j]) number_of_correct += 1 feedback.update({each_entry_name: 'correct'}) elif result == 'incorrect': data['raw_submitted_answers'][each_entry_name] = str(a_tru[i, j] + (random.uniform(1, 10) * random.choice([-1, 1]))) feedback.update({each_entry_name: 'incorrect'}) elif result == 'invalid': if random.choice([True, False]): data['raw_submitted_answers'][each_entry_name] = '1,2' data['format_errors'][each_entry_name] = '(Invalid format)' else: data['raw_submitted_answers'][name] = '' data['format_errors'][each_entry_name] = '(Invalid blank entry)' else: raise Exception('invalid result: %s' % result) if result == 'invalid': data['format_errors'][name] = 'At least one of the entries has invalid format (empty entries or not a double precision floating point number)' 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 test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', 1) allow_partial_credit = pl.get_boolean_attrib(element, 'allow-partial-credit', False) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) # 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) result = random.choices(['correct', 'incorrect', 'incorrect'], [5, 5, 1])[0] number_of_correct = 0 feedback = {} for i in range(m): for j in range(n): each_entry_name = name + str(n * i + j + 1) if result == 'correct': data['raw_submitted_answers'][each_entry_name] = str(a_tru[i, j]) number_of_correct += 1 feedback.update({each_entry_name: 'correct'}) elif result == 'incorrect': data['raw_submitted_answers'][each_entry_name] = str(a_tru[i, j] + (random.uniform(1, 10) * random.choice([-1, 1]))) feedback.update({each_entry_name: 'incorrect'}) elif result == 'invalid': if random.choice([True, False]): data['raw_submitted_answers'][each_entry_name] = '1,2' data['format_errors'][each_entry_name] = '(Invalid format)' else: data['raw_submitted_answers'][name] = '' data['format_errors'][each_entry_name] = '(Invalid blank entry)' else: raise Exception('invalid result: %s' % result) if result == 'invalid': data['format_errors'][name] = 'At least one of the entries has invalid format (empty entries or not a double precision floating point number)' 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 test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', WEIGHT_DEFAULT) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) result = random.choices(['correct', 'incorrect', 'invalid'], [5, 5, 1])[0] if result == 'correct': data['raw_submitted_answers'][name] = str(a_tru) data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['partial_scores'][name] = {'score': 0, 'weight': weight} # Get method of comparison, with relabs as default comparison = pl.get_string_attrib(element, 'comparison', COMPARISON_DEFAULT) if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', RTOL_DEFAULT) atol = pl.get_float_attrib(element, 'atol', ATOL_DEFAULT) # Get max error according to numpy.allclose() eps = np.absolute(a_tru) * rtol + atol eps += random.uniform(1, 10) answer = a_tru + eps * random.choice([-1, 1]) elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', DIGITS_DEFAULT) # Get max error according to pl.is_correct_scalar_sf() if (a_tru == 0): n = digits - 1 else: n = -int(np.floor(np.log10(np.abs(a_tru)))) + (digits - 1) eps = 0.51 * (10**-n) eps += random.uniform(1, 10) answer = a_tru + eps * random.choice([-1, 1]) elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', DIGITS_DEFAULT) # Get max error according to pl.is_correct_scalar_dd() eps = 0.51 * (10**-digits) eps += random.uniform(1, 10) answer = a_tru + eps * random.choice([-1, 1]) else: raise ValueError('method of comparison "%s" is not valid' % comparison) data['raw_submitted_answers'][name] = str(answer) elif result == 'invalid': # FIXME: add more invalid expressions, make text of format_errors # correct, and randomize data['raw_submitted_answers'][name] = '1 + 2' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result)
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', 1) # Get remove-spaces option remove_spaces = pl.get_string_attrib(element, 'remove-spaces', False) # Get remove-leading-trailing option remove_leading_trailing = pl.get_string_attrib(element, 'remove-leading-trailing', False) # 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) # Remove the leading and trailing characters if (remove_leading_trailing): a_sub = a_sub.strip() # Remove the blank spaces between characters if (remove_spaces): a_sub = a_sub.replace(' ', '') if a_tru == a_sub: data['partial_scores'][name] = {'score': 1, 'weight': weight} else: data['partial_scores'][name] = {'score': 0, 'weight': weight}
def grade(data): # get the submitted answers MatrixA = pl.from_json(data['submitted_answers']['A']) MatrixB = MatrixA.dot(MatrixA) if ((not MatrixB.any()) and MatrixA.any()): data['partial_scores']['A'] = {"score": 1, "weight": 1} data['score'] = 1.0 else: data['score'] = 0
def render(element_html, data): # Get attribs element = lxml.html.fragment_fromstring(element_html) engine = pl.get_string_attrib(element, 'engine', ENGINE_DEFAULT) input_param = pl.get_string_attrib(element, 'params-name-matrix', PARAMS_NAME_MATRIX_DEFAULT) input_label = pl.get_string_attrib(element, 'params-name-labels', PARAMS_NAME_LABELS_DEFAULT) if len(str(element.text)) == 0 and input_param is None: raise Exception( 'No graph source given! Must either define graph in HTML or provide source in params.' ) graphviz_data = None if input_param is not None: mat = np.array(pl.from_json(data['params'][input_param])) label = None if input_label is not None: label = np.array(pl.from_json(data['params'][input_label])) graphviz_data = json.dumps( graphviz_from_matrix(mat, label, engine, element)) else: # Read the contents of this element as the data to render # we dump the string to json to ensure that newlines are # properly encoded graphviz_data = json.dumps(str(element.text)) html_params = { 'uuid': pl.get_uuid(), 'workerURL': '/node_modules/viz.js/full.render.js', 'data': graphviz_data, 'engine': engine, } with open('pl-graph.mustache') as f: html = chevron.render(f, html_params).strip() return html
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', 1) # Get remove-spaces option remove_spaces = pl.get_string_attrib(element, 'remove-spaces', False) # Get remove-leading-trailing option remove_leading_trailing = pl.get_string_attrib(element, 'remove-leading-trailing', False) # 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) # Remove the leading and trailing characters if (remove_leading_trailing): a_sub = a_sub.strip() # Remove the blank spaces between characters if (remove_spaces): a_sub = a_sub.replace(' ', '') if a_tru == a_sub: data['partial_scores'][name] = {'score': 1, 'weight': weight} else: data['partial_scores'][name] = {'score': 0, 'weight': weight}
def render(element_html, element_index, data): element = lxml.html.fragment_fromstring(element_html) digits = pl.get_integer_attrib(element, 'digits', 2) matlab_data = '' python_data = 'import numpy as np\n\n' for child in element: if child.tag == 'variable': # Raise exception of variable does not have a name pl.check_attribs(child, required_attribs=['params_name'], optional_attribs=[]) # Get name of variable var_name = pl.get_string_attrib(child, 'params_name') # Get value of variable, raising exception if variable does not exist var_data = data['params'].get(var_name, None) if var_data is None: raise Exception('No value in data["params"] for variable %s in pl_matrix_output element' % var_name) # If the variable is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) var_data = pl.from_json(var_data) if np.isscalar(var_data): prefix = '' suffix = '' else: # Wrap the variable in an ndarray (if it's already one, this does nothing) var_data = np.array(var_data) # Check shape of variable if var_data.ndim != 2: raise Exception('Value in data["params"] for variable %s in pl_matrix_output element must be a scalar or a 2D array' % var_name) # Create prefix/suffix so python string is np.array( ... ) prefix = 'np.array(' suffix = ')' # Create string for matlab and python format matlab_data += pl.inner_html(child) + ' = ' + pl.string_from_2darray(var_data, language='matlab', digits=digits) + ';\n' python_data += pl.inner_html(child) + ' = ' + prefix + pl.string_from_2darray(var_data, language='python', digits=digits) + suffix + '\n' html_params = { 'default_is_matlab': True, 'matlab_data': matlab_data, 'python_data': python_data, 'element_index': element_index, 'uuid': pl.get_uuid() } with open('pl_matrix_output.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() return html
def test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', WEIGHT_DEFAULT) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) # Wrap true answer in ndarray (if it already is one, this does nothing) a_tru = np.array(a_tru) result = random.choices(['correct', 'incorrect', 'invalid'], [5, 5, 1])[0] if random.choice([True, False]): # matlab if result == 'correct': data['raw_submitted_answers'][name] = pl.numpy_to_matlab( a_tru, ndigits=12, wtype='g') data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = pl.numpy_to_matlab( a_tru + (random.uniform(1, 10) * random.choice([-1, 1])), ndigits=12, wtype='g') data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': # FIXME: add more invalid expressions, make text of format_errors # correct, and randomize data['raw_submitted_answers'][name] = '[1, 2, 3]' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result) else: # python if result == 'correct': data['raw_submitted_answers'][name] = str(np.array(a_tru).tolist()) data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = str( (a_tru + (random.uniform(1, 10) * random.choice([-1, 1]))).tolist()) data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': # FIXME: add more invalid expressions, make text of format_errors # correct, and randomize data['raw_submitted_answers'][name] = '[[1, 2, 3], [4, 5]]' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result)
def parse(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') # Get true answer a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: return a_tru = np.array(a_tru) if a_tru.ndim != 2: raise ValueError('true answer must be a 2D array') else: m, n = np.shape(a_tru) A = np.empty([m, n]) # Create an array for the submitted answer to be stored in data['submitted_answer'][name] # used for display in the answer and submission panels # Also creates invalid error messages invalid_format = False 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) if a_sub is None: data['submitted_answers'][each_entry_name] = None data['format_errors'][each_entry_name] = '(No submitted answer)' invalid_format = True elif not a_sub: data['submitted_answers'][each_entry_name] = None data['format_errors'][each_entry_name] = '(Invalid blank entry)' invalid_format = True else: a_sub_parsed = pl.string_to_number(a_sub, allow_complex=False) if a_sub_parsed is None: data['submitted_answers'][each_entry_name] = None data['format_errors'][each_entry_name] = '(Invalid format)' invalid_format = True elif not np.isfinite(a_sub_parsed): data['submitted_answers'][each_entry_name] = None data['format_errors'][each_entry_name] = '(Invalid format - not finite)' invalid_format = True else: data['submitted_answers'][each_entry_name] = pl.to_json(a_sub_parsed) A[i, j] = a_sub_parsed if invalid_format: data['format_errors'][name] = 'At least one of the entries has invalid format (empty entries or not a double precision floating point number)' data['submitted_answers'][name] = None else: data['submitted_answers'][name] = pl.to_json(A)
def parse(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') allow_fractions = pl.get_boolean_attrib(element, 'allow-fractions', ALLOW_FRACTIONS_DEFAULT) # Get true answer a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: return a_tru = np.array(a_tru) if a_tru.ndim != 2: raise ValueError('true answer must be a 2D array') else: m, n = np.shape(a_tru) A = np.empty([m, n]) # Create an array for the submitted answer to be stored in data['submitted_answer'][name] # used for display in the answer and submission panels # Also creates invalid error messages invalid_format = False 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) value, newdata = pl.string_fraction_to_number(a_sub, allow_fractions, allow_complex=False) if value is not None: A[i, j] = value data['submitted_answers'][each_entry_name] = newdata[ 'submitted_answers'] else: invalid_format = True data['format_errors'][each_entry_name] = newdata[ 'format_errors'] data['submitted_answers'][each_entry_name] = None if invalid_format: with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: data['format_errors'][name] = chevron.render( f, { 'format_error': True, 'allow_fractions': allow_fractions }).strip() data['submitted_answers'][name] = None else: data['submitted_answers'][name] = pl.to_json(A)
def test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', 1) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) # Wrap true answer in ndarray (if it already is one, this does nothing) a_tru = np.array(a_tru) result = random.choices(['correct', 'incorrect', 'invalid'], [5, 5, 1])[0] if random.choice([True, False]): # matlab if result == 'correct': data['raw_submitted_answers'][name] = pl.numpy_to_matlab(a_tru, ndigits=12, wtype='g') data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = pl.numpy_to_matlab(a_tru + (random.uniform(1, 10) * random.choice([-1, 1])), ndigits=12, wtype='g') data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': # FIXME: add more invalid expressions, make text of format_errors # correct, and randomize data['raw_submitted_answers'][name] = '[1, 2, 3]' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result) else: # python if result == 'correct': data['raw_submitted_answers'][name] = str(np.array(a_tru).tolist()) data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = str((a_tru + (random.uniform(1, 10) * random.choice([-1, 1]))).tolist()) data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': # FIXME: add more invalid expressions, make text of format_errors # correct, and randomize data['raw_submitted_answers'][name] = '[[1, 2, 3], [4, 5]]' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result)
def format_true_ans(element, data, name): a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: # Get comparison parameters comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': # FIXME: render correctly with respect to rtol and atol a_tru = '{:.12g}'.format(a_tru) elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) a_tru = pl.string_from_number_sigfig(a_tru, digits=digits) elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) a_tru = '{:.{ndigits}f}'.format(a_tru, ndigits=digits) else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) return a_tru
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) force_text = pl.get_boolean_attrib(element, 'text', TEXT_DEFAULT) varname = pl.get_string_attrib(element, 'params-name') show_header = pl.get_boolean_attrib(element, 'show-header', SHOW_HEADER_DEFAULT) show_index = pl.get_boolean_attrib(element, 'show-index', SHOW_INDEX_DEFAULT) show_dimensions = pl.get_boolean_attrib(element, 'show-dimensions', SHOW_DIMENSIONS_DEFAULT) if varname not in data['params']: raise Exception('Could not find {} in params!'.format(varname)) var_out = data['params'][varname] html = '' var_type = 'text' # determine the type of variable to render if isinstance(var_out, dict) and '_type' in var_out: if not force_text: var_type = var_out['_type'] var_out = pl.from_json(var_out) # render the output variable if var_type == 'dataframe': html += var_out.to_html(header=show_header, index=show_index, classes=['pl-python-variable-table']) if show_dimensions: html += '<p class="pl-python-variable-table-dimensions">{} rows x {} columns</p>'.format( str(var_out.shape[0]), str(var_out.shape[1])) else: no_highlight = pl.get_boolean_attrib(element, 'no-highlight', NO_HIGHLIGHT_DEFAULT) prefix = pl.get_string_attrib(element, 'prefix', PREFIX_DEFAULT) suffix = pl.get_string_attrib(element, 'suffix', SUFFIX_DEFAULT) text = prefix + repr(var_out) + suffix html += '<pl-code language="python" no-highlight="{}">{}</pl-code>'.format( no_highlight, text) return html
def test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', WEIGHT_DEFAULT) base = pl.get_integer_attrib(element, 'base', BASE_DEFAULT) # Get correct answer a_tru = data['correct_answers'][name] # If correct answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_tru = pl.from_json(a_tru) if isinstance(a_tru, str): a_tru = pl.string_to_integer(a_tru, base) result = data['test_type'] if result == 'correct': if base > 0: data['raw_submitted_answers'][name] = numpy.base_repr(a_tru, base) elif random.choice([True, False]): data['raw_submitted_answers'][name] = numpy.base_repr(a_tru, 10) else: # Use 0x format data['raw_submitted_answers'][name] = f'{a_tru:#x}' data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = numpy.base_repr( a_tru + (random.randint(1, 11) * random.choice([-1, 1])), base if base > 0 else 10) data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': # FIXME: add more invalid expressions, make text of format_errors # correct, and randomize if random.choice([True, False]): data['raw_submitted_answers'][name] = '1 + 2' else: data['raw_submitted_answers'][name] = '3.4' data['format_errors'][name] = 'invalid' else: raise Exception('invalid result: %s' % result)
def prepare(element_html, data): element = lxml.html.fragment_fromstring(element_html) required_attribs = ['answers-name'] optional_attribs = [ 'weight', 'correct-answer', 'label', 'suffix', 'display', 'size', 'show-help-text', 'base', 'allow-blank', 'blank-value' ] pl.check_attribs(element, required_attribs, optional_attribs) name = pl.get_string_attrib(element, 'answers-name') base = pl.get_integer_attrib(element, 'base', BASE_DEFAULT) if base != 0 and (base < 2 or base > 36): raise Exception('Base must be either 0, or between 2 and 36') correct_answer = pl.get_string_attrib(element, 'correct-answer', CORRECT_ANSWER_DEFAULT) if correct_answer is not None: if name in data['correct_answers']: raise Exception('duplicate correct_answers variable name: %s' % name) # Test conversion, but leave as string so proper value is shown on answer panel data['correct_answers'][name] = correct_answer if correct_answer is None: correct_answer = pl.from_json(data['correct_answers'].get(name, None)) if correct_answer is not None: try: if not isinstance(correct_answer, int): correct_answer = int(correct_answer, base) except Exception: raise Exception('correct answer is not a valid input: %s' % name) if correct_answer > 2**53 - 1 or correct_answer < -((2**53) - 1): raise Exception( 'correct answer must be between -9007199254740991 and +9007199254740991 (that is, between -(2^53 - 1) and +(2^53 - 1)).' )
def render(element_html, element_index, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers_name') variables_string = pl.get_string_attrib(element, 'variables', None) variables = get_variables_list(variables_string) if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) operators = ', '.join([ 'cos', 'sin', 'tan', 'exp', 'log', 'sqrt', '( )', '+', '-', '*', '/', '^', '**' ]) constants = ', '.join(['pi']) info_params = { 'format': True, 'variables': variables_string, 'operators': operators, 'constants': constants } with open('pl_symbolic_input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl_symbolic_input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl_symbolic_input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: a_sub = data['submitted_answers'][name] a_sub = phs.convert_string_to_sympy(a_sub, variables) html_params['a_sub'] = sympy.latex(a_sub) else: raw_submitted_answer = data['raw_submitted_answers'].get( name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape( raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl_symbolic_input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: if isinstance(a_tru, str): a_tru = phs.convert_string_to_sympy(a_tru, variables) html_params = {'answer': True, 'a_tru': sympy.latex(a_tru)} with open('pl_symbolic_input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, element_index, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers_name') label = pl.get_string_attrib(element, 'label', None) suffix = pl.get_string_attrib(element, 'suffix', None) display = pl.get_string_attrib(element, 'display', 'inline') if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) # Get comparison parameters and info strings 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) info_params = { 'format': True, 'relabs': True, 'rtol': rtol, 'atol': atol } elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) info_params = { 'format': True, 'sigfig': True, 'digits': digits, 'comparison_eps': 0.51 * (10**-(digits - 1)) } elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) info_params = { 'format': True, 'decdig': True, 'digits': digits, 'comparison_eps': 0.51 * (10**-(digits - 0)) } else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) info_params['allow_complex'] = pl.get_boolean_attrib( element, 'allow_complex', False) with open('pl_number_input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl_number_input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'suffix': suffix, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError( 'method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl_number_input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If 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) html_params['suffix'] = suffix html_params['a_sub'] = '{:.12g}'.format(a_sub) else: raw_submitted_answer = data['raw_submitted_answers'].get( name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape( raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl_number_input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: # Get comparison parameters 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) # FIXME: render correctly with respect to rtol and atol a_tru = '{:.12g}'.format(a_tru) elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) a_tru = pl.string_from_number_sigfig(a_tru, digits=digits) elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) a_tru = '{:.{ndigits}f}'.format(a_tru, ndigits=digits) else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) # FIXME: render correctly with respect to method of comparison html_params = { 'answer': True, 'label': label, 'a_tru': a_tru, 'suffix': suffix } with open('pl_number_input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def test(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') weight = pl.get_integer_attrib(element, 'weight', WEIGHT_DEFAULT) result = random.choices(['correct', 'incorrect', 'invalid'], [5, 5, 1])[0] if result == 'correct': data['raw_submitted_answers'][name] = str( pl.from_json(data['correct_answers'][name])) data['partial_scores'][name] = {'score': 1, 'weight': weight} elif result == 'incorrect': data['raw_submitted_answers'][name] = str( pl.from_json(data['correct_answers'][name])) + ' + {:d}'.format( random.randint(1, 100)) data['partial_scores'][name] = {'score': 0, 'weight': weight} elif result == 'invalid': invalid_type = random.choice([ 'float', 'complex', 'expression', 'function', 'variable', 'syntax', 'escape', 'comment' ]) if invalid_type == 'float': data['raw_submitted_answers'][name] = 'x + 1.234' s = 'Your answer contains the floating-point number ' + str( 1.234) + '. ' s += 'All numbers must be expressed as integers (or ratios of integers). ' s += '<br><br><pre>' + phs.point_to_error('x + 1.234', 4) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'complex': data['raw_submitted_answers'][name] = 'x + (1+2j)' s = 'Your answer contains the complex number ' + str(2j) + '. ' s += 'All numbers must be expressed as integers (or ratios of integers). ' s += '<br><br><pre>' + phs.point_to_error('x + (1+2j)', 7) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'expression': data['raw_submitted_answers'][name] = '1 and 0' s = 'Your answer has an invalid expression. ' s += '<br><br><pre>' + phs.point_to_error('1 and 0', 0) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'function': data['raw_submitted_answers'][name] = 'atan(x)' s = 'Your answer calls an invalid function "' + 'atan' + '". ' s += '<br><br><pre>' + phs.point_to_error('atan(x)', 0) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'variable': data['raw_submitted_answers'][name] = 'x + y' s = 'Your answer refers to an invalid variable "' + 'y' + '". ' s += '<br><br><pre>' + phs.point_to_error('x + y', 4) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'syntax': data['raw_submitted_answers'][name] = 'x +* 1' s = 'Your answer has a syntax error. ' s += '<br><br><pre>' + phs.point_to_error('x +* 1', 4) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'escape': data['raw_submitted_answers'][name] = 'x + 1\\n' s = 'Your answer must not contain the character "\\". ' s += '<br><br><pre>' + phs.point_to_error('x + 1\\n', 5) + '</pre>' data['format_errors'][name] = s elif invalid_type == 'comment': data['raw_submitted_answers'][name] = 'x # some text' s = 'Your answer must not contain the character "#". ' s += '<br><br><pre>' + phs.point_to_error('x # some text', 2) + '</pre>' data['format_errors'][name] = s else: raise Exception('invalid invalid_type: %s' % invalid_type) else: raise Exception('invalid result: %s' % result)
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 render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) if '_pl_matrix_input_format' in data['submitted_answers']: format_type = data['submitted_answers']['_pl_matrix_input_format'].get(name, 'matlab') else: format_type = 'matlab' if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) # Get comparison parameters and info strings 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) if (rtol < 0): raise ValueError('Attribute rtol = {:g} must be non-negative'.format(rtol)) if (atol < 0): raise ValueError('Attribute atol = {:g} must be non-negative'.format(atol)) info_params = {'format': True, 'relabs': True, 'rtol': '{:g}'.format(rtol), 'atol': '{:g}'.format(atol)} elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError('Attribute digits = {:d} must be non-negative'.format(digits)) info_params = {'format': True, 'sigfig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 1))} elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError('Attribute digits = {:d} must be non-negative'.format(digits)) info_params = {'format': True, 'decdig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 0))} else: raise ValueError('method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) info_params['allow_complex'] = pl.get_boolean_attrib(element, 'allow-complex', False) with open('pl-matrix-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-matrix-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-matrix-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If 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) # Wrap answer in an ndarray (if it's already one, this does nothing) a_sub = np.array(a_sub) # Format answer as a string html_params['a_sub'] = pl.string_from_2darray(a_sub, language=format_type, digits=12, presentation_type='g') else: raw_submitted_answer = data['raw_submitted_answers'].get(name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-matrix-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': # Get true answer - do nothing if it does not exist a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: a_tru = np.array(a_tru) # Get comparison parameters 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) # FIXME: render correctly with respect to rtol and atol matlab_data = pl.string_from_2darray(a_tru, language='matlab', digits=12, presentation_type='g') python_data = pl.string_from_2darray(a_tru, language='python', digits=12, presentation_type='g') elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) matlab_data = pl.string_from_2darray(a_tru, language='matlab', digits=digits, presentation_type='sigfig') python_data = pl.string_from_2darray(a_tru, language='python', digits=digits, presentation_type='sigfig') elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) matlab_data = pl.string_from_2darray(a_tru, language='matlab', digits=digits, presentation_type='f') python_data = pl.string_from_2darray(a_tru, language='python', digits=digits, presentation_type='f') else: raise ValueError('method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) html_params = { 'answer': True, 'label': label, 'matlab_data': matlab_data, 'python_data': python_data, 'uuid': pl.get_uuid() } if format_type == 'matlab': html_params['default_is_matlab'] = True else: html_params['default_is_python'] = True with open('pl-matrix-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) suffix = pl.get_string_attrib(element, 'suffix', None) display = pl.get_string_attrib(element, 'display', DISPLAY_DEFAULT) if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) html_params = { 'question': True, 'name': name, 'label': label, 'suffix': suffix, 'editable': editable, 'size': pl.get_integer_attrib(element, 'size', SIZE_DEFAULT), 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) # Get comparison parameters and info strings comparison = pl.get_string_attrib(element, 'comparison', COMPARISON_DEFAULT) if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', RTOL_DEFAULT) atol = pl.get_float_attrib(element, 'atol', ATOL_DEFAULT) if (rtol < 0): raise ValueError( 'Attribute rtol = {:g} must be non-negative'.format(rtol)) if (atol < 0): raise ValueError( 'Attribute atol = {:g} must be non-negative'.format(atol)) info_params = { 'format': True, 'relabs': True, 'rtol': '{:g}'.format(rtol), 'atol': '{:g}'.format(atol) } elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', DIGITS_DEFAULT) if (digits < 0): raise ValueError( 'Attribute digits = {:d} must be non-negative'.format( digits)) info_params = { 'format': True, 'sigfig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 1)) } elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', DIGITS_DEFAULT) if (digits < 0): raise ValueError( 'Attribute digits = {:d} must be non-negative'.format( digits)) info_params = { 'format': True, 'decdig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 0)) } else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) # Update parameters for the info popup show_correct = 'correct' in html_params and pl.get_boolean_attrib( element, 'show-correct-answer', SHOW_CORRECT_ANSWER_DEFAULT) info_params['allow_complex'] = pl.get_boolean_attrib( element, 'allow-complex', ALLOW_COMPLEX_DEFAULT) info_params['show_info'] = pl.get_boolean_attrib( element, 'show-help-text', SHOW_HELP_TEXT_DEFAULT) info_params['show_correct'] = show_correct # Find the true answer to be able to display it in the info popup ans_true = None if pl.get_boolean_attrib(element, 'show-correct-answer', SHOW_CORRECT_ANSWER_DEFAULT): ans_true = format_true_ans(element, data, name) if ans_true is not None: info_params['a_tru'] = ans_true with open('pl-number-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-number-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) # Within mustache, the shortformat generates the shortinfo that is used as a placeholder inside of the numeric entry. # Here we opt to not generate the value, hence the placeholder is empty. info_params['shortformat'] = pl.get_boolean_attrib( element, 'show-placeholder', SHOW_PLACEHOLDER_DEFAULT) shortinfo = chevron.render(f, info_params).strip() html_params['info'] = info html_params['shortinfo'] = shortinfo # Determine the title of the popup based on what information is being shown if pl.get_boolean_attrib(element, 'show-help-text', SHOW_HELP_TEXT_DEFAULT): html_params['popup_title'] = 'Number' else: html_params['popup_title'] = 'Correct Answer' # Enable or disable the popup if pl.get_boolean_attrib(element, 'show-help-text', SHOW_HELP_TEXT_DEFAULT) or show_correct: html_params['show_info'] = True html_params[ 'display_append_span'] = 'questionmark' in html_params or suffix if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError( 'method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-number-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If 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) html_params['suffix'] = suffix html_params['a_sub'] = '{:.12g}'.format(a_sub) else: raw_submitted_answer = data['raw_submitted_answers'].get( name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape( raw_submitted_answer) # Add true answer to be able to display it in the submitted answer panel ans_true = None if pl.get_boolean_attrib(element, 'show-correct-answer', SHOW_CORRECT_ANSWER_DEFAULT): ans_true = format_true_ans(element, data, name) if ans_true is not None: html_params['a_tru'] = ans_true partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-number-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': ans_true = format_true_ans(element, data, name) if ans_true is not None: html_params = { 'answer': True, 'label': label, 'a_tru': ans_true, 'suffix': suffix } with open('pl-number-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
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 render(element_html, data): element = lxml.html.fragment_fromstring(element_html) # get the name of the element, in this case, the name of the array name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) allow_partial_credit = pl.get_boolean_attrib(element, 'allow-partial-credit', False) allow_feedback = pl.get_boolean_attrib(element, 'allow-feedback', allow_partial_credit) if data['panel'] == 'question': editable = data['editable'] # Get true answer a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: raise Exception( 'No value in data["correct_answers"] for variable %s in pl-matrix-component-input element' % name) else: if np.isscalar(a_tru): raise Exception( 'Value in data["correct_answers"] for variable %s in pl-matrix-component-input element cannot be a scalar.' % name) else: a_tru = np.array(a_tru) if a_tru.ndim != 2: raise Exception( 'Value in data["correct_answers"] for variable %s in pl-matrix-component-input element must be a 2D array.' % name) else: m, n = np.shape(a_tru) input_array = createTableForHTMLDisplay(m, n, name, label, data, 'input') # Get comparison parameters and info strings 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) if (rtol < 0): raise ValueError( 'Attribute rtol = {:g} must be non-negative'.format(rtol)) if (atol < 0): raise ValueError( 'Attribute atol = {:g} must be non-negative'.format(atol)) info_params = { 'format': True, 'relabs': True, 'rtol': '{:g}'.format(rtol), 'atol': '{:g}'.format(atol) } elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError( 'Attribute digits = {:d} must be non-negative'.format( digits)) info_params = { 'format': True, 'sigfig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 1)) } elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError( 'Attribute digits = {:d} must be non-negative'.format( digits)) info_params = { 'format': True, 'decdig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 0)) } else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'input_array': input_array, 'inline': True, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } a_tru = pl.from_json(data['correct_answers'].get(name, None)) m, n = np.shape(a_tru) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If 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) # Wrap answer in an ndarray (if it's already one, this does nothing) a_sub = np.array(a_sub) # Format submitted answer as a latex string sub_latex = '$' + pl.latex_from_2darray( a_sub, presentation_type='g', digits=12) + '$' # When allowing feedback, display submitted answers using html table sub_html_table = createTableForHTMLDisplay(m, n, name, label, data, 'output-feedback') if allow_feedback and score is not None: if score < 1: html_params['a_sub_feedback'] = sub_html_table else: html_params['a_sub'] = sub_latex else: html_params['a_sub'] = sub_latex else: # create html table to show submitted answer when there is an invalid format html_params['raw_submitted_answer'] = createTableForHTMLDisplay( m, n, name, label, data, 'output-invalid') with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': # Get true answer - do nothing if it does not exist a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: a_tru = np.array(a_tru) # Get comparison parameters and create the display data 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) # FIXME: render correctly with respect to rtol and atol latex_data = '$' + pl.latex_from_2darray( a_tru, presentation_type='g', digits=12) + '$' elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) latex_data = '$' + pl.latex_from_2darray( a_tru, presentation_type='sigfig', digits=digits) + '$' elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) latex_data = '$' + pl.latex_from_2darray( a_tru, presentation_type='f', digits=digits) + '$' else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) html_params = { 'answer': True, 'label': label, 'latex_data': latex_data, 'uuid': pl.get_uuid() } with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', LABEL_DEFAULT) suffix = pl.get_string_attrib(element, 'suffix', SUFFIX_DEFAULT) display = pl.get_string_attrib(element, 'display', DISPLAY_DEFAULT) remove_leading_trailing = pl.get_string_attrib( element, 'remove-leading-trailing', REMOVE_LEADING_TRAILING_DEFAULT) remove_spaces = pl.get_string_attrib(element, 'remove-spaces', REMOVE_SPACES_DEFAULT) placeholder = pl.get_string_attrib(element, 'placeholder', PLACEHOLDER_DEFAULT) if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) if remove_leading_trailing: if remove_spaces: space_hint = 'All spaces will be removed from your answer.' else: space_hint = 'Leading and trailing spaces will be removed from your answer.' else: if remove_spaces: space_hint = 'All spaces between text will be removed but leading and trailing spaces will be left as part of your answer.' else: space_hint = 'Leading and trailing spaces will be left as part of your answer.' # Get info strings info_params = {'format': True, 'space_hint': space_hint} with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: template = f.read() info = chevron.render(template, info_params).strip() info_params.pop('format', None) html_params = { 'question': True, 'name': name, 'label': label, 'suffix': suffix, 'remove-leading-trailing': remove_leading_trailing, 'remove-spaces': remove_spaces, 'editable': editable, 'info': info, 'placeholder': placeholder, 'size': pl.get_integer_attrib(element, 'size', SIZE_DEFAULT), 'show_info': pl.get_boolean_attrib(element, 'show-help-text', SHOW_HELP_TEXT_DEFAULT), 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) html_params['display_append_span'] = html_params['show_info'] or suffix if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError( 'method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None and name in data['submitted_answers']: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If 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) a_sub = pl.escape_unicode_string(a_sub) html_params['suffix'] = suffix html_params['a_sub'] = a_sub elif name not in data['submitted_answers']: html_params['missing_input'] = True html_params['parse_error'] = None else: raw_submitted_answer = data['raw_submitted_answers'].get( name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = pl.escape_unicode_string( raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) html_params['error'] = html_params['parse_error'] or html_params.get( 'missing_input', False) with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: html_params = { 'answer': True, 'label': label, 'a_tru': a_tru, 'suffix': suffix } with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) # get the name of the element, in this case, the name of the array name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) allow_partial_credit = pl.get_boolean_attrib(element, 'allow-partial-credit', False) allow_feedback = pl.get_boolean_attrib(element, 'allow-feedback', allow_partial_credit) if data['panel'] == 'question': editable = data['editable'] # Get true answer a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: raise Exception('No value in data["correct_answers"] for variable %s in pl-matrix-component-input element' % name) else: if np.isscalar(a_tru): raise Exception('Value in data["correct_answers"] for variable %s in pl-matrix-component-input element cannot be a scalar.' % name) else: a_tru = np.array(a_tru) if a_tru.ndim != 2: raise Exception('Value in data["correct_answers"] for variable %s in pl-matrix-component-input element must be a 2D array.' % name) else: m, n = np.shape(a_tru) input_array = createTableForHTMLDisplay(m, n, name, label, data, 'input') # Get comparison parameters and info strings 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) if (rtol < 0): raise ValueError('Attribute rtol = {:g} must be non-negative'.format(rtol)) if (atol < 0): raise ValueError('Attribute atol = {:g} must be non-negative'.format(atol)) info_params = {'format': True, 'relabs': True, 'rtol': '{:g}'.format(rtol), 'atol': '{:g}'.format(atol)} elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError('Attribute digits = {:d} must be non-negative'.format(digits)) info_params = {'format': True, 'sigfig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 1))} elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError('Attribute digits = {:d} must be non-negative'.format(digits)) info_params = {'format': True, 'decdig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 0))} else: raise ValueError('method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'input_array': input_array, 'inline': True, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } a_tru = pl.from_json(data['correct_answers'].get(name, None)) m, n = np.shape(a_tru) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If 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) # Wrap answer in an ndarray (if it's already one, this does nothing) a_sub = np.array(a_sub) # Format submitted answer as a latex string sub_latex = '$' + pl.latex_from_2darray(a_sub, presentation_type='g', digits=12) + '$' # When allowing feedback, display submitted answers using html table sub_html_table = createTableForHTMLDisplay(m, n, name, label, data, 'output-feedback') if allow_feedback and score is not None: if score < 1: html_params['a_sub_feedback'] = sub_html_table else: html_params['a_sub'] = sub_latex else: html_params['a_sub'] = sub_latex else: # create html table to show submitted answer when there is an invalid format html_params['raw_submitted_answer'] = createTableForHTMLDisplay(m, n, name, label, data, 'output-invalid') with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': # Get true answer - do nothing if it does not exist a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: a_tru = np.array(a_tru) # Get comparison parameters and create the display data 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) # FIXME: render correctly with respect to rtol and atol latex_data = '$' + pl.latex_from_2darray(a_tru, presentation_type='g', digits=12) + '$' elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) latex_data = '$' + pl.latex_from_2darray(a_tru, presentation_type='sigfig', digits=digits) + '$' elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) latex_data = '$' + pl.latex_from_2darray(a_tru, presentation_type='f', digits=digits) + '$' else: raise ValueError('method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) html_params = { 'answer': True, 'label': label, 'latex_data': latex_data, 'uuid': pl.get_uuid() } with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) digits = pl.get_integer_attrib(element, 'digits', 2) show_matlab = pl.get_boolean_attrib(element, 'show-matlab', True) show_mathematica = pl.get_boolean_attrib(element, 'show-mathematica', True) show_python = pl.get_boolean_attrib(element, 'show-python', True) default_tab = pl.get_string_attrib(element, 'default-tab', 'matlab') tab_list = ['matlab', 'mathematica', 'python'] if default_tab not in tab_list: raise Exception(f'invalid default-tab: {default_tab}') # Setting the default tab displayed_tab = [show_matlab, show_mathematica, show_python] if not any(displayed_tab): raise Exception('All tabs have been hidden from display. At least one tab must be shown.') default_tab_index = tab_list.index(default_tab) # If not displayed, make first visible tab the default if not displayed_tab[default_tab_index]: first_display = displayed_tab.index(True) default_tab = tab_list[first_display] default_tab_index = tab_list.index(default_tab) # Active tab should be the default tab default_tab_list = [False, False, False] default_tab_list[default_tab_index] = True [active_tab_matlab, active_tab_mathematica, active_tab_python] = default_tab_list # Process parameter data matlab_data = '' mathematica_data = '' python_data = 'import numpy as np\n\n' for child in element: if child.tag == 'variable': # Raise exception if variable does not have a name pl.check_attribs(child, required_attribs=['params-name'], optional_attribs=['comment', 'digits']) # Get name of variable var_name = pl.get_string_attrib(child, 'params-name') # Get value of variable, raising exception if variable does not exist var_data = data['params'].get(var_name, None) if var_data is None: raise Exception('No value in data["params"] for variable %s in pl-variable-output element' % var_name) # If the variable is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) var_data = pl.from_json(var_data) # Get comment, if it exists var_matlab_comment = '' var_mathematica_comment = '' var_python_comment = '' if pl.has_attrib(child, 'comment'): var_comment = pl.get_string_attrib(child, 'comment') var_matlab_comment = f' % {var_comment}' var_mathematica_comment = f' (* {var_comment} *)' var_python_comment = f' # {var_comment}' # Get digit for child, if it exists if not pl.has_attrib(child, 'digits'): var_digits = digits else: var_digits = pl.get_string_attrib(child, 'digits') # Assembling Python array formatting if np.isscalar(var_data): prefix = '' suffix = '' else: # Wrap the variable in an ndarray (if it's already one, this does nothing) var_data = np.array(var_data) # Check shape of variable if var_data.ndim > 2: raise Exception('Value in data["params"] for variable %s in pl-variable-output element must be a scalar, a vector, or a 2D array' % var_name) # Create prefix/suffix so python string is np.array( ... ) prefix = 'np.array(' suffix = ')' # Mathematica reserved letters: C D E I K N O mathematica_reserved = ['C', 'D', 'E', 'I', 'K', 'N', 'O'] if pl.inner_html(child) in mathematica_reserved: mathematica_suffix = 'm' else: mathematica_suffix = '' # Create string for matlab and python format var_name_disp = pl.inner_html(child) var_matlab_data = pl.string_from_numpy(var_data, language='matlab', digits=var_digits) var_mathematica = pl.string_from_numpy(var_data, language='mathematica', digits=var_digits) var_python_data = pl.string_from_numpy(var_data, language='python', digits=var_digits) matlab_data += f'{var_name_disp} = {var_matlab_data};{var_matlab_comment}\n' mathematica_data += f'{var_name_disp}{mathematica_suffix} = {var_mathematica};{var_mathematica_comment}\n' python_data += f'{var_name_disp} = {prefix}{var_python_data}{suffix}{var_python_comment}\n' html_params = { 'active_tab_matlab': active_tab_matlab, 'active_tab_mathematica': active_tab_mathematica, 'active_tab_python': active_tab_python, 'show_matlab': show_matlab, 'show_mathematica': show_mathematica, 'show_python': show_python, 'matlab_data': matlab_data, 'mathematica_data': mathematica_data, 'python_data': python_data, 'uuid': pl.get_uuid() } with open('pl-variable-output.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) suffix = pl.get_string_attrib(element, 'suffix', None) display = pl.get_string_attrib(element, 'display', 'inline') remove_leading_trailing = pl.get_string_attrib(element, 'remove-leading-trailing', False) remove_spaces = pl.get_string_attrib(element, 'remove-spaces', False) placeholder = pl.get_string_attrib(element, 'placeholder', None) if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) # Get info strings info_params = {'format': True} with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: template = f.read() info = chevron.render(template, info_params).strip() info_params.pop('format', None) html_params = { 'question': True, 'name': name, 'label': label, 'suffix': suffix, 'remove-leading-trailing': remove_leading_trailing, 'remove-spaces': remove_spaces, 'editable': editable, 'info': info, 'placeholder': placeholder, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError( 'method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If 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) html_params['suffix'] = suffix html_params['a_sub'] = a_sub else: raw_submitted_answer = data['raw_submitted_answers'].get( name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape( raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: html_params = { 'answer': True, 'label': label, 'a_tru': a_tru, 'suffix': suffix } with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) suffix = pl.get_string_attrib(element, 'suffix', None) display = pl.get_string_attrib(element, 'display', 'inline') if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) # Get info strings info_params = {'format': True} with open('pl-integer-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-integer-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'suffix': suffix, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError('method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-integer-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If 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) html_params['suffix'] = suffix html_params['a_sub'] = '{:d}'.format(a_sub) else: raw_submitted_answer = data['raw_submitted_answers'].get(name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-integer-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: html_params = {'answer': True, 'label': label, 'a_tru': '{:d}'.format(a_tru), 'suffix': suffix} with open('pl-integer-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html