123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- 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.matrices 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):
- # Check if the function returns a matrix, in that case, just apply
- # the function instead of creating an ElementwiseApplyFunc object:
- 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, **kwargs):
- deep = kwargs.get("deep", True)
- expr = self.expr
- if deep:
- expr = expr.doit(**kwargs)
- function = self.function
- if isinstance(function, Lambda) and function.is_identity:
- # This is a Lambda containing the identity function.
- 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()
- 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:
- # Vector:
- 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:
- # Matrix case:
- 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())
|