Coverage for /usr/lib/python3/dist-packages/scipy/linalg/_matfuncs.py: 15%

212 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

1# 

2# Author: Travis Oliphant, March 2002 

3# 

4from itertools import product 

5 

6import numpy as np 

7from numpy import (Inf, dot, diag, prod, logical_not, ravel, transpose, 

8 conjugate, absolute, amax, sign, isfinite, triu) 

9from numpy.lib.scimath import sqrt as csqrt 

10 

11# Local imports 

12from scipy.linalg import LinAlgError, bandwidth 

13from ._misc import norm 

14from ._basic import solve, inv 

15from ._decomp_svd import svd 

16from ._decomp_schur import schur, rsf2csf 

17from ._expm_frechet import expm_frechet, expm_cond 

18from ._matfuncs_sqrtm import sqrtm 

19from ._matfuncs_expm import pick_pade_structure, pade_UV_calc 

20 

21__all__ = ['expm', 'cosm', 'sinm', 'tanm', 'coshm', 'sinhm', 'tanhm', 'logm', 

22 'funm', 'signm', 'sqrtm', 'fractional_matrix_power', 'expm_frechet', 

23 'expm_cond', 'khatri_rao'] 

24 

25eps = np.finfo('d').eps 

26feps = np.finfo('f').eps 

27 

28_array_precision = {'i': 1, 'l': 1, 'f': 0, 'd': 1, 'F': 0, 'D': 1} 

29 

30 

31############################################################################### 

32# Utility functions. 

33 

34 

35def _asarray_square(A): 

36 """ 

37 Wraps asarray with the extra requirement that the input be a square matrix. 

38 

39 The motivation is that the matfuncs module has real functions that have 

40 been lifted to square matrix functions. 

41 

42 Parameters 

43 ---------- 

44 A : array_like 

45 A square matrix. 

46 

47 Returns 

48 ------- 

49 out : ndarray 

50 An ndarray copy or view or other representation of A. 

51 

52 """ 

53 A = np.asarray(A) 

54 if len(A.shape) != 2 or A.shape[0] != A.shape[1]: 

55 raise ValueError('expected square array_like input') 

56 return A 

57 

58 

59def _maybe_real(A, B, tol=None): 

60 """ 

61 Return either B or the real part of B, depending on properties of A and B. 

62 

63 The motivation is that B has been computed as a complicated function of A, 

64 and B may be perturbed by negligible imaginary components. 

65 If A is real and B is complex with small imaginary components, 

66 then return a real copy of B. The assumption in that case would be that 

67 the imaginary components of B are numerical artifacts. 

68 

69 Parameters 

70 ---------- 

71 A : ndarray 

72 Input array whose type is to be checked as real vs. complex. 

73 B : ndarray 

74 Array to be returned, possibly without its imaginary part. 

75 tol : float 

76 Absolute tolerance. 

77 

78 Returns 

79 ------- 

80 out : real or complex array 

81 Either the input array B or only the real part of the input array B. 

82 

83 """ 

84 # Note that booleans and integers compare as real. 

85 if np.isrealobj(A) and np.iscomplexobj(B): 

86 if tol is None: 

87 tol = {0:feps*1e3, 1:eps*1e6}[_array_precision[B.dtype.char]] 

88 if np.allclose(B.imag, 0.0, atol=tol): 

89 B = B.real 

90 return B 

91 

92 

93############################################################################### 

94# Matrix functions. 

95 

96 

97def fractional_matrix_power(A, t): 

