def eval_product(parse_result): """ Multiply/divide inputs appropriately Arguments: parse_result: A list of numbers to combine, separated by "*" and "/" [a, "*", b, "/", c] = a*b/c Has some extra logic to avoid ambiguous vector triple products. See https://github.com/mitodl/mitx-grading-library/issues/108 Usage ===== >>> MathExpression.eval_product([2,"*",3,"/",4]) 1.5 >>> try: ... MathExpression.eval_product([2,"*",3,"+",4]) ... except CalcError as error: ... print(error) Unexpected symbol + in eval_product """ double_vector_mult_has_occured = False triple_vector_mult_error = CalcError( "Multiplying three or more vectors is ambiguous. " "Please place parentheses around vector multiplications.") result = parse_result[0] data = parse_result[1:] while data: op = data.pop(0) value = data.pop(0) if op == '/': # Don't use in-place ops, it conflicts with numpy version 1.16 # 'same-type' casting result = result / value elif op == '*': if is_vector(value): if double_vector_mult_has_occured: raise triple_vector_mult_error elif is_vector(result): double_vector_mult_has_occured = True result = result * value else: raise CalcError( "Unexpected symbol {} in eval_product".format(op)) # Need to cast np numerics as builtins here (in addition to during # eval_node) because the result is changing shape result = cast_np_numeric_as_builtin(result) return result
def vector_phase_comparer(comparer_params_eval, student_eval, utils): """ Check that student input equals a given input (to within tolerance), up to an overall phase factor. comparer_params: [target_vector] Usage ===== >>> from mitxgraders import MatrixGrader >>> grader = MatrixGrader( ... answers={ ... 'comparer_params': [ ... '[1, exp(-i*phi)]', ... ], ... 'comparer': vector_phase_comparer ... }, ... variables=['phi'], ... ) >>> grader(None, '[1, exp(-i*phi)]')['ok'] True >>> grader(None, '[exp(i*phi/2), exp(-i*phi/2)]')['ok'] True >>> grader(None, '[i, exp(i*(pi/2 - phi))]')['ok'] True >>> grader(None, '[1, exp(+i*phi)]')['ok'] False >>> grader(None, '[2, 2*exp(-i*phi)]')['ok'] False The comparer_params should be list with a single vector: >>> grader = MatrixGrader( ... answers={ ... 'comparer_params': [ ... '[1, 1, 0]', ... '[0, 1, 1]' ... ], ... 'comparer': vector_phase_comparer ... }, ... ) >>> try: ... grader(None, '[1, 2, 3]') # doctest: +ELLIPSIS ... except StudentFacingError as error: ... print(error) Problem Configuration Error: ...to a single vector. """ # Validate that author comparer_params evaluate to a single vector if not len(comparer_params_eval) == 1 and is_vector(comparer_params_eval[0]): raise StudentFacingError('Problem Configuration Error: comparer_params ' 'should be a list of strings that evaluate to a single vector.') # We'll check that student input is in the span as target vector and that # it has the same magnitude in_span = vector_span_comparer(comparer_params_eval, student_eval, utils) expected_mag = np.linalg.norm(comparer_params_eval[0]) student_mag = np.linalg.norm(student_eval) same_magnitude = utils.within_tolerance(expected_mag, student_mag) return in_span and same_magnitude