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)
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())