98 """ 

99 Compute the fractional power of a matrix. 

100 

101 Proceeds according to the discussion in section (6) of [1]_. 

102 

103 Parameters 

104 ---------- 

105 A : (N, N) array_like 

106 Matrix whose fractional power to evaluate. 

107 t : float 

108 Fractional power. 

109 

110 Returns 

111 ------- 

112 X : (N, N) array_like 

113 The fractional power of the matrix. 

114 

115 References 

116 ---------- 

117 .. [1] Nicholas J. Higham and Lijing lin (2011) 

118 "A Schur-Pade Algorithm for Fractional Powers of a Matrix." 

119 SIAM Journal on Matrix Analysis and Applications, 

120 32 (3). pp. 1056-1078. ISSN 0895-4798 

121 

122 Examples 

123 -------- 

124 >>> import numpy as np 

125 >>> from scipy.linalg import fractional_matrix_power 

126 >>> a = np.array([[1.0, 3.0], [1.0, 4.0]]) 

127 >>> b = fractional_matrix_power(a, 0.5) 

128 >>> b 

129 array([[ 0.75592895, 1.13389342], 

130 [ 0.37796447, 1.88982237]]) 

131 >>> np.dot(b, b) # Verify square root 

132 array([[ 1., 3.], 

133 [ 1., 4.]]) 

134 

135 """ 

136 # This fixes some issue with imports; 

137 # this function calls onenormest which is in scipy.sparse. 

138 A = _asarray_square(A) 

139 import scipy.linalg._matfuncs_inv_ssq 

140 return scipy.linalg._matfuncs_inv_ssq._fractional_matrix_power(A, t) 

141 

142 

143def logm(A, disp=True): 

144 """ 

145 Compute matrix logarithm. 

146 

147 The matrix logarithm is the inverse of 

148 expm: expm(logm(`A`)) == `A` 

149 

150 Parameters 

151 ---------- 

152 A : (N, N) array_like 

153 Matrix whose logarithm to evaluate 

154 disp : bool, optional 

155 Print warning if error in the result is estimated large 

156 instead of returning estimated error. (Default: True) 

157 

158 Returns 

159 ------- 

160 logm : (N, N) ndarray 

161 Matrix logarithm of `A` 

162 errest : float 

163 (if disp == False) 

164 

165 1-norm of the estimated error, ||err||_1 / ||A||_1 

166 

167 References 

168 ---------- 

169 .. [1] Awad H. Al-Mohy and Nicholas J. Higham (2012) 

170 "Improved Inverse Scaling and Squaring Algorithms 

171 for the Matrix Logarithm." 

172 SIAM Journal on Scientific Computing, 34 (4). C152-C169. 

173 ISSN 1095-7197 

174 

175 .. [2] Nicholas J. Higham (2008) 

176 "Functions of Matrices: Theory and Computation" 

177 ISBN 978-0-898716-46-7 

178 

179 .. [3] Nicholas J. Higham and Lijing lin (2011) 

180 "A Schur-Pade Algorithm for Fractional Powers of a Matrix." 

181 SIAM Journal on Matrix Analysis and Applications, 

182 32 (3). pp. 1056-1078. ISSN 0895-4798 

183 

184 Examples 

185 -------- 

186 >>> import numpy as np 

187 >>> from scipy.linalg import logm, expm 

188 >>> a = np.array([[1.0, 3.0], [1.0, 4.0]]) 

189 >>> b = logm(a) 

190 >>> b 

191 array([[-1.02571087, 2.05142174], 

192 [ 0.68380725, 1.02571087]]) 

193 >>> expm(b) # Verify expm(logm(a)) returns a 

194 array([[ 1., 3.], 

195 [ 1., 4.]]) 

196 

197 """ 

198 A = _asarray_square(A) 

199 # Avoid circular import ... this is OK, right? 

200 import scipy.linalg._matfuncs_inv_ssq 

201 F = scipy.linalg._matfuncs_inv_ssq._logm(A) 

202 F = _maybe_real(A, F) 

203 errtol = 1000*eps 

204 #TODO use a better error approximation 

205 errest = norm(expm(F)-A,1) / norm(A,1) 

206 if disp: 

207 if not isfinite(errest) or errest >= errtol: 

208 print("logm result may be inaccurate, approximate err =", errest) 

209 return F 

210 else: 

211 return F, errest 

212 

213 

214def expm(A): 

