123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- from sympy.core import Mul, sympify
- from sympy.core.add import Add
- from sympy.core.expr import ExprBuilder
- from sympy.core.sorting import default_sort_key
- from sympy.matrices.common import ShapeError
- from sympy.matrices.expressions.matexpr import MatrixExpr
- from sympy.matrices.expressions.special import ZeroMatrix, OneMatrix
- from sympy.strategies import (
- unpack, flatten, condition, exhaust, rm_id, sort
- )
- def hadamard_product(*matrices):
- """
- Return the elementwise (aka Hadamard) product of matrices.
- Examples
- ========
- >>> from sympy import hadamard_product, MatrixSymbol
- >>> A = MatrixSymbol('A', 2, 3)
- >>> B = MatrixSymbol('B', 2, 3)
- >>> hadamard_product(A)
- A
- >>> hadamard_product(A, B)
- HadamardProduct(A, B)
- >>> hadamard_product(A, B)[0, 1]
- A[0, 1]*B[0, 1]
- """
- if not matrices:
- raise TypeError("Empty Hadamard product is undefined")
- validate(*matrices)
- if len(matrices) == 1:
- return matrices[0]
- else:
- matrices = [i for i in matrices if not i.is_Identity]
- return HadamardProduct(*matrices).doit()
- class HadamardProduct(MatrixExpr):
- """
- Elementwise product of matrix expressions
- Examples
- ========
- Hadamard product for matrix symbols:
- >>> from sympy import hadamard_product, HadamardProduct, MatrixSymbol
- >>> A = MatrixSymbol('A', 5, 5)
- >>> B = MatrixSymbol('B', 5, 5)
- >>> isinstance(hadamard_product(A, B), HadamardProduct)
- True
- Notes
- =====
- This is a symbolic object that simply stores its argument without
- evaluating it. To actually compute the product, use the function
- ``hadamard_product()`` or ``HadamardProduct.doit``
- """
- is_HadamardProduct = True
- def __new__(cls, *args, evaluate=False, check=True):
- args = list(map(sympify, args))
- if check:
- validate(*args)
- obj = super().__new__(cls, *args)
- if evaluate:
- obj = obj.doit(deep=False)
- return obj
- @property
- def shape(self):
- return self.args[0].shape
- def _entry(self, i, j, **kwargs):
- return Mul(*[arg._entry(i, j, **kwargs) for arg in self.args])
- def _eval_transpose(self):
- from sympy.matrices.expressions.transpose import transpose
- return HadamardProduct(*list(map(transpose, self.args)))
- def doit(self, **ignored):
- expr = self.func(*[i.doit(**ignored) for i in self.args])
- # Check for explicit matrices:
- from sympy.matrices.matrices import MatrixBase
- from sympy.matrices.immutable import ImmutableMatrix
- explicit = [i for i in expr.args if isinstance(i, MatrixBase)]
- if explicit:
- remainder = [i for i in expr.args if i not in explicit]
- expl_mat = ImmutableMatrix([
- Mul.fromiter(i) for i in zip(*explicit)
- ]).reshape(*self.shape)
- expr = HadamardProduct(*([expl_mat] + remainder))
- return canonicalize(expr)
- def _eval_derivative(self, x):
- terms = []
- args = list(self.args)
- for i in range(len(args)):
- factors = args[:i] + [args[i].diff(x)] + args[i+1:]
- terms.append(hadamard_product(*factors))
- return Add.fromiter(terms)
- def _eval_derivative_matrix_lines(self, x):
- from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
- from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
- from sympy.matrices.expressions.matexpr import _make_matrix
- with_x_ind = [i for i, arg in enumerate(self.args) if arg.has(x)]
- lines = []
- for ind in with_x_ind:
- left_args = self.args[:ind]
- right_args = self.args[ind+1:]
- d = self.args[ind]._eval_derivative_matrix_lines(x)
- hadam = hadamard_product(*(right_args + left_args))
- diagonal = [(0, 2), (3, 4)]
- diagonal = [e for j, e in enumerate(diagonal) if self.shape[j] != 1]
- for i in d:
- l1 = i._lines[i._first_line_index]
- l2 = i._lines[i._second_line_index]
- subexpr = ExprBuilder(
- ArrayDiagonal,
- [
- ExprBuilder(
- ArrayTensorProduct,
- [
- ExprBuilder(_make_matrix, [l1]),
- hadam,
- ExprBuilder(_make_matrix, [l2]),
- ]
- ),
- *diagonal],
- )
- i._first_pointer_parent = subexpr.args[0].args[0].args
- i._first_pointer_index = 0
- i._second_pointer_parent = subexpr.args[0].args[2].args
- i._second_pointer_index = 0
- i._lines = [subexpr]
- lines.append(i)
- return lines
- def validate(*args):
- if not all(arg.is_Matrix for arg in args):
- raise TypeError("Mix of Matrix and Scalar symbols")
- A = args[0]
- for B in args[1:]:
- if A.shape != B.shape:
- raise ShapeError("Matrices %s and %s are not aligned" % (A, B))
- # TODO Implement algorithm for rewriting Hadamard product as diagonal matrix
- # if matmul identy matrix is multiplied.
- def canonicalize(x):
- """Canonicalize the Hadamard product ``x`` with mathematical properties.
- Examples
- ========
- >>> from sympy import MatrixSymbol, HadamardProduct
- >>> from sympy import OneMatrix, ZeroMatrix
- >>> from sympy.matrices.expressions.hadamard import canonicalize
- >>> from sympy import init_printing
- >>> init_printing(use_unicode=False)
- >>> A = MatrixSymbol('A', 2, 2)
- >>> B = MatrixSymbol('B', 2, 2)
- >>> C = MatrixSymbol('C', 2, 2)
- Hadamard product associativity:
- >>> X = HadamardProduct(A, HadamardProduct(B, C))
- >>> X
- A.*(B.*C)
- >>> canonicalize(X)
- A.*B.*C
- Hadamard product commutativity:
- >>> X = HadamardProduct(A, B)
- >>> Y = HadamardProduct(B, A)
- >>> X
- A.*B
- >>> Y
- B.*A
- >>> canonicalize(X)
- A.*B
- >>> canonicalize(Y)
- A.*B
- Hadamard product identity:
- >>> X = HadamardProduct(A, OneMatrix(2, 2))
- >>> X
- A.*1
- >>> canonicalize(X)
- A
- Absorbing element of Hadamard product:
- >>> X = HadamardProduct(A, ZeroMatrix(2, 2))
- >>> X
- A.*0
- >>> canonicalize(X)
- 0
- Rewriting to Hadamard Power
- >>> X = HadamardProduct(A, A, A)
- >>> X
- A.*A.*A
- >>> canonicalize(X)
- .3
- A
- Notes
- =====
- As the Hadamard product is associative, nested products can be flattened.
- The Hadamard product is commutative so that factors can be sorted for
- canonical form.
- A matrix of only ones is an identity for Hadamard product,
- so every matrices of only ones can be removed.
- Any zero matrix will make the whole product a zero matrix.
- Duplicate elements can be collected and rewritten as HadamardPower
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Hadamard_product_(matrices)
- """
- # Associativity
- rule = condition(
- lambda x: isinstance(x, HadamardProduct),
- flatten
- )
- fun = exhaust(rule)
- x = fun(x)
- # Identity
- fun = condition(
- lambda x: isinstance(x, HadamardProduct),
- rm_id(lambda x: isinstance(x, OneMatrix))
- )
- x = fun(x)
- # Absorbing by Zero Matrix
- def absorb(x):
- if any(isinstance(c, ZeroMatrix) for c in x.args):
- return ZeroMatrix(*x.shape)
- else:
- return x
- fun = condition(
- lambda x: isinstance(x, HadamardProduct),
- absorb
- )
- x = fun(x)
- # Rewriting with HadamardPower
- if isinstance(x, HadamardProduct):
- from collections import Counter
- tally = Counter(x.args)
- new_arg = []
- for base, exp in tally.items():
- if exp == 1:
- new_arg.append(base)
- else:
- new_arg.append(HadamardPower(base, exp))
- x = HadamardProduct(*new_arg)
- # Commutativity
- fun = condition(
- lambda x: isinstance(x, HadamardProduct),
- sort(default_sort_key)
- )
- x = fun(x)
- # Unpacking
- x = unpack(x)
- return x
- def hadamard_power(base, exp):
- base = sympify(base)
- exp = sympify(exp)
- if exp == 1:
- return base
- if not base.is_Matrix:
- return base**exp
- if exp.is_Matrix:
- raise ValueError("cannot raise expression to a matrix")
- return HadamardPower(base, exp)
- class HadamardPower(MatrixExpr):
- r"""
- Elementwise power of matrix expressions
- Parameters
- ==========
- base : scalar or matrix
- exp : scalar or matrix
- Notes
- =====
- There are four definitions for the hadamard power which can be used.
- Let's consider `A, B` as `(m, n)` matrices, and `a, b` as scalars.
- Matrix raised to a scalar exponent:
- .. math::
- A^{\circ b} = \begin{bmatrix}
- A_{0, 0}^b & A_{0, 1}^b & \cdots & A_{0, n-1}^b \\
- A_{1, 0}^b & A_{1, 1}^b & \cdots & A_{1, n-1}^b \\
- \vdots & \vdots & \ddots & \vdots \\
- A_{m-1, 0}^b & A_{m-1, 1}^b & \cdots & A_{m-1, n-1}^b
- \end{bmatrix}
- Scalar raised to a matrix exponent:
- .. math::
- a^{\circ B} = \begin{bmatrix}
- a^{B_{0, 0}} & a^{B_{0, 1}} & \cdots & a^{B_{0, n-1}} \\
- a^{B_{1, 0}} & a^{B_{1, 1}} & \cdots & a^{B_{1, n-1}} \\
- \vdots & \vdots & \ddots & \vdots \\
- a^{B_{m-1, 0}} & a^{B_{m-1, 1}} & \cdots & a^{B_{m-1, n-1}}
- \end{bmatrix}
- Matrix raised to a matrix exponent:
- .. math::
- A^{\circ B} = \begin{bmatrix}
- A_{0, 0}^{B_{0, 0}} & A_{0, 1}^{B_{0, 1}} &
- \cdots & A_{0, n-1}^{B_{0, n-1}} \\
- A_{1, 0}^{B_{1, 0}} & A_{1, 1}^{B_{1, 1}} &
- \cdots & A_{1, n-1}^{B_{1, n-1}} \\
- \vdots & \vdots &
- \ddots & \vdots \\
- A_{m-1, 0}^{B_{m-1, 0}} & A_{m-1, 1}^{B_{m-1, 1}} &
- \cdots & A_{m-1, n-1}^{B_{m-1, n-1}}
- \end{bmatrix}
- Scalar raised to a scalar exponent:
- .. math::
- a^{\circ b} = a^b
- """
- def __new__(cls, base, exp):
- base = sympify(base)
- exp = sympify(exp)
- if base.is_scalar and exp.is_scalar:
- return base ** exp
- if base.is_Matrix and exp.is_Matrix and base.shape != exp.shape:
- raise ValueError(
- 'The shape of the base {} and '
- 'the shape of the exponent {} do not match.'
- .format(base.shape, exp.shape)
- )
- obj = super().__new__(cls, base, exp)
- return obj
- @property
- def base(self):
- return self._args[0]
- @property
- def exp(self):
- return self._args[1]
- @property
- def shape(self):
- if self.base.is_Matrix:
- return self.base.shape
- return self.exp.shape
- def _entry(self, i, j, **kwargs):
- base = self.base
- exp = self.exp
- if base.is_Matrix:
- a = base._entry(i, j, **kwargs)
- elif base.is_scalar:
- a = base
- else:
- raise ValueError(
- 'The base {} must be a scalar or a matrix.'.format(base))
- if exp.is_Matrix:
- b = exp._entry(i, j, **kwargs)
- elif exp.is_scalar:
- b = exp
- else:
- raise ValueError(
- 'The exponent {} must be a scalar or a matrix.'.format(exp))
- return a ** b
- def _eval_transpose(self):
- from sympy.matrices.expressions.transpose import transpose
- return HadamardPower(transpose(self.base), self.exp)
- def _eval_derivative(self, x):
- from sympy.functions.elementary.exponential import log
- dexp = self.exp.diff(x)
- logbase = self.base.applyfunc(log)
- dlbase = logbase.diff(x)
- return hadamard_product(
- dexp*logbase + self.exp*dlbase,
- self
- )
- def _eval_derivative_matrix_lines(self, x):
- from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
- from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
- from sympy.matrices.expressions.matexpr import _make_matrix
- lr = self.base._eval_derivative_matrix_lines(x)
- for i in lr:
- diagonal = [(1, 2), (3, 4)]
- diagonal = [e for j, e in enumerate(diagonal) if self.base.shape[j] != 1]
- l1 = i._lines[i._first_line_index]
- l2 = i._lines[i._second_line_index]
- subexpr = ExprBuilder(
- ArrayDiagonal,
- [
- ExprBuilder(
- ArrayTensorProduct,
- [
- ExprBuilder(_make_matrix, [l1]),
- self.exp*hadamard_power(self.base, self.exp-1),
- ExprBuilder(_make_matrix, [l2]),
- ]
- ),
- *diagonal],
- validator=ArrayDiagonal._validate
- )
- i._first_pointer_parent = subexpr.args[0].args[0].args
- i._first_pointer_index = 0
- i._first_line_index = 0
- i._second_pointer_parent = subexpr.args[0].args[2].args
- i._second_pointer_index = 0
- i._second_line_index = 0
- i._lines = [subexpr]
- return lr
|