|
from sympy.core.expr import ExprBuilder |
|
from sympy.core.function import (Function, FunctionClass, Lambda) |
|
from sympy.core.symbol import Dummy |
|
from sympy.core.sympify import sympify, _sympify |
|
from sympy.matrices.expressions import MatrixExpr |
|
from sympy.matrices.matrixbase import MatrixBase |
|
|
|
|
|
class ElementwiseApplyFunction(MatrixExpr): |
|
r""" |
|
Apply function to a matrix elementwise without evaluating. |
|
|
|
Examples |
|
======== |
|
|
|
It can be created by calling ``.applyfunc(<function>)`` on a matrix |
|
expression: |
|
|
|
>>> from sympy import MatrixSymbol |
|
>>> from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction |
|
>>> from sympy import exp |
|
>>> X = MatrixSymbol("X", 3, 3) |
|
>>> X.applyfunc(exp) |
|
Lambda(_d, exp(_d)).(X) |
|
|
|
Otherwise using the class constructor: |
|
|
|
>>> from sympy import eye |
|
>>> expr = ElementwiseApplyFunction(exp, eye(3)) |
|
>>> expr |
|
Lambda(_d, exp(_d)).(Matrix([ |
|
[1, 0, 0], |
|
[0, 1, 0], |
|
[0, 0, 1]])) |
|
>>> expr.doit() |
|
Matrix([ |
|
[E, 1, 1], |
|
[1, E, 1], |
|
[1, 1, E]]) |
|
|
|
Notice the difference with the real mathematical functions: |
|
|
|
>>> exp(eye(3)) |
|
Matrix([ |
|
[E, 0, 0], |
|
[0, E, 0], |
|
[0, 0, E]]) |
|
""" |
|
|
|
def __new__(cls, function, expr): |
|
expr = _sympify(expr) |
|
if not expr.is_Matrix: |
|
raise ValueError("{} must be a matrix instance.".format(expr)) |
|
|
|
if expr.shape == (1, 1): |
|
|
|
|
|
ret = function(expr) |
|
if isinstance(ret, MatrixExpr): |
|
return ret |
|
|
|
if not isinstance(function, (FunctionClass, Lambda)): |
|
d = Dummy('d') |
|
function = Lambda(d, function(d)) |
|
|
|
function = sympify(function) |
|
if not isinstance(function, (FunctionClass, Lambda)): |
|
raise ValueError( |
|
"{} should be compatible with SymPy function classes." |
|
.format(function)) |
|
|
|
if 1 not in function.nargs: |
|
raise ValueError( |
|
'{} should be able to accept 1 arguments.'.format(function)) |
|
|
|
if not isinstance(function, Lambda): |
|
d = Dummy('d') |
|
function = Lambda(d, function(d)) |
|
|
|
obj = MatrixExpr.__new__(cls, function, expr) |
|
return obj |
|
|
|
@property |
|
def function(self): |
|
return self.args[0] |
|
|
|
@property |
|
def expr(self): |
|
return self.args[1] |
|
|
|
@property |
|
def shape(self): |
|
return self.expr.shape |
|
|
|
def doit(self, **hints): |
|
deep = hints.get("deep", True) |
|
expr = self.expr |
|
if deep: |
|
expr = expr.doit(**hints) |
|
function = self.function |
|
if isinstance(function, Lambda) and function.is_identity: |
|
|
|
return expr |
|
if isinstance(expr, MatrixBase): |
|
return expr.applyfunc(self.function) |
|
elif isinstance(expr, ElementwiseApplyFunction): |
|
return ElementwiseApplyFunction( |
|
lambda x: self.function(expr.function(x)), |
|
expr.expr |
|
).doit(**hints) |
|
else: |
|
return self |
|
|
|
def _entry(self, i, j, **kwargs): |
|
return self.function(self.expr._entry(i, j, **kwargs)) |
|
|
|
def _get_function_fdiff(self): |
|
d = Dummy("d") |
|
function = self.function(d) |
|
fdiff = function.diff(d) |
|
if isinstance(fdiff, Function): |
|
fdiff = type(fdiff) |
|
else: |
|
fdiff = Lambda(d, fdiff) |
|
return fdiff |
|
|
|
def _eval_derivative(self, x): |
|
from sympy.matrices.expressions.hadamard import hadamard_product |
|
dexpr = self.expr.diff(x) |
|
fdiff = self._get_function_fdiff() |
|
return hadamard_product( |
|
dexpr, |
|
ElementwiseApplyFunction(fdiff, self.expr) |
|
) |
|
|
|
def _eval_derivative_matrix_lines(self, x): |
|
from sympy.matrices.expressions.special import Identity |
|
from sympy.tensor.array.expressions.array_expressions import ArrayContraction |
|
from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal |
|
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct |
|
|
|
fdiff = self._get_function_fdiff() |
|
lr = self.expr._eval_derivative_matrix_lines(x) |
|
ewdiff = ElementwiseApplyFunction(fdiff, self.expr) |
|
if 1 in x.shape: |
|
|
|
iscolumn = self.shape[1] == 1 |
|
for i in lr: |
|
if iscolumn: |
|
ptr1 = i.first_pointer |
|
ptr2 = Identity(self.shape[1]) |
|
else: |
|
ptr1 = Identity(self.shape[0]) |
|
ptr2 = i.second_pointer |
|
|
|
subexpr = ExprBuilder( |
|
ArrayDiagonal, |
|
[ |
|
ExprBuilder( |
|
ArrayTensorProduct, |
|
[ |
|
ewdiff, |
|
ptr1, |
|
ptr2, |
|
] |
|
), |
|
(0, 2) if iscolumn else (1, 4) |
|
], |
|
validator=ArrayDiagonal._validate |
|
) |
|
i._lines = [subexpr] |
|
i._first_pointer_parent = subexpr.args[0].args |
|
i._first_pointer_index = 1 |
|
i._second_pointer_parent = subexpr.args[0].args |
|
i._second_pointer_index = 2 |
|
else: |
|
|
|
for i in lr: |
|
ptr1 = i.first_pointer |
|
ptr2 = i.second_pointer |
|
newptr1 = Identity(ptr1.shape[1]) |
|
newptr2 = Identity(ptr2.shape[1]) |
|
subexpr = ExprBuilder( |
|
ArrayContraction, |
|
[ |
|
ExprBuilder( |
|
ArrayTensorProduct, |
|
[ptr1, newptr1, ewdiff, ptr2, newptr2] |
|
), |
|
(1, 2, 4), |
|
(5, 7, 8), |
|
], |
|
validator=ArrayContraction._validate |
|
) |
|
i._first_pointer_parent = subexpr.args[0].args |
|
i._first_pointer_index = 1 |
|
i._second_pointer_parent = subexpr.args[0].args |
|
i._second_pointer_index = 4 |
|
i._lines = [subexpr] |
|
return lr |
|
|
|
def _eval_transpose(self): |
|
from sympy.matrices.expressions.transpose import Transpose |
|
return self.func(self.function, Transpose(self.expr).doit()) |
|
|