215 """Compute the matrix exponential of an array. 

216 

217 Parameters 

218 ---------- 

219 A : ndarray 

220 Input with last two dimensions are square ``(..., n, n)``. 

221 

222 Returns 

223 ------- 

224 eA : ndarray 

225 The resulting matrix exponential with the same shape of ``A`` 

226 

227 Notes 

228 ----- 

229 Implements the algorithm given in [1], which is essentially a Pade 

230 approximation with a variable order that is decided based on the array 

231 data. 

232 

233 For input with size ``n``, the memory usage is in the worst case in the 

234 order of ``8*(n**2)``. If the input data is not of single and double 

235 precision of real and complex dtypes, it is copied to a new array. 

236 

237 For cases ``n >= 400``, the exact 1-norm computation cost, breaks even with 

238 1-norm estimation and from that point on the estimation scheme given in 

239 [2] is used to decide on the approximation order. 

240 

241 References 

242 ---------- 

243 .. [1] Awad H. Al-Mohy and Nicholas J. Higham, (2009), "A New Scaling 

244 and Squaring Algorithm for the Matrix Exponential", SIAM J. Matrix 

245 Anal. Appl. 31(3):970-989, :doi:`10.1137/09074721X` 

246 

247 .. [2] Nicholas J. Higham and Francoise Tisseur (2000), "A Block Algorithm 

248 for Matrix 1-Norm Estimation, with an Application to 1-Norm 

249 Pseudospectra." SIAM J. Matrix Anal. Appl. 21(4):1185-1201, 

250 :doi:`10.1137/S0895479899356080` 

251 

252 Examples 

253 -------- 

254 >>> import numpy as np 

255 >>> from scipy.linalg import expm, sinm, cosm 

256 

257 Matrix version of the formula exp(0) = 1: 

258 

259 >>> expm(np.zeros((3, 2, 2))) 

260 array([[[1., 0.], 

261 [0., 1.]], 

262 <BLANKLINE> 

263 [[1., 0.], 

264 [0., 1.]], 

265 <BLANKLINE> 

266 [[1., 0.], 

267 [0., 1.]]]) 

268 

269 Euler's identity (exp(i*theta) = cos(theta) + i*sin(theta)) 

270 applied to a matrix: 

271 

272 >>> a = np.array([[1.0, 2.0], [-1.0, 3.0]]) 

273 >>> expm(1j*a) 

274 array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j], 

275 [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]]) 

276 >>> cosm(a) + 1j*sinm(a) 

277 array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j], 

278 [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]]) 

279 

280 """ 

281 a = np.asarray(A) 

282 if a.size == 1 and a.ndim < 2: 

283 return np.array([[np.exp(a.item())]]) 

284 

285 if a.ndim < 2: 

286 raise LinAlgError('The input array must be at least two-dimensional') 

287 if a.shape[-1] != a.shape[-2]: 

288 raise LinAlgError('Last 2 dimensions of the array must be square') 

289 n = a.shape[-1] 

290 # Empty array 

291 if min(*a.shape) == 0: 

292 return np.empty_like(a) 

293 

294 # Scalar case 

295 if a.shape[-2:] == (1, 1): 

296 return np.exp(a) 

297 

298 if not np.issubdtype(a.dtype, np.inexact): 

299 a = a.astype(float) 

300 elif a.dtype == np.float16: 

301 a = a.astype(np.float32) 

302 

303 # Explicit formula for 2x2 case, formula (2.2) in [1] 

304 # without Kahan's method numerical instabilities can occur. 

305 if a.shape[-2:] == (2, 2): 

306 a1, a2, a3, a4 = (a[..., [0], [0]], 

307 a[..., [0], [1]], 

308 a[..., [1], [0]], 

309 a[..., [1], [1]]) 

310 mu = csqrt((a1-a4)**2 + 4*a2*a3)/2. # csqrt slow but handles neg.vals 

311 

312 eApD2 = np.exp((a1+a4)/2.) 

313 AmD2 = (a1 - a4)/2. 

314 coshMu = np.cosh(mu) 

315 sinchMu = np.ones_like(coshMu) 

316 mask = mu != 0 

317 sinchMu[mask] = np.sinh(mu[mask]) / mu[mask] 

318 eA = np.empty((a.shape), dtype=mu.dtype) 

319 eA[..., [0], [0]] = eApD2 * (coshMu + AmD2*sinchMu) 

