コード例 #1
0
    def parse(self, expression):
        """
        If expression is in parser cache, return cached result, otherwise
        delegate to raw_parse.
        """
        expression_no_whitespace = expression.replace(' ', '')
        cache_key = expression_no_whitespace
        if expression_no_whitespace in self.cache:
            return self.cache[cache_key]

        try:
            parsed = self.raw_parse(expression_no_whitespace)
        except ParseException:
            msg = "Invalid Input: Could not parse '{}' as a formula"
            raise UnableToParse(msg.format(expression))

        self.cache[cache_key] = parsed
        return parsed
コード例 #2
0
    def eval_array(parse_result, metadata_dict):
        """
        Takes in a list of evaluated expressions and returns it as a MathArray.
        May mutate metadata_dict.

        If passed a list of numpy arrays, generates a matrix/tensor/etc.

        Arguments:
            parse_result: A list containing each element of the array
            metadata_dict: A dictionary with key 'max_array_dim_used', whose
                value should be an integer. If the result of eval_array has higher
                dimension than 'max_array_dim_used', this value will be updated.

        Usage
        =====
        Returns MathArray instances and updates metadata_dict['max_array_dim_used']
        if needed:
        >>> metadata_dict = { 'max_array_dim_used': 0 }
        >>> MathExpression.eval_array([1, 2, 3], metadata_dict)
        MathArray([1, 2, 3])
        >>> metadata_dict['max_array_dim_used']
        1

        If metadata_dict['max_array_dim_used'] is larger than returned array value,
        then metadata_dict is not updated:
        >>> metadata_dict = { 'max_array_dim_used': 2 }
        >>> MathExpression.eval_array([1, 2, 3], metadata_dict)
        MathArray([1, 2, 3])
        >>> metadata_dict['max_array_dim_used']
        2

        >>> metadata_dict = { 'max_array_dim_used': 0 }
        >>> MathExpression.eval_array([         # doctest: +NORMALIZE_WHITESPACE
        ...     [1 , 2],
        ...     [3, 4]
        ... ], metadata_dict)
        MathArray([[1,  2],
                [3,  4]])

        In practice, this is called recursively:
        >>> metadata_dict = { 'max_array_dim_used': 0 }
        >>> MathExpression.eval_array([         # doctest: +NORMALIZE_WHITESPACE
        ...     MathExpression.eval_array([1, 2, 3], metadata_dict),
        ...     MathExpression.eval_array([4, 5, 6], metadata_dict)
        ... ], metadata_dict)
        MathArray([[1, 2, 3],
               [4, 5, 6]])
        >>> metadata_dict['max_array_dim_used']
        2

        One complex entry will convert everything to complex:
        >>> metadata_dict = { 'max_array_dim_used': 0 }
        >>> MathExpression.eval_array([         # doctest: +NORMALIZE_WHITESPACE
        ...     MathExpression.eval_array([1, 2j, 3], metadata_dict),
        ...     MathExpression.eval_array([4, 5, 6], metadata_dict)
        ... ], metadata_dict)
        MathArray([[ 1.+0.j,  0.+2.j,  3.+0.j],
               [ 4.+0.j,  5.+0.j,  6.+0.j]])

        We try to detect shape errors:
        >>> metadata_dict = { 'max_array_dim_used': 0 }
        >>> try:                                            # doctest: +ELLIPSIS
        ...     MathExpression.eval_array([
        ...         MathExpression.eval_array([1, 2, 3], metadata_dict),
        ...         4
        ...     ], metadata_dict)
        ... except UnableToParse as error:
        ...     print(error)
        Unable to parse vector/matrix. If you're trying ...
        >>> metadata_dict = { 'max_array_dim_used': 0 }
        >>> try:                                            # doctest: +ELLIPSIS
        ...     MathExpression.eval_array([
        ...         2.0,
        ...         MathExpression.eval_array([1, 2, 3], metadata_dict),
        ...         4
        ...     ], metadata_dict)
        ... except UnableToParse as error:
        ...     print(error)
        Unable to parse vector/matrix. If you're trying ...
        """
        shape_message = ("Unable to parse vector/matrix. If you're trying to "
                         "enter a matrix, this is most likely caused by an "
                         "unequal number of elements in each row.")

        try:
            array = MathArray(parse_result)
        except ValueError:
            # This happens, for example, with np.array([1, 2, [3]])
            # when using numpy version 1.6
            raise UnableToParse(shape_message)

        if array.dtype == 'object':
            # This happens, for example, with np.array([[1], 2, 3]),
            # OR with with np.array([1, 2, [3]]) in recent versions of numpy
            raise UnableToParse(shape_message)

        if array.ndim > metadata_dict['max_array_dim_used']:
            metadata_dict['max_array_dim_used'] = array.ndim

        return array
