Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
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