320 eA[..., [0], [1]] = eApD2 * a2 * sinchMu 

321 eA[..., [1], [0]] = eApD2 * a3 * sinchMu 

322 eA[..., [1], [1]] = eApD2 * (coshMu - AmD2*sinchMu) 

323 if np.isrealobj(a): 

324 return eA.real 

325 return eA 

326 

327 # larger problem with unspecified stacked dimensions. 

328 n = a.shape[-1] 

329 eA = np.empty(a.shape, dtype=a.dtype) 

330 # working memory to hold intermediate arrays 

331 Am = np.empty((5, n, n), dtype=a.dtype) 

332 

333 # Main loop to go through the slices of an ndarray and passing to expm 

334 for ind in product(*[range(x) for x in a.shape[:-2]]): 

335 aw = a[ind] 

336 

337 lu = bandwidth(aw) 

338 if not any(lu): # a is diagonal? 

339 eA[ind] = np.diag(np.exp(np.diag(aw))) 

340 continue 

341 

342 # Generic/triangular case; copy the slice into scratch and send. 

343 # Am will be mutated by pick_pade_structure 

344 Am[0, :, :] = aw 

345 m, s = pick_pade_structure(Am) 

346 

347 if s != 0: # scaling needed 

348 Am[:4] *= [[[2**(-s)]], [[4**(-s)]], [[16**(-s)]], [[64**(-s)]]] 

349 

350 pade_UV_calc(Am, n, m) 

351 eAw = Am[0] 

352 

353 if s != 0: # squaring needed 

354 

355 if (lu[1] == 0) or (lu[0] == 0): # lower/upper triangular 

356 # This branch implements Code Fragment 2.1 of [1] 

357 

358 diag_aw = np.diag(aw) 

359 # einsum returns a writable view 

360 np.einsum('ii->i', eAw)[:] = np.exp(diag_aw * 2**(-s)) 

361 # super/sub diagonal 

362 sd = np.diag(aw, k=-1 if lu[1] == 0 else 1) 

363 

364 for i in range(s-1, -1, -1): 

365 eAw = eAw @ eAw 

366 

367 # diagonal 

368 np.einsum('ii->i', eAw)[:] = np.exp(diag_aw * 2.**(-i)) 

369 exp_sd = _exp_sinch(diag_aw * (2.**(-i))) * (sd * 2**(-i)) 

370 if lu[1] == 0: # lower 

371 np.einsum('ii->i', eAw[1:, :-1])[:] = exp_sd 

372 else: # upper 

373 np.einsum('ii->i', eAw[:-1, 1:])[:] = exp_sd 

374 

375 else: # generic 

376 for _ in range(s): 

377 eAw = eAw @ eAw 

378 

379 # Zero out the entries from np.empty in case of triangular input 

380 if (lu[0] == 0) or (lu[1] == 0): 

381 eA[ind] = np.triu(eAw) if lu[0] == 0 else np.tril(eAw) 

382 else: 

383 eA[ind] = eAw 

384 

385 return eA 

386 

387 

388def _exp_sinch(x): 

389 # Higham's formula (10.42), might overflow, see GH-11839 

390 lexp_diff = np.diff(np.exp(x)) 

391 l_diff = np.diff(x) 

392 mask_z = l_diff == 0. 

393 lexp_diff[~mask_z] /= l_diff[~mask_z] 

394 lexp_diff[mask_z] = np.exp(x[:-1][mask_z]) 

395 return lexp_diff 

396 

397 

398def cosm(A): 

399 """ 

400 Compute the matrix cosine. 

401 

402 This routine uses expm to compute the matrix exponentials. 

403 

404 Parameters 

405 ---------- 

406 A : (N, N) array_like 

407 Input array 

408 

409 Returns 

410 ------- 

411 cosm : (N, N) ndarray 

412 Matrix cosine of A 

413 

414 Examples 

415 -------- 

416 >>> import numpy as np 

417 >>> from scipy.linalg import expm, sinm, cosm 

418 

419 Euler's identity (exp(i*theta) = cos(theta) + i*sin(theta)) 

420 applied to a matrix: 

421 

422 >>> a = np.array([[1.0, 2.0], [-1.0, 3.0]]) 

423 >>> expm(1j*a) 

424 array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j], 

425 [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]]) 

426 >>> cosm(a) + 1j*sinm(a) 

427 array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j], 

428 [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]]) 

429 

430 """ 

