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

1from collections import Counter 

2 

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 

15 

16 

17def hadamard_product(*matrices): 

18 """ 

19 Return the elementwise (aka Hadamard) product of matrices. 

20 

21 Examples 

22 ======== 

23 

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() 

39 

40 

41class HadamardProduct(MatrixExpr): 

42 """ 

43 Elementwise product of matrix expressions 

44 

45 Examples 

46 ======== 

47 

48 Hadamard product for matrix symbols: 

49 

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 

55 

56 Notes 

57 ===== 

58 

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 

64 

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") 

70 

71 if not all(isinstance(arg, MatrixExpr) for arg in args): 

72 raise TypeError("Mix of Matrix and Scalar symbols") 

73 

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') 

79 

80 if check is not False: 

81 validate(*args) 

82 

83 obj = super().__new__(cls, *args) 

84 if evaluate: 

85 obj = obj.doit(deep=False) 

86 return obj 

87 

88 @property 

89 def shape(self): 

90 return self.args[0].shape 

91 

92 def _entry(self, i, j, **kwargs): 

93 return Mul(*[arg._entry(i, j, **kwargs) for arg in self.args]) 

94 

95 def _eval_transpose(self): 

96 from sympy.matrices.expressions.transpose import transpose 

97 return HadamardProduct(*list(map(transpose, self.args))) 

98 

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 

104 

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)) 

112 

113 return canonicalize(expr) 

114 

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) 

122 

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 

127 

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:] 

133 

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], 

153 

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) 

161 

162 return lines 

163 

164 

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. 

169 

170 Examples 

171 ======== 

172 

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) 

178 

179 >>> A = MatrixSymbol('A', 2, 2) 

180 >>> B = MatrixSymbol('B', 2, 2) 

181 >>> C = MatrixSymbol('C', 2, 2) 

182 

183 Hadamard product associativity: 

184 

185 >>> X = HadamardProduct(A, HadamardProduct(B, C)) 

186 >>> X 

187 A.*(B.*C) 

188 >>> canonicalize(X) 

189 A.*B.*C 

190 

191 Hadamard product commutativity: 

192 

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 

203 

204 Hadamard product identity: 

205 

206 >>> X = HadamardProduct(A, OneMatrix(2, 2)) 

207 >>> X 

208 A.*1 

209 >>> canonicalize(X) 

210 A 

211 

212 Absorbing element of Hadamard product: 

213 

214 >>> X = HadamardProduct(A, ZeroMatrix(2, 2)) 

215 >>> X 

216 A.*0 

217 >>> canonicalize(X) 

218 0 

219 

220 Rewriting to Hadamard Power 

221 

222 >>> X = HadamardProduct(A, A, A) 

223 >>> X 

224 A.*A.*A 

225 >>> canonicalize(X) 

226 .3 

227 A 

228 

229 Notes 

230 ===== 

231 

232 As the Hadamard product is associative, nested products can be flattened. 

233 

234 The Hadamard product is commutative so that factors can be sorted for 

235 canonical form. 

236 

237 A matrix of only ones is an identity for Hadamard product, 

238 so every matrices of only ones can be removed. 

239 

240 Any zero matrix will make the whole product a zero matrix. 

241 

242 Duplicate elements can be collected and rewritten as HadamardPower 

243 

244 References 

245 ========== 

246 

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) 

256 

257 # Identity 

258 fun = condition( 

259 lambda x: isinstance(x, HadamardProduct), 

260 rm_id(lambda x: isinstance(x, OneMatrix)) 

261 ) 

262 x = fun(x) 

263 

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) 

275 

276 # Rewriting with HadamardPower 

277 if isinstance(x, HadamardProduct): 

278 tally = Counter(x.args) 

279 

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)) 

286 

287 x = HadamardProduct(*new_arg) 

288 

289 # Commutativity 

290 fun = condition( 

291 lambda x: isinstance(x, HadamardProduct), 

292 sort(default_sort_key) 

293 ) 

294 x = fun(x) 

295 

296 # Unpacking 

297 x = unpack(x) 

298 return x 

299 

300 

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) 

311 

312 

313class HadamardPower(MatrixExpr): 

314 r""" 

315 Elementwise power of matrix expressions 

316 

317 Parameters 

318 ========== 

319 

320 base : scalar or matrix 

321 

322 exp : scalar or matrix 

323 

324 Notes 

325 ===== 

326 

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. 

329 

330 Matrix raised to a scalar exponent: 

331 

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} 

339 

340 Scalar raised to a matrix exponent: 

341 

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} 

349 

350 Matrix raised to a matrix exponent: 

351 

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} 

363 

364 Scalar raised to a scalar exponent: 

365 

366 .. math:: 

367 a^{\circ b} = a^b 

368 """ 

369 

370 def __new__(cls, base, exp): 

371 base = sympify(base) 

372 exp = sympify(exp) 

373 

374 if base.is_scalar and exp.is_scalar: 

375 return base ** exp 

376 

377 if isinstance(base, MatrixExpr) and isinstance(exp, MatrixExpr): 

378 validate(base, exp) 

379 

380 obj = super().__new__(cls, base, exp) 

381 return obj 

382 

383 @property 

384 def base(self): 

385 return self._args[0] 

386 

387 @property 

388 def exp(self): 

389 return self._args[1] 

390 

391 @property 

392 def shape(self): 

393 if self.base.is_Matrix: 

394 return self.base.shape 

395 return self.exp.shape 

396 

397 def _entry(self, i, j, **kwargs): 

398 base = self.base 

399 exp = self.exp 

400 

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)) 

408 

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)) 

416 

417 return a ** b 

418 

419 def _eval_transpose(self): 

420 from sympy.matrices.expressions.transpose import transpose 

421 return HadamardPower(transpose(self.base), self.exp) 

422 

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 ) 

431 

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 

436 

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