Example #1
0
def eval_cartesian(
    expression: sy.Expr,
    x_0: Union[float, np.ndarray],
    y_0: Union[float, np.ndarray],
) -> Union[float, np.ndarray]:
    """
    Evaluate an expression that is in in Cartesian coordinates, either
    at a single position or on a grid of positions.

    Args:
        expression: A sympy expression.
        x_0: The value(s) of :math:`x` at which to evaluate the given
            `expression`. This can either be a single float, or an array
            of arbitrary size (its shape, however, must match `y_0`).
        y_0: The value(s) of :math:`y` at which to evaluate the given
            `expression`. This can either be a single float, or an array
            of arbitrary size (its shape, however, must match `x_0`).

    Returns:
        The value of `expression` at the given position(s). The type
        and shape of the output matches the one of the input: for `x_0`,
        `y_0` as floats, a float is returned; for numpy array inputs, a
        numpy array is returned.
    """

    # Make sure that expression is a function of Cartesian coordinates
    assert is_cartesian(expression), \
        '"expression" is not in Cartesian coordinates!'

    # Make sure that x_0 and y_0 have compatible shapes
    assert ((isinstance(x_0, float) and isinstance(y_0, float)) or
            (isinstance(x_0, np.ndarray) and isinstance(y_0, np.ndarray) and
             x_0.shape == y_0.shape)), \
        '"x_0" and "y_0" must be either both float, or both numpy array ' \
        'with the same shape!'

    # If the expression is not constant, we can use sympy.lambdify() to
    # generate a numpy version of the expression, which can be used to
    # evaluate the function efficiently:
    if not expression.is_constant():

        numpy_func: Callable[..., Union[float, np.ndarray]] = \
            sy.utilities.lambdify(args=sy.symbols('x, y'),
                                  expr=expression,
                                  modules='numpy')

    # Otherwise, that is, if the expression is constant, we need to define
    # the evaluation function manually because the result of sympy.lambdify()
    # does not behave as desired (it does not vectorize properly).
    else:

        # The multiplication with _ / _ makes sure that everything that is NaN
        # in the input also is NaN in the output; non-NaN values are unchanged
        def numpy_func(_: float, __: float) -> float:
            return float(expression) * _ / _ * __ / __

        numpy_func = np.vectorize(numpy_func)

    return numpy_func(x_0, y_0)
Example #2
0
def laplace_transform_extended(expr: Expr,
                               t: Expr,
                               s: Expr,
                               fmap: Dict[Function, Function],
                               czero=True,
                               noconds=False):
    """
    Laplace transform extended to handle function symbols and their
    derivatives.
    """
    update_roc, get_roc = floating_reducer(max, 0)
    append_cond, get_cond = floating_reducer(And, True)

    def L(expr):
        result = laplace_transform_f(fmap, t, s, expr, czero=czero)
        if not isinstance(result, tuple):
            return result
        transform, roc, cond = result
        append_cond(cond)
        update_roc(roc)
        return transform

    result = traverse_linop(L, lambda expr: expr.is_constant(t), expr)
    return result if noconds else (result, get_roc(), get_cond())