431 A = _asarray_square(A) 

432 if np.iscomplexobj(A): 

433 return 0.5*(expm(1j*A) + expm(-1j*A)) 

434 else: 

435 return expm(1j*A).real 

436 

437 

438def sinm(A): 

439 """ 

440 Compute the matrix sine. 

441 

442 This routine uses expm to compute the matrix exponentials. 

443 

444 Parameters 

445 ---------- 

446 A : (N, N) array_like 

447 Input array. 

448 

449 Returns 

450 ------- 

451 sinm : (N, N) ndarray 

452 Matrix sine of `A` 

453 

454 Examples 

455 -------- 

456 >>> import numpy as np 

457 >>> from scipy.linalg import expm, sinm, cosm 

458 

459 Euler's identity (exp(i*theta) = cos(theta) + i*sin(theta)) 

460 applied to a matrix: 

461 

462 >>> a = np.array([[1.0, 2.0], [-1.0, 3.0]]) 

463 >>> expm(1j*a) 

464 array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j], 

465 [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]]) 

466 >>> cosm(a) + 1j*sinm(a) 

467 array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j], 

468 [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]]) 

469 

470 """ 

471 A = _asarray_square(A) 

472 if np.iscomplexobj(A): 

473 return -0.5j*(expm(1j*A) - expm(-1j*A)) 

474 else: 

475 return expm(1j*A).imag 

476 

477 

478def tanm(A): 

479 """ 

480 Compute the matrix tangent. 

481 

482 This routine uses expm to compute the matrix exponentials. 

483 

484 Parameters 

485 ---------- 

486 A : (N, N) array_like 

487 Input array. 

488 

489 Returns 

490 ------- 

491 tanm : (N, N) ndarray 

492 Matrix tangent of `A` 

493 

494 Examples 

495 -------- 

496 >>> import numpy as np 

497 >>> from scipy.linalg import tanm, sinm, cosm 

498 >>> a = np.array([[1.0, 3.0], [1.0, 4.0]]) 

499 >>> t = tanm(a) 

500 >>> t 

501 array([[ -2.00876993, -8.41880636], 

502 [ -2.80626879, -10.42757629]]) 

503 

504 Verify tanm(a) = sinm(a).dot(inv(cosm(a))) 

505 

506 >>> s = sinm(a) 

507 >>> c = cosm(a) 

508 >>> s.dot(np.linalg.inv(c)) 

509 array([[ -2.00876993, -8.41880636], 

510 [ -2.80626879, -10.42757629]]) 

511 

512 """ 

513 A = _asarray_square(A) 

514 return _maybe_real(A, solve(cosm(A), sinm(A))) 

515 

516 

517def coshm(A): 

518 """ 

519 Compute the hyperbolic matrix cosine. 

520 

521 This routine uses expm to compute the matrix exponentials. 

522 

523 Parameters 

524 ---------- 

525 A : (N, N) array_like 

526 Input array. 

527 

528 Returns 

529 ------- 

530 coshm : (N, N) ndarray 

531 Hyperbolic matrix cosine of `A` 

532 

533 Examples 

534 -------- 

535 >>> import numpy as np 

536 >>> from scipy.linalg import tanhm, sinhm, coshm 

537 >>> a = np.array([[1.0, 3.0], [1.0, 4.0]]) 

538 >>> c = coshm(a) 

539 >>> c 

540 array([[ 11.24592233, 38.76236492], 

541 [ 12.92078831, 50.00828725]]) 

542 

543 Verify tanhm(a) = sinhm(a).dot(inv(coshm(a))) 

544 

545 >>> t = tanhm(a) 

546 >>> s = sinhm(a) 

547 >>> t - s.dot(np.linalg.inv(c)) 

548 array([[ 2.72004641e-15, 4.55191440e-15], 

549 [ 0.00000000e+00, -5.55111512e-16]]) 

550 

551 """ 

