Coverage for /usr/lib/python3/dist-packages/sympy/matrices/expressions/hadamard.py: 20%
178 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from collections import Counter
3from sympy.core import Mul, sympify
4from sympy.core.add import Add
5from sympy.core.expr import ExprBuilder
6from sympy.core.sorting import default_sort_key
7from sympy.functions.elementary.exponential import log
8from sympy.matrices.expressions.matexpr import MatrixExpr
9from sympy.matrices.expressions._shape import validate_matadd_integer as validate
10from sympy.matrices.expressions.special import ZeroMatrix, OneMatrix
11from sympy.strategies import (
12 unpack, flatten, condition, exhaust, rm_id, sort
13)
14from sympy.utilities.exceptions import sympy_deprecation_warning
17def hadamard_product(*matrices):
18 """
19 Return the elementwise (aka Hadamard) product of matrices.
21 Examples
22 ========
24 >>> from sympy import hadamard_product, MatrixSymbol
25 >>> A = MatrixSymbol('A', 2, 3)
26 >>> B = MatrixSymbol('B', 2, 3)
27 >>> hadamard_product(A)
28 A
29 >>> hadamard_product(A, B)
30 HadamardProduct(A, B)
31 >>> hadamard_product(A, B)[0, 1]
32 A[0, 1]*B[0, 1]
33 """
34 if not matrices:
35 raise TypeError("Empty Hadamard product is undefined")
36 if len(matrices) == 1:
37 return matrices[0]
38 return HadamardProduct(*matrices).doit()
41class HadamardProduct(MatrixExpr):
42 """
43 Elementwise product of matrix expressions
45 Examples
46 ========
48 Hadamard product for matrix symbols:
50 >>> from sympy import hadamard_product, HadamardProduct, MatrixSymbol
51 >>> A = MatrixSymbol('A', 5, 5)
52 >>> B = MatrixSymbol('B', 5, 5)
53 >>> isinstance(hadamard_product(A, B), HadamardProduct)
54 True
56 Notes
57 =====
59 This is a symbolic object that simply stores its argument without
60 evaluating it. To actually compute the product, use the function
61 ``hadamard_product()`` or ``HadamardProduct.doit``
62 """
63 is_HadamardProduct = True
65 def __new__(cls, *args, evaluate=False, check=None):
66 args = list(map(sympify, args))
67 if len(args) == 0:
68 # We currently don't have a way to support one-matrices of generic dimensions:
69 raise ValueError("HadamardProduct needs at least one argument")
71 if not all(isinstance(arg, MatrixExpr) for arg in args):
72 raise TypeError("Mix of Matrix and Scalar symbols")
74 if check is not None:
75 sympy_deprecation_warning(
76 "Passing check to HadamardProduct is deprecated and the check argument will be removed in a future version.",
77 deprecated_since_version="1.11",
78 active_deprecations_target='remove-check-argument-from-matrix-operations')
80 if check is not False:
81 validate(*args)
83 obj = super().__new__(cls, *args)
84 if evaluate:
85 obj = obj.doit(deep=False)
86 return obj
88 @property
89 def shape(self):
90 return self.args[0].shape
92 def _entry(self, i, j, **kwargs):
93 return Mul(*[arg._entry(i, j, **kwargs) for arg in self.args])
95 def _eval_transpose(self):
96 from sympy.matrices.expressions.transpose import transpose
97 return HadamardProduct(*list(map(transpose, self.args)))
99 def doit(self, **hints):
100 expr = self.func(*(i.doit(**hints) for i in self.args))
101 # Check for explicit matrices:
102 from sympy.matrices.matrices import MatrixBase
103 from sympy.matrices.immutable import ImmutableMatrix
105 explicit = [i for i in expr.args if isinstance(i, MatrixBase)]
106 if explicit:
107 remainder = [i for i in expr.args if i not in explicit]
108 expl_mat = ImmutableMatrix([
109 Mul.fromiter(i) for i in zip(*explicit)
110 ]).reshape(*self.shape)
111 expr = HadamardProduct(*([expl_mat] + remainder))
113 return canonicalize(expr)
115 def _eval_derivative(self, x):
116 terms = []
117 args = list(self.args)
118 for i in range(len(args)):
119 factors = args[:i] + [args[i].diff(x)] + args[i+1:]
120 terms.append(hadamard_product(*factors))
121 return Add.fromiter(terms)
123 def _eval_derivative_matrix_lines(self, x):
124 from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
125 from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
126 from sympy.matrices.expressions.matexpr import _make_matrix
128 with_x_ind = [i for i, arg in enumerate(self.args) if arg.has(x)]
129 lines = []
130 for ind in with_x_ind:
131 left_args = self.args[:ind]
132 right_args = self.args[ind+1:]
134 d = self.args[ind]._eval_derivative_matrix_lines(x)
135 hadam = hadamard_product(*(right_args + left_args))
136 diagonal = [(0, 2), (3, 4)]
137 diagonal = [e for j, e in enumerate(diagonal) if self.shape[j] != 1]
138 for i in d:
139 l1 = i._lines[i._first_line_index]
140 l2 = i._lines[i._second_line_index]
141 subexpr = ExprBuilder(
142 ArrayDiagonal,
143 [
144 ExprBuilder(
145 ArrayTensorProduct,
146 [
147 ExprBuilder(_make_matrix, [l1]),
148 hadam,
149 ExprBuilder(_make_matrix, [l2]),
150 ]
151 ),
152 *diagonal],
154 )
155 i._first_pointer_parent = subexpr.args[0].args[0].args
156 i._first_pointer_index = 0
157 i._second_pointer_parent = subexpr.args[0].args[2].args
158 i._second_pointer_index = 0
159 i._lines = [subexpr]
160 lines.append(i)
162 return lines
165# TODO Implement algorithm for rewriting Hadamard product as diagonal matrix
166# if matmul identy matrix is multiplied.
167def canonicalize(x):
168 """Canonicalize the Hadamard product ``x`` with mathematical properties.
170 Examples
171 ========
173 >>> from sympy import MatrixSymbol, HadamardProduct
174 >>> from sympy import OneMatrix, ZeroMatrix
175 >>> from sympy.matrices.expressions.hadamard import canonicalize
176 >>> from sympy import init_printing
177 >>> init_printing(use_unicode=False)
179 >>> A = MatrixSymbol('A', 2, 2)
180 >>> B = MatrixSymbol('B', 2, 2)
181 >>> C = MatrixSymbol('C', 2, 2)
183 Hadamard product associativity:
185 >>> X = HadamardProduct(A, HadamardProduct(B, C))
186 >>> X
187 A.*(B.*C)
188 >>> canonicalize(X)
189 A.*B.*C
191 Hadamard product commutativity:
193 >>> X = HadamardProduct(A, B)
194 >>> Y = HadamardProduct(B, A)
195 >>> X
196 A.*B
197 >>> Y
198 B.*A
199 >>> canonicalize(X)
200 A.*B
201 >>> canonicalize(Y)
202 A.*B
204 Hadamard product identity:
206 >>> X = HadamardProduct(A, OneMatrix(2, 2))
207 >>> X
208 A.*1
209 >>> canonicalize(X)
210 A
212 Absorbing element of Hadamard product:
214 >>> X = HadamardProduct(A, ZeroMatrix(2, 2))
215 >>> X
216 A.*0
217 >>> canonicalize(X)
218 0
220 Rewriting to Hadamard Power
222 >>> X = HadamardProduct(A, A, A)
223 >>> X
224 A.*A.*A
225 >>> canonicalize(X)
226 .3
227 A
229 Notes
230 =====
232 As the Hadamard product is associative, nested products can be flattened.
234 The Hadamard product is commutative so that factors can be sorted for
235 canonical form.
237 A matrix of only ones is an identity for Hadamard product,
238 so every matrices of only ones can be removed.
240 Any zero matrix will make the whole product a zero matrix.
242 Duplicate elements can be collected and rewritten as HadamardPower
244 References
245 ==========
247 .. [1] https://en.wikipedia.org/wiki/Hadamard_product_(matrices)
248 """
249 # Associativity
250 rule = condition(
251 lambda x: isinstance(x, HadamardProduct),
252 flatten
253 )
254 fun = exhaust(rule)
255 x = fun(x)
257 # Identity
258 fun = condition(
259 lambda x: isinstance(x, HadamardProduct),
260 rm_id(lambda x: isinstance(x, OneMatrix))
261 )
262 x = fun(x)
264 # Absorbing by Zero Matrix
265 def absorb(x):
266 if any(isinstance(c, ZeroMatrix) for c in x.args):
267 return ZeroMatrix(*x.shape)
268 else:
269 return x
270 fun = condition(
271 lambda x: isinstance(x, HadamardProduct),
272 absorb
273 )
274 x = fun(x)
276 # Rewriting with HadamardPower
277 if isinstance(x, HadamardProduct):
278 tally = Counter(x.args)
280 new_arg = []
281 for base, exp in tally.items():
282 if exp == 1:
283 new_arg.append(base)
284 else:
285 new_arg.append(HadamardPower(base, exp))
287 x = HadamardProduct(*new_arg)
289 # Commutativity
290 fun = condition(
291 lambda x: isinstance(x, HadamardProduct),
292 sort(default_sort_key)
293 )
294 x = fun(x)
296 # Unpacking
297 x = unpack(x)
298 return x
301def hadamard_power(base, exp):
302 base = sympify(base)
303 exp = sympify(exp)
304 if exp == 1:
305 return base
306 if not base.is_Matrix:
307 return base**exp
308 if exp.is_Matrix:
309 raise ValueError("cannot raise expression to a matrix")
310 return HadamardPower(base, exp)
313class HadamardPower(MatrixExpr):
314 r"""
315 Elementwise power of matrix expressions
317 Parameters
318 ==========
320 base : scalar or matrix
322 exp : scalar or matrix
324 Notes
325 =====
327 There are four definitions for the hadamard power which can be used.
328 Let's consider `A, B` as `(m, n)` matrices, and `a, b` as scalars.
330 Matrix raised to a scalar exponent:
332 .. math::
333 A^{\circ b} = \begin{bmatrix}
334 A_{0, 0}^b & A_{0, 1}^b & \cdots & A_{0, n-1}^b \\
335 A_{1, 0}^b & A_{1, 1}^b & \cdots & A_{1, n-1}^b \\
336 \vdots & \vdots & \ddots & \vdots \\
337 A_{m-1, 0}^b & A_{m-1, 1}^b & \cdots & A_{m-1, n-1}^b
338 \end{bmatrix}
340 Scalar raised to a matrix exponent:
342 .. math::
343 a^{\circ B} = \begin{bmatrix}
344 a^{B_{0, 0}} & a^{B_{0, 1}} & \cdots & a^{B_{0, n-1}} \\
345 a^{B_{1, 0}} & a^{B_{1, 1}} & \cdots & a^{B_{1, n-1}} \\
346 \vdots & \vdots & \ddots & \vdots \\
347 a^{B_{m-1, 0}} & a^{B_{m-1, 1}} & \cdots & a^{B_{m-1, n-1}}
348 \end{bmatrix}
350 Matrix raised to a matrix exponent:
352 .. math::
353 A^{\circ B} = \begin{bmatrix}
354 A_{0, 0}^{B_{0, 0}} & A_{0, 1}^{B_{0, 1}} &
355 \cdots & A_{0, n-1}^{B_{0, n-1}} \\
356 A_{1, 0}^{B_{1, 0}} & A_{1, 1}^{B_{1, 1}} &
357 \cdots & A_{1, n-1}^{B_{1, n-1}} \\
358 \vdots & \vdots &
359 \ddots & \vdots \\
360 A_{m-1, 0}^{B_{m-1, 0}} & A_{m-1, 1}^{B_{m-1, 1}} &
361 \cdots & A_{m-1, n-1}^{B_{m-1, n-1}}
362 \end{bmatrix}
364 Scalar raised to a scalar exponent:
366 .. math::
367 a^{\circ b} = a^b
368 """
370 def __new__(cls, base, exp):
371 base = sympify(base)
372 exp = sympify(exp)
374 if base.is_scalar and exp.is_scalar:
375 return base ** exp
377 if isinstance(base, MatrixExpr) and isinstance(exp, MatrixExpr):
378 validate(base, exp)
380 obj = super().__new__(cls, base, exp)
381 return obj
383 @property
384 def base(self):
385 return self._args[0]
387 @property
388 def exp(self):
389 return self._args[1]
391 @property
392 def shape(self):
393 if self.base.is_Matrix:
394 return self.base.shape
395 return self.exp.shape
397 def _entry(self, i, j, **kwargs):
398 base = self.base
399 exp = self.exp
401 if base.is_Matrix:
402 a = base._entry(i, j, **kwargs)
403 elif base.is_scalar:
404 a = base
405 else:
406 raise ValueError(
407 'The base {} must be a scalar or a matrix.'.format(base))
409 if exp.is_Matrix:
410 b = exp._entry(i, j, **kwargs)
411 elif exp.is_scalar:
412 b = exp
413 else:
414 raise ValueError(
415 'The exponent {} must be a scalar or a matrix.'.format(exp))
417 return a ** b
419 def _eval_transpose(self):
420 from sympy.matrices.expressions.transpose import transpose
421 return HadamardPower(transpose(self.base), self.exp)
423 def _eval_derivative(self, x):
424 dexp = self.exp.diff(x)
425 logbase = self.base.applyfunc(log)
426 dlbase = logbase.diff(x)
427 return hadamard_product(
428 dexp*logbase + self.exp*dlbase,
429 self
430 )
432 def _eval_derivative_matrix_lines(self, x):
433 from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
434 from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
435 from sympy.matrices.expressions.matexpr import _make_matrix
437 lr = self.base._eval_derivative_matrix_lines(x)
438 for i in lr:
439 diagonal = [(1, 2), (3, 4)]
440 diagonal = [e for j, e in enumerate(diagonal) if self.base.shape[j] != 1]
441 l1 = i._lines[i._first_line_index]
442 l2 = i._lines[i._second_line_index]
443 subexpr = ExprBuilder(
444 ArrayDiagonal,
445 [
446 ExprBuilder(
447 ArrayTensorProduct,
448 [
449 ExprBuilder(_make_matrix, [l1]),
450 self.exp*hadamard_power(self.base, self.exp-1),
451 ExprBuilder(_make_matrix, [l2]),
452 ]
453 ),
454 *diagonal],
455 validator=ArrayDiagonal._validate
456 )
457 i._first_pointer_parent = subexpr.args[0].args[0].args
458 i._first_pointer_index = 0
459 i._first_line_index = 0
460 i._second_pointer_parent = subexpr.args[0].args[2].args
461 i._second_pointer_index = 0
462 i._second_line_index = 0
463 i._lines = [subexpr]
464 return lr