コード例 #3
0
def evaluator(formula,
              variables=DEFAULT_VARIABLES,
              functions=DEFAULT_FUNCTIONS,
              suffixes=DEFAULT_SUFFIXES,
              max_array_dim=None,
              allow_inf=False):
    """
    Evaluate an expression; that is, take a string of math and return a float.

    Arguments
    =========
    - formula (str): The formula to be evaluated
    Pass a scope consisting of variables, functions, and suffixes:
    - variables (dict): maps strings to variable values, defaults to DEFAULT_VARIABLES
    - functions (dict): maps strings to functions, defaults to DEFAULT_FUNCTIONS
    - suffixes (dict): maps strings to suffix values, defaults to DEFAULT_SUFFIXES
    Also:
    - max_array_dim: Maximum dimension of MathArrays
    - allow_inf: Whether to raise an error if the evaluator encounters an infinity

    NOTE: Everything is case sensitive (this is different to edX!)

    Usage
    =====
    Evaluates the formula and records usage of functions/variables/suffixes:
    >>> result = evaluator("1+1", {}, {}, {})
    >>> expected = ( 2.0 , EvalMetaData(
    ...     variables_used=set(),
    ...     functions_used=set(),
    ...     suffixes_used=set(),
    ...     max_array_dim_used=0
    ... ))
    >>> result == expected
    True
    >>> result = evaluator("square(x) + 5k",
    ...     variables={'x':5, 'y': 10},
    ...     functions={'square': lambda x: x**2, 'cube': lambda x: x**3},
    ...     suffixes={'%': 0.01, 'k': 1000  })
    >>> expected = ( 5025.0 , EvalMetaData(
    ...     variables_used=set(['x']),
    ...     functions_used=set(['square']),
    ...     suffixes_used=set(['k']),
    ...     max_array_dim_used=0
    ... ))
    >>> result == expected
    True

    Empty submissions evaluate to nan:
    >>> evaluator("")[0]
    nan

    Submissions that generate infinities will raise an error:
    >>> try:                                                # doctest: +ELLIPSIS
    ...     evaluator("inf", variables={'inf': float('inf')})[0]
    ... except CalcOverflowError as error:
    ...     print(error)
    Numerical overflow occurred. Does your expression generate ...

    Unless you specify that infinity is ok:
    >>> evaluator("inf", variables={'inf': float('inf')}, allow_inf=True)[0]
    inf
    """

    empty_usage = EvalMetaData(variables_used=set(),
                               functions_used=set(),
                               suffixes_used=set(),
                               max_array_dim_used=0)
    if formula is None:
        # No need to go further.
        return float('nan'), empty_usage
    formula = formula.strip()
    if formula == "":
        # No need to go further.
        return float('nan'), empty_usage

    parsed = parse(formula)
    result, eval_metadata = parsed.eval(variables,
                                        functions,
                                        suffixes,
                                        allow_inf=allow_inf)

    # Were vectors/matrices/tensors used when they shouldn't have been?
    if max_array_dim is not None and eval_metadata.max_array_dim_used > max_array_dim:
        if max_array_dim == 0:
            msg = "Vector and matrix expressions have been forbidden in this entry."
        elif max_array_dim == 1:
            msg = "Matrix expressions have been forbidden in this entry."
        else:
            msg = "Tensor expressions have been forbidden in this entry."
        raise UnableToParse(msg)

    # Return the result of the evaluation, as well as the set of functions used
    return result, eval_metadata