552 A = _asarray_square(A) 

553 return _maybe_real(A, 0.5 * (expm(A) + expm(-A))) 

554 

555 

556def sinhm(A): 

557 """ 

558 Compute the hyperbolic matrix sine. 

559 

560 This routine uses expm to compute the matrix exponentials. 

561 

562 Parameters 

563 ---------- 

564 A : (N, N) array_like 

565 Input array. 

566 

567 Returns 

568 ------- 

569 sinhm : (N, N) ndarray 

570 Hyperbolic matrix sine of `A` 

571 

572 Examples 

573 -------- 

574 >>> import numpy as np 

575 >>> from scipy.linalg import tanhm, sinhm, coshm 

576 >>> a = np.array([[1.0, 3.0], [1.0, 4.0]]) 

577 >>> s = sinhm(a) 

578 >>> s 

579 array([[ 10.57300653, 39.28826594], 

580 [ 13.09608865, 49.86127247]]) 

581 

582 Verify tanhm(a) = sinhm(a).dot(inv(coshm(a))) 

583 

584 >>> t = tanhm(a) 

585 >>> c = coshm(a) 

586 >>> t - s.dot(np.linalg.inv(c)) 

587 array([[ 2.72004641e-15, 4.55191440e-15], 

588 [ 0.00000000e+00, -5.55111512e-16]]) 

589 

590 """ 

591 A = _asarray_square(A) 

592 return _maybe_real(A, 0.5 * (expm(A) - expm(-A))) 

593 

594 

595def tanhm(A): 

596 """ 

597 Compute the hyperbolic matrix tangent. 

598 

599 This routine uses expm to compute the matrix exponentials. 

600 

601 Parameters 

602 ---------- 

603 A : (N, N) array_like 

604 Input array 

605 

606 Returns 

607 ------- 

608 tanhm : (N, N) ndarray 

609 Hyperbolic matrix tangent of `A` 

610 

611 Examples 

612 -------- 

613 >>> import numpy as np 

614 >>> from scipy.linalg import tanhm, sinhm, coshm 

615 >>> a = np.array([[1.0, 3.0], [1.0, 4.0]]) 

616 >>> t = tanhm(a) 

617 >>> t 

618 array([[ 0.3428582 , 0.51987926], 

619 [ 0.17329309, 0.86273746]]) 

620 

621 Verify tanhm(a) = sinhm(a).dot(inv(coshm(a))) 

622 

623 >>> s = sinhm(a) 

624 >>> c = coshm(a) 

625 >>> t - s.dot(np.linalg.inv(c)) 

626 array([[ 2.72004641e-15, 4.55191440e-15], 

627 [ 0.00000000e+00, -5.55111512e-16]]) 

628 

629 """ 

630 A = _asarray_square(A) 

631 return _maybe_real(A, solve(coshm(A), sinhm(A))) 

632 

633 

634def funm(A, func, disp=True): 

