_matfuncs.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059
  1. #
  2. # Author: Travis Oliphant, March 2002
  3. #
  4. import warnings
  5. from itertools import product
  6. import numpy as np
  7. from numpy import (dot, diag, prod, logical_not, ravel, transpose,
  8. conjugate, absolute, amax, sign, isfinite, triu)
  9. from scipy._lib._util import _apply_over_batch
  10. from scipy._lib.deprecation import _NoValue
  11. # Local imports
  12. from scipy.linalg import LinAlgError, bandwidth, LinAlgWarning
  13. from ._misc import norm
  14. from ._basic import solve, inv
  15. from ._decomp_svd import svd
  16. from ._decomp_schur import schur, rsf2csf
  17. from ._expm_frechet import expm_frechet, expm_cond
  18. from ._matfuncs_schur_sqrtm import recursive_schur_sqrtm
  19. from ._matfuncs_expm import pick_pade_structure, pade_UV_calc
  20. from ._linalg_pythran import _funm_loops # type: ignore[import-not-found]
  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. eps = np.finfo('d').eps
  25. feps = np.finfo('f').eps
  26. _array_precision = {'i': 1, 'l': 1, 'f': 0, 'd': 1, 'F': 0, 'D': 1}
  27. ###############################################################################
  28. # Utility functions.
  29. def _asarray_square(A):
  30. """
  31. Wraps asarray with the extra requirement that the input be a square matrix.
  32. The motivation is that the matfuncs module has real functions that have
  33. been lifted to square matrix functions.
  34. Parameters
  35. ----------
  36. A : array_like
  37. A square matrix.
  38. Returns
  39. -------
  40. out : ndarray
  41. An ndarray copy or view or other representation of A.
  42. """
  43. A = np.asarray(A)
  44. if len(A.shape) != 2 or A.shape[0] != A.shape[1]:
  45. raise ValueError('expected square array_like input')
  46. return A
  47. def _maybe_real(A, B, tol=None):
  48. """
  49. Return either B or the real part of B, depending on properties of A and B.
  50. The motivation is that B has been computed as a complicated function of A,
  51. and B may be perturbed by negligible imaginary components.
  52. If A is real and B is complex with small imaginary components,
  53. then return a real copy of B. The assumption in that case would be that
  54. the imaginary components of B are numerical artifacts.
  55. Parameters
  56. ----------
  57. A : ndarray
  58. Input array whose type is to be checked as real vs. complex.
  59. B : ndarray
  60. Array to be returned, possibly without its imaginary part.
  61. tol : float
  62. Absolute tolerance.
  63. Returns
  64. -------
  65. out : real or complex array
  66. Either the input array B or only the real part of the input array B.
  67. """
  68. # Note that booleans and integers compare as real.
  69. if np.isrealobj(A) and np.iscomplexobj(B):
  70. if tol is None:
  71. tol = {0: feps*1e3, 1: eps*1e6}[_array_precision[B.dtype.char]]
  72. if np.allclose(B.imag, 0.0, atol=tol):
  73. B = B.real
  74. return B
  75. ###############################################################################
  76. # Matrix functions.
  77. @_apply_over_batch(('A', 2))
  78. def fractional_matrix_power(A, t):
  79. """
  80. Compute the fractional power of a matrix.
  81. Proceeds according to the discussion in section (6) of [1]_.
  82. Parameters
  83. ----------
  84. A : (N, N) array_like
  85. Matrix whose fractional power to evaluate.
  86. t : float
  87. Fractional power.
  88. Returns
  89. -------
  90. X : (N, N) array_like
  91. The fractional power of the matrix.
  92. References
  93. ----------
  94. .. [1] Nicholas J. Higham and Lijing Lin (2011)
  95. "A Schur-Pade Algorithm for Fractional Powers of a Matrix."
  96. SIAM Journal on Matrix Analysis and Applications,
  97. 32 (3). pp. 1056-1078. ISSN 0895-4798.
  98. :doi:`10.1137/10081232X`
  99. Examples
  100. --------
  101. >>> import numpy as np
  102. >>> from scipy.linalg import fractional_matrix_power
  103. >>> a = np.array([[1.0, 3.0], [1.0, 4.0]])
  104. >>> b = fractional_matrix_power(a, 0.5)
  105. >>> b
  106. array([[ 0.75592895, 1.13389342],
  107. [ 0.37796447, 1.88982237]])
  108. >>> np.dot(b, b) # Verify square root
  109. array([[ 1., 3.],
  110. [ 1., 4.]])
  111. """
  112. # This fixes some issue with imports;
  113. # this function calls onenormest which is in scipy.sparse.
  114. A = _asarray_square(A)
  115. import scipy.linalg._matfuncs_inv_ssq
  116. return scipy.linalg._matfuncs_inv_ssq._fractional_matrix_power(A, t)
  117. @_apply_over_batch(('A', 2))
  118. def logm(A, disp=_NoValue):
  119. """
  120. Compute matrix logarithm.
  121. The matrix logarithm is the inverse of
  122. expm: expm(logm(`A`)) == `A`
  123. Parameters
  124. ----------
  125. A : (N, N) array_like
  126. Matrix whose logarithm to evaluate
  127. disp : bool, optional
  128. Emit warning if error in the result is estimated large
  129. instead of returning estimated error. (Default: True)
  130. .. deprecated:: 1.16.0
  131. The `disp` argument is deprecated and will be
  132. removed in SciPy 1.18.0. The previously returned error estimate
  133. can be computed as ``norm(expm(logm(A)) - A, 1) / norm(A, 1)``.
  134. Returns
  135. -------
  136. logm : (N, N) ndarray
  137. Matrix logarithm of `A`
  138. errest : float
  139. (if disp == False)
  140. 1-norm of the estimated error, ||err||_1 / ||A||_1
  141. References
  142. ----------
  143. .. [1] Awad H. Al-Mohy and Nicholas J. Higham (2012)
  144. "Improved Inverse Scaling and Squaring Algorithms
  145. for the Matrix Logarithm."
  146. SIAM Journal on Scientific Computing, 34 (4). C152-C169.
  147. ISSN 1095-7197
  148. .. [2] Nicholas J. Higham (2008)
  149. "Functions of Matrices: Theory and Computation"
  150. ISBN 978-0-898716-46-7
  151. .. [3] Nicholas J. Higham and Lijing lin (2011)
  152. "A Schur-Pade Algorithm for Fractional Powers of a Matrix."
  153. SIAM Journal on Matrix Analysis and Applications,
  154. 32 (3). pp. 1056-1078. ISSN 0895-4798
  155. Examples
  156. --------
  157. >>> import numpy as np
  158. >>> from scipy.linalg import logm, expm
  159. >>> a = np.array([[1.0, 3.0], [1.0, 4.0]])
  160. >>> b = logm(a)
  161. >>> b
  162. array([[-1.02571087, 2.05142174],
  163. [ 0.68380725, 1.02571087]])
  164. >>> expm(b) # Verify expm(logm(a)) returns a
  165. array([[ 1., 3.],
  166. [ 1., 4.]])
  167. """
  168. if disp is _NoValue:
  169. disp = True
  170. else:
  171. warnings.warn("The `disp` argument is deprecated "
  172. "and will be removed in SciPy 1.18.0.",
  173. DeprecationWarning, stacklevel=2)
  174. A = np.asarray(A) # squareness checked in `_logm`
  175. # Avoid circular import ... this is OK, right?
  176. import scipy.linalg._matfuncs_inv_ssq
  177. F = scipy.linalg._matfuncs_inv_ssq._logm(A)
  178. F = _maybe_real(A, F)
  179. errtol = 1000*eps
  180. # TODO use a better error approximation
  181. with np.errstate(divide='ignore', invalid='ignore'):
  182. errest = norm(expm(F)-A, 1) / np.asarray(norm(A, 1), dtype=A.dtype).real[()]
  183. if disp:
  184. if not isfinite(errest) or errest >= errtol:
  185. message = f"logm result may be inaccurate, approximate err = {errest}"
  186. warnings.warn(message, RuntimeWarning, stacklevel=2)
  187. return F
  188. else:
  189. return F, errest
  190. def expm(A):
  191. """Compute the matrix exponential of an array.
  192. Array argument(s) of this function may have additional
  193. "batch" dimensions prepended to the core shape. In this case, the array is treated
  194. as a batch of lower-dimensional slices; see :ref:`linalg_batch` for details.
  195. Parameters
  196. ----------
  197. A : ndarray
  198. Input with last two dimensions are square ``(..., n, n)``.
  199. Returns
  200. -------
  201. eA : ndarray
  202. The resulting matrix exponential with the same shape of ``A``
  203. Notes
  204. -----
  205. Implements the algorithm given in [1]_, which is essentially a Pade
  206. approximation with a variable order that is decided based on the array
  207. data.
  208. For input with size ``n``, the memory usage is in the worst case in the
  209. order of ``8*(n**2)``. If the input data is not of single and double
  210. precision of real and complex dtypes, it is copied to a new array.
  211. For cases ``n >= 400``, the exact 1-norm computation cost, breaks even with
  212. 1-norm estimation and from that point on the estimation scheme given in
  213. [2]_ is used to decide on the approximation order.
  214. References
  215. ----------
  216. .. [1] Awad H. Al-Mohy and Nicholas J. Higham, (2009), "A New Scaling
  217. and Squaring Algorithm for the Matrix Exponential", SIAM J. Matrix
  218. Anal. Appl. 31(3):970-989, :doi:`10.1137/09074721X`
  219. .. [2] Nicholas J. Higham and Francoise Tisseur (2000), "A Block Algorithm
  220. for Matrix 1-Norm Estimation, with an Application to 1-Norm
  221. Pseudospectra." SIAM J. Matrix Anal. Appl. 21(4):1185-1201,
  222. :doi:`10.1137/S0895479899356080`
  223. Examples
  224. --------
  225. >>> import numpy as np
  226. >>> from scipy.linalg import expm, sinm, cosm
  227. Matrix version of the formula exp(0) = 1:
  228. >>> expm(np.zeros((3, 2, 2)))
  229. array([[[1., 0.],
  230. [0., 1.]],
  231. <BLANKLINE>
  232. [[1., 0.],
  233. [0., 1.]],
  234. <BLANKLINE>
  235. [[1., 0.],
  236. [0., 1.]]])
  237. Euler's identity (exp(i*theta) = cos(theta) + i*sin(theta))
  238. applied to a matrix:
  239. >>> a = np.array([[1.0, 2.0], [-1.0, 3.0]])
  240. >>> expm(1j*a)
  241. array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j],
  242. [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]])
  243. >>> cosm(a) + 1j*sinm(a)
  244. array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j],
  245. [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]])
  246. """
  247. a = np.asarray(A)
  248. if a.size == 1 and a.ndim < 2:
  249. return np.array([[np.exp(a.item())]])
  250. if a.ndim < 2:
  251. raise LinAlgError('The input array must be at least two-dimensional')
  252. if a.shape[-1] != a.shape[-2]:
  253. raise LinAlgError('Last 2 dimensions of the array must be square')
  254. # Empty array
  255. if min(*a.shape) == 0:
  256. dtype = expm(np.eye(2, dtype=a.dtype)).dtype
  257. return np.empty_like(a, dtype=dtype)
  258. # Scalar case
  259. if a.shape[-2:] == (1, 1):
  260. return np.exp(a)
  261. if not np.issubdtype(a.dtype, np.inexact):
  262. a = a.astype(np.float64)
  263. elif a.dtype == np.float16:
  264. a = a.astype(np.float32)
  265. # An explicit formula for 2x2 case exists (formula (2.2) in [1]_). However, without
  266. # Kahan's method, numerical instabilities can occur (See gh-19584). Hence removed
  267. # here until we have a more stable implementation.
  268. n = a.shape[-1]
  269. eA = np.empty(a.shape, dtype=a.dtype)
  270. # working memory to hold intermediate arrays
  271. Am = np.empty((5, n, n), dtype=a.dtype)
  272. # Main loop to go through the slices of an ndarray and passing to expm
  273. for ind in product(*[range(x) for x in a.shape[:-2]]):
  274. aw = a[ind]
  275. lu = bandwidth(aw)
  276. if not any(lu): # a is diagonal?
  277. eA[ind] = np.diag(np.exp(np.diag(aw)))
  278. continue
  279. # Generic/triangular case; copy the slice into scratch and send.
  280. # Am will be mutated by pick_pade_structure
  281. # If s != 0, scaled Am will be returned from pick_pade_structure.
  282. Am[0, :, :] = aw
  283. m, s = pick_pade_structure(Am)
  284. if (m < 0):
  285. raise MemoryError("scipy.linalg.expm could not allocate sufficient"
  286. " memory while trying to compute the Pade "
  287. f"structure (error code {m}).")
  288. info = pade_UV_calc(Am, m)
  289. if info != 0:
  290. if info <= -11:
  291. # We raise it from failed mallocs; negative LAPACK codes > -7
  292. raise MemoryError("scipy.linalg.expm could not allocate "
  293. "sufficient memory while trying to compute the "
  294. f"exponential (error code {info}).")
  295. else:
  296. # LAPACK wrong argument error or exact singularity.
  297. # Neither should happen.
  298. raise RuntimeError("scipy.linalg.expm got an internal LAPACK "
  299. "error during the exponential computation "
  300. f"(error code {info})")
  301. eAw = Am[0]
  302. if s != 0: # squaring needed
  303. if (lu[1] == 0) or (lu[0] == 0): # lower/upper triangular
  304. # This branch implements Code Fragment 2.1 of [1]_
  305. diag_aw = np.diag(aw)
  306. # einsum returns a writable view
  307. np.einsum('ii->i', eAw)[:] = np.exp(diag_aw * 2**(-s))
  308. # super/sub diagonal
  309. sd = np.diag(aw, k=-1 if lu[1] == 0 else 1)
  310. for i in range(s-1, -1, -1):
  311. eAw = eAw @ eAw
  312. # diagonal
  313. np.einsum('ii->i', eAw)[:] = np.exp(diag_aw * 2.**(-i))
  314. exp_sd = _exp_sinch(diag_aw * (2.**(-i))) * (sd * 2**(-i))
  315. if lu[1] == 0: # lower
  316. np.einsum('ii->i', eAw[1:, :-1])[:] = exp_sd
  317. else: # upper
  318. np.einsum('ii->i', eAw[:-1, 1:])[:] = exp_sd
  319. else: # generic
  320. for _ in range(s):
  321. eAw = eAw @ eAw
  322. # Zero out the entries from np.empty in case of triangular input
  323. if (lu[0] == 0) or (lu[1] == 0):
  324. eA[ind] = np.triu(eAw) if lu[0] == 0 else np.tril(eAw)
  325. else:
  326. eA[ind] = eAw
  327. return eA
  328. def _exp_sinch(x):
  329. # Higham's formula (10.42), might overflow, see GH-11839
  330. lexp_diff = np.diff(np.exp(x))
  331. l_diff = np.diff(x)
  332. mask_z = l_diff == 0.
  333. lexp_diff[~mask_z] /= l_diff[~mask_z]
  334. lexp_diff[mask_z] = np.exp(x[:-1][mask_z])
  335. return lexp_diff
  336. def sqrtm(A, disp=_NoValue, blocksize=_NoValue):
  337. """
  338. Compute, if exists, the matrix square root.
  339. The matrix square root of ``A`` is a matrix ``X`` such that ``X @ X = A``.
  340. Every square matrix is not guaranteed to have a matrix square root, for
  341. example, the array ``[[0, 1], [0, 0]]`` does not have a square root.
  342. Moreover, not every real matrix has a real square root. Hence, for
  343. real-valued matrices the return type can be complex if, numerically, there
  344. is an eigenvalue on the negative real axis.
  345. Array argument(s) of this function may have additional
  346. "batch" dimensions prepended to the core shape. In this case, the array is treated
  347. as a batch of lower-dimensional slices; see :ref:`linalg_batch` for details.
  348. Parameters
  349. ----------
  350. A : ndarray
  351. Input with last two dimensions are square ``(..., n, n)``.
  352. disp : bool, optional
  353. Print warning if error in the result is estimated large
  354. instead of returning estimated error. (Default: True)
  355. .. deprecated:: 1.16.0
  356. The `disp` argument is deprecated and will be
  357. removed in SciPy 1.18.0. The previously returned error estimate
  358. can be computed as ``norm(X @ X - A, 'fro')**2 / norm(A, 'fro')``
  359. blocksize : integer, optional
  360. .. deprecated:: 1.16.0
  361. The `blocksize` argument is deprecated as it is unused by the algorithm
  362. and will be removed in SciPy 1.18.0.
  363. Returns
  364. -------
  365. sqrtm : ndarray
  366. Computed matrix squareroot of `A` with same size ``(..., n, n)``.
  367. errest : float
  368. Frobenius norm of the estimated error, ||err||_F / ||A||_F. Only
  369. returned, if ``disp`` is set to ``False``. This return argument will be
  370. removed in version 1.20.0 and only the sqrtm result will be returned.
  371. .. deprecated:: 1.16.0
  372. Notes
  373. -----
  374. This function uses the Schur decomposition method to compute the matrix
  375. square root following [1]_ and for real matrices [2]_. Moreover, note
  376. that, there exist matrices that have square roots that are not polynomials
  377. in ``A``. For a classical example from [2]_, the matrix satisfies::
  378. [ a, a**2 + 1]**2 [-1, 0]
  379. [-1, -a] = [ 0, -1]
  380. for any scalar ``a`` but it is not a polynomial in ``-I``. Thus, they will
  381. not be found by this function.
  382. References
  383. ----------
  384. .. [1] Edvin Deadman, Nicholas J. Higham, Rui Ralha (2013)
  385. "Blocked Schur Algorithms for Computing the Matrix Square Root,
  386. Lecture Notes in Computer Science, 7782. pp. 171-182.
  387. :doi:`10.1016/0024-3795(87)90118-2`
  388. .. [2] Nicholas J. Higham (1987) "Computing real square roots of a real
  389. matrix", Linear Algebra and its Applications, 88/89:405-430.
  390. :doi:`10.1016/0024-3795(87)90118-2`
  391. Examples
  392. --------
  393. >>> import numpy as np
  394. >>> from scipy.linalg import sqrtm
  395. >>> a = np.array([[1.0, 3.0], [1.0, 4.0]])
  396. >>> r = sqrtm(a)
  397. >>> r
  398. array([[ 0.75592895, 1.13389342],
  399. [ 0.37796447, 1.88982237]])
  400. >>> r.dot(r)
  401. array([[ 1., 3.],
  402. [ 1., 4.]])
  403. """
  404. if disp is _NoValue:
  405. disp = True
  406. else:
  407. warnings.warn("The `disp` argument is deprecated and will be removed in SciPy "
  408. "1.18.0.",
  409. DeprecationWarning, stacklevel=2)
  410. if blocksize is not _NoValue:
  411. warnings.warn("The `blocksize` argument is deprecated and will be removed in "
  412. "SciPy 1.18.0.",
  413. DeprecationWarning, stacklevel=2)
  414. a = np.asarray(A)
  415. if a.size == 1 and a.ndim < 2:
  416. return np.array([[np.exp(a.item())]])
  417. if a.ndim < 2:
  418. raise LinAlgError('The input array must be at least two-dimensional')
  419. if a.shape[-1] != a.shape[-2]:
  420. raise LinAlgError('Last 2 dimensions of the array must be square')
  421. # Empty array
  422. if min(*a.shape) == 0:
  423. dtype = sqrtm(np.eye(2, dtype=a.dtype)).dtype
  424. return np.empty_like(a, dtype=dtype)
  425. # Scalar case
  426. if a.shape[-2:] == (1, 1):
  427. return np.emath.sqrt(a)
  428. if not np.issubdtype(a.dtype, np.inexact):
  429. a = a.astype(np.float64)
  430. elif a.dtype == np.float16:
  431. a = a.astype(np.float32)
  432. elif a.dtype.char in 'G':
  433. a = a.astype(np.complex128)
  434. elif a.dtype.char in 'g':
  435. a = a.astype(np.float64)
  436. if a.dtype.char not in 'fdFD':
  437. raise TypeError("scipy.linalg.sqrtm is not supported for the data type"
  438. f" {a.dtype}")
  439. res, isIllconditioned, isSingular, info = recursive_schur_sqrtm(a)
  440. if info < 0:
  441. raise LinAlgError(f"Internal error in scipy.linalg.sqrtm: {info}")
  442. if isSingular or isIllconditioned:
  443. if isSingular:
  444. msg = ("Matrix is singular. The result might be inaccurate or the"
  445. " array might not have a square root.")
  446. else:
  447. msg = ("Matrix is ill-conditioned. The result might be inaccurate"
  448. " or the array might not have a square root.")
  449. warnings.warn(msg, LinAlgWarning, stacklevel=2)
  450. if disp is False:
  451. try:
  452. arg2 = norm(res @ res - A, 'fro')**2 / norm(A, 'fro')
  453. except ValueError:
  454. # NaNs in matrix
  455. arg2 = np.inf
  456. return res, arg2
  457. else:
  458. return res
  459. @_apply_over_batch(('A', 2))
  460. def cosm(A):
  461. """
  462. Compute the matrix cosine.
  463. This routine uses expm to compute the matrix exponentials.
  464. Parameters
  465. ----------
  466. A : (N, N) array_like
  467. Input array
  468. Returns
  469. -------
  470. cosm : (N, N) ndarray
  471. Matrix cosine of A
  472. Examples
  473. --------
  474. >>> import numpy as np
  475. >>> from scipy.linalg import expm, sinm, cosm
  476. Euler's identity (exp(i*theta) = cos(theta) + i*sin(theta))
  477. applied to a matrix:
  478. >>> a = np.array([[1.0, 2.0], [-1.0, 3.0]])
  479. >>> expm(1j*a)
  480. array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j],
  481. [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]])
  482. >>> cosm(a) + 1j*sinm(a)
  483. array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j],
  484. [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]])
  485. """
  486. A = _asarray_square(A)
  487. if np.iscomplexobj(A):
  488. return 0.5*(expm(1j*A) + expm(-1j*A))
  489. else:
  490. return expm(1j*A).real
  491. @_apply_over_batch(('A', 2))
  492. def sinm(A):
  493. """
  494. Compute the matrix sine.
  495. This routine uses expm to compute the matrix exponentials.
  496. Parameters
  497. ----------
  498. A : (N, N) array_like
  499. Input array.
  500. Returns
  501. -------
  502. sinm : (N, N) ndarray
  503. Matrix sine of `A`
  504. Examples
  505. --------
  506. >>> import numpy as np
  507. >>> from scipy.linalg import expm, sinm, cosm
  508. Euler's identity (exp(i*theta) = cos(theta) + i*sin(theta))
  509. applied to a matrix:
  510. >>> a = np.array([[1.0, 2.0], [-1.0, 3.0]])
  511. >>> expm(1j*a)
  512. array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j],
  513. [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]])
  514. >>> cosm(a) + 1j*sinm(a)
  515. array([[ 0.42645930+1.89217551j, -2.13721484-0.97811252j],
  516. [ 1.06860742+0.48905626j, -1.71075555+0.91406299j]])
  517. """
  518. A = _asarray_square(A)
  519. if np.iscomplexobj(A):
  520. return -0.5j*(expm(1j*A) - expm(-1j*A))
  521. else:
  522. return expm(1j*A).imag
  523. @_apply_over_batch(('A', 2))
  524. def tanm(A):
  525. """
  526. Compute the matrix tangent.
  527. This routine uses expm to compute the matrix exponentials.
  528. Parameters
  529. ----------
  530. A : (N, N) array_like
  531. Input array.
  532. Returns
  533. -------
  534. tanm : (N, N) ndarray
  535. Matrix tangent of `A`
  536. Examples
  537. --------
  538. >>> import numpy as np
  539. >>> from scipy.linalg import tanm, sinm, cosm
  540. >>> a = np.array([[1.0, 3.0], [1.0, 4.0]])
  541. >>> t = tanm(a)
  542. >>> t
  543. array([[ -2.00876993, -8.41880636],
  544. [ -2.80626879, -10.42757629]])
  545. Verify tanm(a) = sinm(a).dot(inv(cosm(a)))
  546. >>> s = sinm(a)
  547. >>> c = cosm(a)
  548. >>> s.dot(np.linalg.inv(c))
  549. array([[ -2.00876993, -8.41880636],
  550. [ -2.80626879, -10.42757629]])
  551. """
  552. A = _asarray_square(A)
  553. return _maybe_real(A, solve(cosm(A), sinm(A)))
  554. @_apply_over_batch(('A', 2))
  555. def coshm(A):
  556. """
  557. Compute the hyperbolic matrix cosine.
  558. This routine uses expm to compute the matrix exponentials.
  559. Parameters
  560. ----------
  561. A : (N, N) array_like
  562. Input array.
  563. Returns
  564. -------
  565. coshm : (N, N) ndarray
  566. Hyperbolic matrix cosine of `A`
  567. Examples
  568. --------
  569. >>> import numpy as np
  570. >>> from scipy.linalg import tanhm, sinhm, coshm
  571. >>> a = np.array([[1.0, 3.0], [1.0, 4.0]])
  572. >>> c = coshm(a)
  573. >>> c
  574. array([[ 11.24592233, 38.76236492],
  575. [ 12.92078831, 50.00828725]])
  576. Verify tanhm(a) = sinhm(a).dot(inv(coshm(a)))
  577. >>> t = tanhm(a)
  578. >>> s = sinhm(a)
  579. >>> t - s.dot(np.linalg.inv(c))
  580. array([[ 2.72004641e-15, 4.55191440e-15],
  581. [ 0.00000000e+00, -5.55111512e-16]])
  582. """
  583. A = _asarray_square(A)
  584. return _maybe_real(A, 0.5 * (expm(A) + expm(-A)))
  585. @_apply_over_batch(('A', 2))
  586. def sinhm(A):
  587. """
  588. Compute the hyperbolic matrix sine.
  589. This routine uses expm to compute the matrix exponentials.
  590. Parameters
  591. ----------
  592. A : (N, N) array_like
  593. Input array.
  594. Returns
  595. -------
  596. sinhm : (N, N) ndarray
  597. Hyperbolic matrix sine of `A`
  598. Examples
  599. --------
  600. >>> import numpy as np
  601. >>> from scipy.linalg import tanhm, sinhm, coshm
  602. >>> a = np.array([[1.0, 3.0], [1.0, 4.0]])
  603. >>> s = sinhm(a)
  604. >>> s
  605. array([[ 10.57300653, 39.28826594],
  606. [ 13.09608865, 49.86127247]])
  607. Verify tanhm(a) = sinhm(a).dot(inv(coshm(a)))
  608. >>> t = tanhm(a)
  609. >>> c = coshm(a)
  610. >>> t - s.dot(np.linalg.inv(c))
  611. array([[ 2.72004641e-15, 4.55191440e-15],
  612. [ 0.00000000e+00, -5.55111512e-16]])
  613. """
  614. A = _asarray_square(A)
  615. return _maybe_real(A, 0.5 * (expm(A) - expm(-A)))
  616. @_apply_over_batch(('A', 2))
  617. def tanhm(A):
  618. """
  619. Compute the hyperbolic matrix tangent.
  620. This routine uses expm to compute the matrix exponentials.
  621. Parameters
  622. ----------
  623. A : (N, N) array_like
  624. Input array
  625. Returns
  626. -------
  627. tanhm : (N, N) ndarray
  628. Hyperbolic matrix tangent of `A`
  629. Examples
  630. --------
  631. >>> import numpy as np
  632. >>> from scipy.linalg import tanhm, sinhm, coshm
  633. >>> a = np.array([[1.0, 3.0], [1.0, 4.0]])
  634. >>> t = tanhm(a)
  635. >>> t
  636. array([[ 0.3428582 , 0.51987926],
  637. [ 0.17329309, 0.86273746]])
  638. Verify tanhm(a) = sinhm(a).dot(inv(coshm(a)))
  639. >>> s = sinhm(a)
  640. >>> c = coshm(a)
  641. >>> t - s.dot(np.linalg.inv(c))
  642. array([[ 2.72004641e-15, 4.55191440e-15],
  643. [ 0.00000000e+00, -5.55111512e-16]])
  644. """
  645. A = _asarray_square(A)
  646. return _maybe_real(A, solve(coshm(A), sinhm(A)))
  647. @_apply_over_batch(('A', 2))
  648. def funm(A, func, disp=True):
  649. """
  650. Evaluate a matrix function specified by a callable.
  651. Returns the value of matrix-valued function ``f`` at `A`. The
  652. function ``f`` is an extension of the scalar-valued function `func`
  653. to matrices.
  654. Parameters
  655. ----------
  656. A : (N, N) array_like
  657. Matrix at which to evaluate the function
  658. func : callable
  659. Callable object that evaluates a scalar function f.
  660. Must be vectorized (eg. using vectorize).
  661. disp : bool, optional
  662. Print warning if error in the result is estimated large
  663. instead of returning estimated error. (Default: True)
  664. Returns
  665. -------
  666. funm : (N, N) ndarray
  667. Value of the matrix function specified by func evaluated at `A`
  668. errest : float
  669. (if disp == False)
  670. 1-norm of the estimated error, ||err||_1 / ||A||_1
  671. Notes
  672. -----
  673. This function implements the general algorithm based on Schur decomposition
  674. (Algorithm 9.1.1. in [1]_).
  675. If the input matrix is known to be diagonalizable, then relying on the
  676. eigendecomposition is likely to be faster. For example, if your matrix is
  677. Hermitian, you can do
  678. >>> from scipy.linalg import eigh
  679. >>> def funm_herm(a, func, check_finite=False):
  680. ... w, v = eigh(a, check_finite=check_finite)
  681. ... ## if you further know that your matrix is positive semidefinite,
  682. ... ## you can optionally guard against precision errors by doing
  683. ... # w = np.maximum(w, 0)
  684. ... w = func(w)
  685. ... return (v * w).dot(v.conj().T)
  686. References
  687. ----------
  688. .. [1] Gene H. Golub, Charles F. van Loan, Matrix Computations 4th ed.
  689. Examples
  690. --------
  691. >>> import numpy as np
  692. >>> from scipy.linalg import funm
  693. >>> a = np.array([[1.0, 3.0], [1.0, 4.0]])
  694. >>> funm(a, lambda x: x*x)
  695. array([[ 4., 15.],
  696. [ 5., 19.]])
  697. >>> a.dot(a)
  698. array([[ 4., 15.],
  699. [ 5., 19.]])
  700. """
  701. A = _asarray_square(A)
  702. # Perform Shur decomposition (lapack ?gees)
  703. T, Z = schur(A)
  704. T, Z = rsf2csf(T, Z)
  705. n, n = T.shape
  706. F = diag(func(diag(T))) # apply function to diagonal elements
  707. F = F.astype(T.dtype.char) # e.g., when F is real but T is complex
  708. minden = abs(T[0, 0])
  709. # implement Algorithm 11.1.1 from Golub and Van Loan
  710. # "matrix Computations."
  711. F, minden = _funm_loops(F, T, n, minden)
  712. F = dot(dot(Z, F), transpose(conjugate(Z)))
  713. F = _maybe_real(A, F)
  714. tol = {0: feps, 1: eps}[_array_precision[F.dtype.char]]
  715. if minden == 0.0:
  716. minden = tol
  717. err = min(1, max(tol, (tol/minden)*norm(triu(T, 1), 1)))
  718. if prod(ravel(logical_not(isfinite(F))), axis=0):
  719. err = np.inf
  720. if disp:
  721. if err > 1000*tol:
  722. print("funm result may be inaccurate, approximate err =", err)
  723. return F
  724. else:
  725. return F, err
  726. @_apply_over_batch(('A', 2))
  727. def signm(A, disp=_NoValue):
  728. """
  729. Matrix sign function.
  730. Extension of the scalar sign(x) to matrices.
  731. Parameters
  732. ----------
  733. A : (N, N) array_like
  734. Matrix at which to evaluate the sign function
  735. disp : bool, optional
  736. Print warning if error in the result is estimated large
  737. instead of returning estimated error. (Default: True)
  738. .. deprecated:: 1.16.0
  739. The `disp` argument is deprecated and will be
  740. removed in SciPy 1.18.0. The previously returned error estimate
  741. can be computed as ``norm(signm @ signm - signm, 1)``.
  742. Returns
  743. -------
  744. signm : (N, N) ndarray
  745. Value of the sign function at `A`
  746. errest : float
  747. (if disp == False)
  748. 1-norm of the estimated error, ||err||_1 / ||A||_1
  749. Examples
  750. --------
  751. >>> from scipy.linalg import signm, eigvals
  752. >>> a = [[1,2,3], [1,2,1], [1,1,1]]
  753. >>> eigvals(a)
  754. array([ 4.12488542+0.j, -0.76155718+0.j, 0.63667176+0.j])
  755. >>> eigvals(signm(a))
  756. array([-1.+0.j, 1.+0.j, 1.+0.j])
  757. """
  758. if disp is _NoValue:
  759. disp = True
  760. else:
  761. warnings.warn("The `disp` argument is deprecated "
  762. "and will be removed in SciPy 1.18.0.",
  763. DeprecationWarning, stacklevel=2)
  764. A = _asarray_square(A)
  765. def rounded_sign(x):
  766. rx = np.real(x)
  767. if rx.dtype.char == 'f':
  768. c = 1e3*feps*amax(x)
  769. else:
  770. c = 1e3*eps*amax(x)
  771. return sign((absolute(rx) > c) * rx)
  772. result, errest = funm(A, rounded_sign, disp=0)
  773. errtol = {0: 1e3*feps, 1: 1e3*eps}[_array_precision[result.dtype.char]]
  774. if errest < errtol:
  775. return result
  776. # Handle signm of defective matrices:
  777. # See "E.D.Denman and J.Leyva-Ramos, Appl.Math.Comp.,
  778. # 8:237-250,1981" for how to improve the following (currently a
  779. # rather naive) iteration process:
  780. # a = result # sometimes iteration converges faster but where??
  781. # Shifting to avoid zero eigenvalues. How to ensure that shifting does
  782. # not change the spectrum too much?
  783. vals = svd(A, compute_uv=False)
  784. max_sv = np.amax(vals)
  785. # min_nonzero_sv = vals[(vals>max_sv*errtol).tolist().count(1)-1]
  786. # c = 0.5/min_nonzero_sv
  787. c = 0.5/max_sv
  788. S0 = A + c*np.identity(A.shape[0])
  789. prev_errest = errest
  790. for i in range(100):
  791. iS0 = inv(S0)
  792. S0 = 0.5*(S0 + iS0)
  793. Pp = 0.5*(dot(S0, S0)+S0)
  794. errest = norm(dot(Pp, Pp)-Pp, 1)
  795. if errest < errtol or prev_errest == errest:
  796. break
  797. prev_errest = errest
  798. if disp:
  799. if not isfinite(errest) or errest >= errtol:
  800. print("signm result may be inaccurate, approximate err =", errest)
  801. return S0
  802. else:
  803. return S0, errest
  804. @_apply_over_batch(('a', 2), ('b', 2))
  805. def khatri_rao(a, b):
  806. r"""
  807. Khatri-Rao product of two matrices.
  808. A column-wise Kronecker product of two matrices
  809. Parameters
  810. ----------
  811. a : (n, k) array_like
  812. Input array
  813. b : (m, k) array_like
  814. Input array
  815. Returns
  816. -------
  817. c: (n*m, k) ndarray
  818. Khatri-rao product of `a` and `b`.
  819. Notes
  820. -----
  821. The mathematical definition of the Khatri-Rao product is:
  822. .. math::
  823. (A_{ij} \bigotimes B_{ij})_{ij}
  824. which is the Kronecker product of every column of A and B, e.g.::
  825. c = np.vstack([np.kron(a[:, k], b[:, k]) for k in range(b.shape[1])]).T
  826. Examples
  827. --------
  828. >>> import numpy as np
  829. >>> from scipy import linalg
  830. >>> a = np.array([[1, 2, 3], [4, 5, 6]])
  831. >>> b = np.array([[3, 4, 5], [6, 7, 8], [2, 3, 9]])
  832. >>> linalg.khatri_rao(a, b)
  833. array([[ 3, 8, 15],
  834. [ 6, 14, 24],
  835. [ 2, 6, 27],
  836. [12, 20, 30],
  837. [24, 35, 48],
  838. [ 8, 15, 54]])
  839. """
  840. a = np.asarray(a)
  841. b = np.asarray(b)
  842. if not (a.ndim == 2 and b.ndim == 2):
  843. raise ValueError("The both arrays should be 2-dimensional.")
  844. if not a.shape[1] == b.shape[1]:
  845. raise ValueError("The number of columns for both arrays "
  846. "should be equal.")
  847. # accommodate empty arrays
  848. if a.size == 0 or b.size == 0:
  849. m = a.shape[0] * b.shape[0]
  850. n = a.shape[1]
  851. return np.empty_like(a, shape=(m, n))
  852. # c = np.vstack([np.kron(a[:, k], b[:, k]) for k in range(b.shape[1])]).T
  853. c = a[..., :, np.newaxis, :] * b[..., np.newaxis, :, :]
  854. return c.reshape((-1,) + c.shape[2:])