import numpy as np
import sympy as sp
from sympy.utilities.lambdify import implemented_function
from sympy.physics.mechanics import dynamicsymbols
from simupy.array import r_, Array
sinc = implemented_function(sp.Function('sinc'), lambda x: np.sinc(x/np.pi))
step = implemented_function(sp.Function('step'), lambda x: 1.0*(x >= 0))
[docs]def process_vector_args(args):
"""
A helper function to process vector arguments so callables can take
vectors or individual components. Essentially unravels the arguments.
"""
new_args = []
for arg in args:
if hasattr(arg, 'shape') and len(arg.shape) > 0:
shape = arg.shape
if (min(shape) != 1 and len(shape) == 2) or len(shape) > 2:
raise AttributeError("Arguments should only contain vectors")
for i in range(max(shape)):
if len(shape) == 1:
new_args.append(arg[i])
elif shape[0] == 1:
new_args.append(arg[0, i])
elif shape[1] == 1:
new_args.append(arg[i, 0])
elif isinstance(arg, (list, tuple)):
for element in arg:
if isinstance(element, (list, tuple)):
raise AttributeError("Arguments should not be nested " +
"lists/tuples")
new_args.append(element)
else: # hope it's atomic!
new_args.append(arg)
return tuple(new_args)
[docs]def lambdify_with_vector_args(args, expr, modules=(
{'ImmutableMatrix': np.matrix}, "numpy", {"Mod": np.mod})
):
"""
A wrapper around sympy's lambdify where process_vector_args is used so
generated callable can take arguments as either vector or individual
components
Parameters
----------
args : list-like of sympy symbols
Input arguments to the expression to call
expr : sympy expression
Expression to turn into a callable for numeric evaluation
modules : list
See lambdify documentation; passed directly as modules keyword.
"""
new_args = process_vector_args(args)
if sp.__version__ < '1.1' and hasattr(expr, '__len__'):
expr = sp.Matrix(expr)
f = sp.lambdify(new_args, expr, modules=modules)
def lambda_function_with_vector_args(*func_args):
new_func_args = process_vector_args(func_args)
return np.array(f(*new_func_args))
lambda_function_with_vector_args.__doc__ = f.__doc__
return lambda_function_with_vector_args
[docs]def grad(f, basis, for_numerical=True):
"""
Compute the symbolic gradient of a vector-valued function with respect to a
basis.
Parameters
----------
f : 1D array_like of sympy Expressions
The vector-valued function to compute the gradient of.
basis : 1D array_like of sympy symbols
The basis symbols to compute the gradient with respect to.
for_numerical : bool, optional
A placeholder for the option of numerically computing the gradient.
Returns
-------
grad : 2D array_like of sympy Expressions
The symbolic gradient.
"""
if hasattr(f, '__len__'): # as of version 1.1.1, Array isn't supported
f = sp.Matrix(f)
return f.__class__([
[
sp.diff(f[x], basis[y])
if not for_numerical or not f[x].has(sp.sign(basis[y])) else 0
for y in range(len(basis))
] for x in range(len(f))
])