635 """ 

636 Evaluate a matrix function specified by a callable. 

637 

638 Returns the value of matrix-valued function ``f`` at `A`. The 

639 function ``f`` is an extension of the scalar-valued function `func` 

640 to matrices. 

641 

642 Parameters 

643 ---------- 

644 A : (N, N) array_like 

645 Matrix at which to evaluate the function 

646 func : callable 

647 Callable object that evaluates a scalar function f. 

648 Must be vectorized (eg. using vectorize). 

649 disp : bool, optional 

650 Print warning if error in the result is estimated large 

651 instead of returning estimated error. (Default: True) 

652 

653 Returns 

654 ------- 

655 funm : (N, N) ndarray 

656 Value of the matrix function specified by func evaluated at `A` 

657 errest : float 

658 (if disp == False) 

659 

660 1-norm of the estimated error, ||err||_1 / ||A||_1 

661 

662 Notes 

663 ----- 

664 This function implements the general algorithm based on Schur decomposition 

665 (Algorithm 9.1.1. in [1]_). 

666 

667 If the input matrix is known to be diagonalizable, then relying on the 

668 eigendecomposition is likely to be faster. For example, if your matrix is 

669 Hermitian, you can do 

670 

671 >>> from scipy.linalg import eigh 

672 >>> def funm_herm(a, func, check_finite=False): 

673 ... w, v = eigh(a, check_finite=check_finite) 

674 ... ## if you further know that your matrix is positive semidefinite, 

675 ... ## you can optionally guard against precision errors by doing 

676 ... # w = np.maximum(w, 0) 

677 ... w = func(w) 

678 ... return (v * w).dot(v.conj().T) 

679 

680 References 

681 ---------- 

682 .. [1] Gene H. Golub, Charles F. van Loan, Matrix Computations 4th ed. 

683 

684 Examples 

685 -------- 

686 >>> import numpy as np 

687 >>> from scipy.linalg import funm 

688 >>> a = np.array([[1.0, 3.0], [1.0, 4.0]]) 

689 >>> funm(a, lambda x: x*x) 

690 array([[ 4., 15.], 

691 [ 5., 19.]]) 

692 >>> a.dot(a) 

693 array([[ 4., 15.], 

694 [ 5., 19.]]) 

695 

696 """ 

697 A = _asarray_square(A) 

698 # Perform Shur decomposition (lapack ?gees) 

699 T, Z = schur(A) 

700 T, Z = rsf2csf(T,Z) 

701 n,n = T.shape 

702 F = diag(func(diag(T))) # apply function to diagonal elements 

703 F = F.astype(T.dtype.char) # e.g., when F is real but T is complex 

704 

705 minden = abs(T[0,0]) 

706 

707 # implement Algorithm 11.1.1 from Golub and Van Loan 

708 # "matrix Computations." 

709 for p in range(1,n): 

710 for i in range(1,n-p+1): 

711 j = i + p 

712 s = T[i-1,j-1] * (F[j-1,j-1] - F[i-1,i-1]) 

713 ksl = slice(i,j-1) 

714 val = dot(T[i-1,ksl],F[ksl,j-1]) - dot(F[i-1,ksl],T[ksl,j-1]) 

715 s = s + val 

716 den = T[j-1,j-1] - T[i-1,i-1] 

717 if den != 0.0: 

718 s = s / den 

719 F[i-1,j-1] = s 

720 minden = min(minden,abs(den)) 

721 

722 F = dot(dot(Z, F), transpose(conjugate(Z))) 

723 F = _maybe_real(A, F) 

724 

725 tol = {0:feps, 1:eps}[_array_precision[F.dtype.char]] 

726 if minden == 0.0: 

727 minden = tol 

728 err = min(1, max(tol,(tol/minden)*norm(triu(T,1),1))) 

729 if prod(ravel(logical_not(isfinite(F))),axis=0): 

730 err = Inf 

731 if disp: 

732 if err > 1000*tol: 

733 print("funm result may be inaccurate, approximate err =", err) 

734 return F 

735 else: 

736 return F, err 

737 

738 

739def signm(A, disp=True): 

740 """ 

741 Matrix sign function. 

742 

743 Extension of the scalar sign(x) to matrices. 

744 

745 Parameters 

746 ---------- 

747 A : (N, N) array_like 

748 Matrix at which to evaluate the sign function 

749 disp : bool, optional 

750 Print warning if error in the result is estimated large 

751 instead of returning estimated error. (Default: True) 

752 

753 Returns 

754 ------- 

755 signm : (N, N) ndarray 

756 Value of the sign function at `A` 

757 errest : float 

758 (if disp == False) 

759 

760 1-norm of the estimated error, ||err||_1 / ||A||_1 

761 

762 Examples 

763 -------- 

764 >>> from scipy.linalg import signm, eigvals 

765 >>> a = [[1,2,3], [1,2,1], [1,1,1]] 

766 >>> eigvals(a) 

767 array([ 4.12488542+0.j, -0.76155718+0.j, 0.63667176+0.j]) 

768 >>> eigvals(signm(a)) 

769 array([-1.+0.j, 1.+0.j, 1.+0.j]) 

770 

771 """ 

772 A = _asarray_square(A) 

773 

774 def rounded_sign(x): 

775 rx = np.real(x) 

776 if rx.dtype.char == 'f': 

777 c = 1e3*feps*amax(x) 

778 else: 

779 c = 1e3*eps*amax(x) 

780 return sign((absolute(rx) > c) * rx) 

781 result, errest = funm(A, rounded_sign, disp=0) 

782 errtol = {0:1e3*feps, 1:1e3*eps}[_array_precision[result.dtype.char]] 

783 if errest < errtol: 

784 return result 

785 

786 # Handle signm of defective matrices: 

787 

788 # See "E.D.Denman and J.Leyva-Ramos, Appl.Math.Comp., 

789 # 8:237-250,1981" for how to improve the following (currently a 

790 # rather naive) iteration process: 

791 

792 # a = result # sometimes iteration converges faster but where?? 

793 

794 # Shifting to avoid zero eigenvalues. How to ensure that shifting does 

795 # not change the spectrum too much? 

796 vals = svd(A, compute_uv=False) 

797 max_sv = np.amax(vals) 

798 # min_nonzero_sv = vals[(vals>max_sv*errtol).tolist().count(1)-1] 

799 # c = 0.5/min_nonzero_sv 

800 c = 0.5/max_sv 

801 S0 = A + c*np.identity(A.shape[0]) 

802 prev_errest = errest 

803 for i in range(100): 

804 iS0 = inv(S0) 

805 S0 = 0.5*(S0 + iS0) 

806 Pp = 0.5*(dot(S0,S0)+S0) 

807 errest = norm(dot(Pp,Pp)-Pp,1) 

808 if errest < errtol or prev_errest == errest: 

809 break 

810 prev_errest = errest 

811 if disp: 

812 if not isfinite(errest) or errest >= errtol: 

813 print("signm result may be inaccurate, approximate err =", errest) 

814 return S0 

815 else: 

816 return S0, errest 

817 

818 

819def khatri_rao(a, b): 

820 r""" 

821 Khatri-rao product 

822 

823 A column-wise Kronecker product of two matrices 

824 

825 Parameters 

826 ---------- 

827 a : (n, k) array_like 

828 Input array 

829 b : (m, k) array_like 

830 Input array 

831 

832 Returns 

833 ------- 

834 c: (n*m, k) ndarray 

835 Khatri-rao product of `a` and `b`. 

836 

837 See Also 

838 -------- 

839 kron : Kronecker product 

840 

841 Notes 

842 ----- 

843 The mathematical definition of the Khatri-Rao product is: 

844 

845 .. math:: 

846 

847 (A_{ij} \bigotimes B_{ij})_{ij} 

848 

849 which is the Kronecker product of every column of A and B, e.g.:: 

850 

851 c = np.vstack([np.kron(a[:, k], b[:, k]) for k in range(b.shape[1])]).T 

852 

853 Examples 

854 -------- 

855 >>> import numpy as np 

856 >>> from scipy import linalg 

857 >>> a = np.array([[1, 2, 3], [4, 5, 6]]) 

858 >>> b = np.array([[3, 4, 5], [6, 7, 8], [2, 3, 9]]) 

859 >>> linalg.khatri_rao(a, b) 

860 array([[ 3, 8, 15], 

861 [ 6, 14, 24], 

862 [ 2, 6, 27], 

863 [12, 20, 30], 

864 [24, 35, 48], 

865 [ 8, 15, 54]]) 

866 

867 """ 

868 a = np.asarray(a) 

869 b = np.asarray(b) 

870 

871 if not (a.ndim == 2 and b.ndim == 2): 

872 raise ValueError("The both arrays should be 2-dimensional.") 

873 

874 if not a.shape[1] == b.shape[1]: 

875 raise ValueError("The number of columns for both arrays " 

876 "should be equal.") 

877 

878 # c = np.vstack([np.kron(a[:, k], b[:, k]) for k in range(b.shape[1])]).T 

879 c = a[..., :, np.newaxis, :] * b[..., np.newaxis, :, :] 

880 return c.reshape((-1,) + c.shape[2:])