Spaces:
Running
Running
"""Lite version of scipy.linalg. | |
Notes | |
----- | |
This module is a lite version of the linalg.py module in SciPy which | |
contains high-level Python interface to the LAPACK library. The lite | |
version only accesses the following LAPACK functions: dgesv, zgesv, | |
dgeev, zgeev, dgesdd, zgesdd, dgelsd, zgelsd, dsyevd, zheevd, dgetrf, | |
zgetrf, dpotrf, zpotrf, dgeqrf, zgeqrf, zungqr, dorgqr. | |
""" | |
__all__ = ['matrix_power', 'solve', 'tensorsolve', 'tensorinv', 'inv', | |
'cholesky', 'eigvals', 'eigvalsh', 'pinv', 'slogdet', 'det', | |
'svd', 'eig', 'eigh', 'lstsq', 'norm', 'qr', 'cond', 'matrix_rank', | |
'LinAlgError', 'multi_dot'] | |
import functools | |
import operator | |
import warnings | |
from numpy.core import ( | |
array, asarray, zeros, empty, empty_like, intc, single, double, | |
csingle, cdouble, inexact, complexfloating, newaxis, all, Inf, dot, | |
add, multiply, sqrt, fastCopyAndTranspose, sum, isfinite, | |
finfo, errstate, geterrobj, moveaxis, amin, amax, product, abs, | |
atleast_2d, intp, asanyarray, object_, matmul, | |
swapaxes, divide, count_nonzero, isnan, sign, argsort, sort | |
) | |
from numpy.core.multiarray import normalize_axis_index | |
from numpy.core.overrides import set_module | |
from numpy.core import overrides | |
from numpy.lib.twodim_base import triu, eye | |
from numpy.linalg import lapack_lite, _umath_linalg | |
array_function_dispatch = functools.partial( | |
overrides.array_function_dispatch, module='numpy.linalg') | |
fortran_int = intc | |
class LinAlgError(Exception): | |
""" | |
Generic Python-exception-derived object raised by linalg functions. | |
General purpose exception class, derived from Python's exception.Exception | |
class, programmatically raised in linalg functions when a Linear | |
Algebra-related condition would prevent further correct execution of the | |
function. | |
Parameters | |
---------- | |
None | |
Examples | |
-------- | |
>>> from numpy import linalg as LA | |
>>> LA.inv(np.zeros((2,2))) | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "...linalg.py", line 350, | |
in inv return wrap(solve(a, identity(a.shape[0], dtype=a.dtype))) | |
File "...linalg.py", line 249, | |
in solve | |
raise LinAlgError('Singular matrix') | |
numpy.linalg.LinAlgError: Singular matrix | |
""" | |
def _determine_error_states(): | |
errobj = geterrobj() | |
bufsize = errobj[0] | |
with errstate(invalid='call', over='ignore', | |
divide='ignore', under='ignore'): | |
invalid_call_errmask = geterrobj()[1] | |
return [bufsize, invalid_call_errmask, None] | |
# Dealing with errors in _umath_linalg | |
_linalg_error_extobj = _determine_error_states() | |
del _determine_error_states | |
def _raise_linalgerror_singular(err, flag): | |
raise LinAlgError("Singular matrix") | |
def _raise_linalgerror_nonposdef(err, flag): | |
raise LinAlgError("Matrix is not positive definite") | |
def _raise_linalgerror_eigenvalues_nonconvergence(err, flag): | |
raise LinAlgError("Eigenvalues did not converge") | |
def _raise_linalgerror_svd_nonconvergence(err, flag): | |
raise LinAlgError("SVD did not converge") | |
def _raise_linalgerror_lstsq(err, flag): | |
raise LinAlgError("SVD did not converge in Linear Least Squares") | |
def get_linalg_error_extobj(callback): | |
extobj = list(_linalg_error_extobj) # make a copy | |
extobj[2] = callback | |
return extobj | |
def _makearray(a): | |
new = asarray(a) | |
wrap = getattr(a, "__array_prepare__", new.__array_wrap__) | |
return new, wrap | |
def isComplexType(t): | |
return issubclass(t, complexfloating) | |
_real_types_map = {single : single, | |
double : double, | |
csingle : single, | |
cdouble : double} | |
_complex_types_map = {single : csingle, | |
double : cdouble, | |
csingle : csingle, | |
cdouble : cdouble} | |
def _realType(t, default=double): | |
return _real_types_map.get(t, default) | |
def _complexType(t, default=cdouble): | |
return _complex_types_map.get(t, default) | |
def _linalgRealType(t): | |
"""Cast the type t to either double or cdouble.""" | |
return double | |
def _commonType(*arrays): | |
# in lite version, use higher precision (always double or cdouble) | |
result_type = single | |
is_complex = False | |
for a in arrays: | |
if issubclass(a.dtype.type, inexact): | |
if isComplexType(a.dtype.type): | |
is_complex = True | |
rt = _realType(a.dtype.type, default=None) | |
if rt is None: | |
# unsupported inexact scalar | |
raise TypeError("array type %s is unsupported in linalg" % | |
(a.dtype.name,)) | |
else: | |
rt = double | |
if rt is double: | |
result_type = double | |
if is_complex: | |
t = cdouble | |
result_type = _complex_types_map[result_type] | |
else: | |
t = double | |
return t, result_type | |
# _fastCopyAndTranpose assumes the input is 2D (as all the calls in here are). | |
_fastCT = fastCopyAndTranspose | |
def _to_native_byte_order(*arrays): | |
ret = [] | |
for arr in arrays: | |
if arr.dtype.byteorder not in ('=', '|'): | |
ret.append(asarray(arr, dtype=arr.dtype.newbyteorder('='))) | |
else: | |
ret.append(arr) | |
if len(ret) == 1: | |
return ret[0] | |
else: | |
return ret | |
def _fastCopyAndTranspose(type, *arrays): | |
cast_arrays = () | |
for a in arrays: | |
if a.dtype.type is not type: | |
a = a.astype(type) | |
cast_arrays = cast_arrays + (_fastCT(a),) | |
if len(cast_arrays) == 1: | |
return cast_arrays[0] | |
else: | |
return cast_arrays | |
def _assert_2d(*arrays): | |
for a in arrays: | |
if a.ndim != 2: | |
raise LinAlgError('%d-dimensional array given. Array must be ' | |
'two-dimensional' % a.ndim) | |
def _assert_stacked_2d(*arrays): | |
for a in arrays: | |
if a.ndim < 2: | |
raise LinAlgError('%d-dimensional array given. Array must be ' | |
'at least two-dimensional' % a.ndim) | |
def _assert_stacked_square(*arrays): | |
for a in arrays: | |
m, n = a.shape[-2:] | |
if m != n: | |
raise LinAlgError('Last 2 dimensions of the array must be square') | |
def _assert_finite(*arrays): | |
for a in arrays: | |
if not isfinite(a).all(): | |
raise LinAlgError("Array must not contain infs or NaNs") | |
def _is_empty_2d(arr): | |
# check size first for efficiency | |
return arr.size == 0 and product(arr.shape[-2:]) == 0 | |
def transpose(a): | |
""" | |
Transpose each matrix in a stack of matrices. | |
Unlike np.transpose, this only swaps the last two axes, rather than all of | |
them | |
Parameters | |
---------- | |
a : (...,M,N) array_like | |
Returns | |
------- | |
aT : (...,N,M) ndarray | |
""" | |
return swapaxes(a, -1, -2) | |
# Linear equations | |
def _tensorsolve_dispatcher(a, b, axes=None): | |
return (a, b) | |
def tensorsolve(a, b, axes=None): | |
""" | |
Solve the tensor equation ``a x = b`` for x. | |
It is assumed that all indices of `x` are summed over in the product, | |
together with the rightmost indices of `a`, as is done in, for example, | |
``tensordot(a, x, axes=b.ndim)``. | |
Parameters | |
---------- | |
a : array_like | |
Coefficient tensor, of shape ``b.shape + Q``. `Q`, a tuple, equals | |
the shape of that sub-tensor of `a` consisting of the appropriate | |
number of its rightmost indices, and must be such that | |
``prod(Q) == prod(b.shape)`` (in which sense `a` is said to be | |
'square'). | |
b : array_like | |
Right-hand tensor, which can be of any shape. | |
axes : tuple of ints, optional | |
Axes in `a` to reorder to the right, before inversion. | |
If None (default), no reordering is done. | |
Returns | |
------- | |
x : ndarray, shape Q | |
Raises | |
------ | |
LinAlgError | |
If `a` is singular or not 'square' (in the above sense). | |
See Also | |
-------- | |
numpy.tensordot, tensorinv, numpy.einsum | |
Examples | |
-------- | |
>>> a = np.eye(2*3*4) | |
>>> a.shape = (2*3, 4, 2, 3, 4) | |
>>> b = np.random.randn(2*3, 4) | |
>>> x = np.linalg.tensorsolve(a, b) | |
>>> x.shape | |
(2, 3, 4) | |
>>> np.allclose(np.tensordot(a, x, axes=3), b) | |
True | |
""" | |
a, wrap = _makearray(a) | |
b = asarray(b) | |
an = a.ndim | |
if axes is not None: | |
allaxes = list(range(0, an)) | |
for k in axes: | |
allaxes.remove(k) | |
allaxes.insert(an, k) | |
a = a.transpose(allaxes) | |
oldshape = a.shape[-(an-b.ndim):] | |
prod = 1 | |
for k in oldshape: | |
prod *= k | |
a = a.reshape(-1, prod) | |
b = b.ravel() | |
res = wrap(solve(a, b)) | |
res.shape = oldshape | |
return res | |
def _solve_dispatcher(a, b): | |
return (a, b) | |
def solve(a, b): | |
""" | |
Solve a linear matrix equation, or system of linear scalar equations. | |
Computes the "exact" solution, `x`, of the well-determined, i.e., full | |
rank, linear matrix equation `ax = b`. | |
Parameters | |
---------- | |
a : (..., M, M) array_like | |
Coefficient matrix. | |
b : {(..., M,), (..., M, K)}, array_like | |
Ordinate or "dependent variable" values. | |
Returns | |
------- | |
x : {(..., M,), (..., M, K)} ndarray | |
Solution to the system a x = b. Returned shape is identical to `b`. | |
Raises | |
------ | |
LinAlgError | |
If `a` is singular or not square. | |
See Also | |
-------- | |
scipy.linalg.solve : Similar function in SciPy. | |
Notes | |
----- | |
.. versionadded:: 1.8.0 | |
Broadcasting rules apply, see the `numpy.linalg` documentation for | |
details. | |
The solutions are computed using LAPACK routine ``_gesv``. | |
`a` must be square and of full-rank, i.e., all rows (or, equivalently, | |
columns) must be linearly independent; if either is not true, use | |
`lstsq` for the least-squares best "solution" of the | |
system/equation. | |
References | |
---------- | |
.. [1] G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando, | |
FL, Academic Press, Inc., 1980, pg. 22. | |
Examples | |
-------- | |
Solve the system of equations ``x0 + 2 * x1 = 1`` and ``3 * x0 + 5 * x1 = 2``: | |
>>> a = np.array([[1, 2], [3, 5]]) | |
>>> b = np.array([1, 2]) | |
>>> x = np.linalg.solve(a, b) | |
>>> x | |
array([-1., 1.]) | |
Check that the solution is correct: | |
>>> np.allclose(np.dot(a, x), b) | |
True | |
""" | |
a, _ = _makearray(a) | |
_assert_stacked_2d(a) | |
_assert_stacked_square(a) | |
b, wrap = _makearray(b) | |
t, result_t = _commonType(a, b) | |
# We use the b = (..., M,) logic, only if the number of extra dimensions | |
# match exactly | |
if b.ndim == a.ndim - 1: | |
gufunc = _umath_linalg.solve1 | |
else: | |
gufunc = _umath_linalg.solve | |
signature = 'DD->D' if isComplexType(t) else 'dd->d' | |
extobj = get_linalg_error_extobj(_raise_linalgerror_singular) | |
r = gufunc(a, b, signature=signature, extobj=extobj) | |
return wrap(r.astype(result_t, copy=False)) | |
def _tensorinv_dispatcher(a, ind=None): | |
return (a,) | |
def tensorinv(a, ind=2): | |
""" | |
Compute the 'inverse' of an N-dimensional array. | |
The result is an inverse for `a` relative to the tensordot operation | |
``tensordot(a, b, ind)``, i. e., up to floating-point accuracy, | |
``tensordot(tensorinv(a), a, ind)`` is the "identity" tensor for the | |
tensordot operation. | |
Parameters | |
---------- | |
a : array_like | |
Tensor to 'invert'. Its shape must be 'square', i. e., | |
``prod(a.shape[:ind]) == prod(a.shape[ind:])``. | |
ind : int, optional | |
Number of first indices that are involved in the inverse sum. | |
Must be a positive integer, default is 2. | |
Returns | |
------- | |
b : ndarray | |
`a`'s tensordot inverse, shape ``a.shape[ind:] + a.shape[:ind]``. | |
Raises | |
------ | |
LinAlgError | |
If `a` is singular or not 'square' (in the above sense). | |
See Also | |
-------- | |
numpy.tensordot, tensorsolve | |
Examples | |
-------- | |
>>> a = np.eye(4*6) | |
>>> a.shape = (4, 6, 8, 3) | |
>>> ainv = np.linalg.tensorinv(a, ind=2) | |
>>> ainv.shape | |
(8, 3, 4, 6) | |
>>> b = np.random.randn(4, 6) | |
>>> np.allclose(np.tensordot(ainv, b), np.linalg.tensorsolve(a, b)) | |
True | |
>>> a = np.eye(4*6) | |
>>> a.shape = (24, 8, 3) | |
>>> ainv = np.linalg.tensorinv(a, ind=1) | |
>>> ainv.shape | |
(8, 3, 24) | |
>>> b = np.random.randn(24) | |
>>> np.allclose(np.tensordot(ainv, b, 1), np.linalg.tensorsolve(a, b)) | |
True | |
""" | |
a = asarray(a) | |
oldshape = a.shape | |
prod = 1 | |
if ind > 0: | |
invshape = oldshape[ind:] + oldshape[:ind] | |
for k in oldshape[ind:]: | |
prod *= k | |
else: | |
raise ValueError("Invalid ind argument.") | |
a = a.reshape(prod, -1) | |
ia = inv(a) | |
return ia.reshape(*invshape) | |
# Matrix inversion | |
def _unary_dispatcher(a): | |
return (a,) | |
def inv(a): | |
""" | |
Compute the (multiplicative) inverse of a matrix. | |
Given a square matrix `a`, return the matrix `ainv` satisfying | |
``dot(a, ainv) = dot(ainv, a) = eye(a.shape[0])``. | |
Parameters | |
---------- | |
a : (..., M, M) array_like | |
Matrix to be inverted. | |
Returns | |
------- | |
ainv : (..., M, M) ndarray or matrix | |
(Multiplicative) inverse of the matrix `a`. | |
Raises | |
------ | |
LinAlgError | |
If `a` is not square or inversion fails. | |
See Also | |
-------- | |
scipy.linalg.inv : Similar function in SciPy. | |
Notes | |
----- | |
.. versionadded:: 1.8.0 | |
Broadcasting rules apply, see the `numpy.linalg` documentation for | |
details. | |
Examples | |
-------- | |
>>> from numpy.linalg import inv | |
>>> a = np.array([[1., 2.], [3., 4.]]) | |
>>> ainv = inv(a) | |
>>> np.allclose(np.dot(a, ainv), np.eye(2)) | |
True | |
>>> np.allclose(np.dot(ainv, a), np.eye(2)) | |
True | |
If a is a matrix object, then the return value is a matrix as well: | |
>>> ainv = inv(np.matrix(a)) | |
>>> ainv | |
matrix([[-2. , 1. ], | |
[ 1.5, -0.5]]) | |
Inverses of several matrices can be computed at once: | |
>>> a = np.array([[[1., 2.], [3., 4.]], [[1, 3], [3, 5]]]) | |
>>> inv(a) | |
array([[[-2. , 1. ], | |
[ 1.5 , -0.5 ]], | |
[[-1.25, 0.75], | |
[ 0.75, -0.25]]]) | |
""" | |
a, wrap = _makearray(a) | |
_assert_stacked_2d(a) | |
_assert_stacked_square(a) | |
t, result_t = _commonType(a) | |
signature = 'D->D' if isComplexType(t) else 'd->d' | |
extobj = get_linalg_error_extobj(_raise_linalgerror_singular) | |
ainv = _umath_linalg.inv(a, signature=signature, extobj=extobj) | |
return wrap(ainv.astype(result_t, copy=False)) | |
def _matrix_power_dispatcher(a, n): | |
return (a,) | |
def matrix_power(a, n): | |
""" | |
Raise a square matrix to the (integer) power `n`. | |
For positive integers `n`, the power is computed by repeated matrix | |
squarings and matrix multiplications. If ``n == 0``, the identity matrix | |
of the same shape as M is returned. If ``n < 0``, the inverse | |
is computed and then raised to the ``abs(n)``. | |
.. note:: Stacks of object matrices are not currently supported. | |
Parameters | |
---------- | |
a : (..., M, M) array_like | |
Matrix to be "powered". | |
n : int | |
The exponent can be any integer or long integer, positive, | |
negative, or zero. | |
Returns | |
------- | |
a**n : (..., M, M) ndarray or matrix object | |
The return value is the same shape and type as `M`; | |
if the exponent is positive or zero then the type of the | |
elements is the same as those of `M`. If the exponent is | |
negative the elements are floating-point. | |
Raises | |
------ | |
LinAlgError | |
For matrices that are not square or that (for negative powers) cannot | |
be inverted numerically. | |
Examples | |
-------- | |
>>> from numpy.linalg import matrix_power | |
>>> i = np.array([[0, 1], [-1, 0]]) # matrix equiv. of the imaginary unit | |
>>> matrix_power(i, 3) # should = -i | |
array([[ 0, -1], | |
[ 1, 0]]) | |
>>> matrix_power(i, 0) | |
array([[1, 0], | |
[0, 1]]) | |
>>> matrix_power(i, -3) # should = 1/(-i) = i, but w/ f.p. elements | |
array([[ 0., 1.], | |
[-1., 0.]]) | |
Somewhat more sophisticated example | |
>>> q = np.zeros((4, 4)) | |
>>> q[0:2, 0:2] = -i | |
>>> q[2:4, 2:4] = i | |
>>> q # one of the three quaternion units not equal to 1 | |
array([[ 0., -1., 0., 0.], | |
[ 1., 0., 0., 0.], | |
[ 0., 0., 0., 1.], | |
[ 0., 0., -1., 0.]]) | |
>>> matrix_power(q, 2) # = -np.eye(4) | |
array([[-1., 0., 0., 0.], | |
[ 0., -1., 0., 0.], | |
[ 0., 0., -1., 0.], | |
[ 0., 0., 0., -1.]]) | |
""" | |
a = asanyarray(a) | |
_assert_stacked_2d(a) | |
_assert_stacked_square(a) | |
try: | |
n = operator.index(n) | |
except TypeError as e: | |
raise TypeError("exponent must be an integer") from e | |
# Fall back on dot for object arrays. Object arrays are not supported by | |
# the current implementation of matmul using einsum | |
if a.dtype != object: | |
fmatmul = matmul | |
elif a.ndim == 2: | |
fmatmul = dot | |
else: | |
raise NotImplementedError( | |
"matrix_power not supported for stacks of object arrays") | |
if n == 0: | |
a = empty_like(a) | |
a[...] = eye(a.shape[-2], dtype=a.dtype) | |
return a | |
elif n < 0: | |
a = inv(a) | |
n = abs(n) | |
# short-cuts. | |
if n == 1: | |
return a | |
elif n == 2: | |
return fmatmul(a, a) | |
elif n == 3: | |
return fmatmul(fmatmul(a, a), a) | |
# Use binary decomposition to reduce the number of matrix multiplications. | |
# Here, we iterate over the bits of n, from LSB to MSB, raise `a` to | |
# increasing powers of 2, and multiply into the result as needed. | |
z = result = None | |
while n > 0: | |
z = a if z is None else fmatmul(z, z) | |
n, bit = divmod(n, 2) | |
if bit: | |
result = z if result is None else fmatmul(result, z) | |
return result | |
# Cholesky decomposition | |
def cholesky(a): | |
""" | |
Cholesky decomposition. | |
Return the Cholesky decomposition, `L * L.H`, of the square matrix `a`, | |
where `L` is lower-triangular and .H is the conjugate transpose operator | |
(which is the ordinary transpose if `a` is real-valued). `a` must be | |
Hermitian (symmetric if real-valued) and positive-definite. No | |
checking is performed to verify whether `a` is Hermitian or not. | |
In addition, only the lower-triangular and diagonal elements of `a` | |
are used. Only `L` is actually returned. | |
Parameters | |
---------- | |
a : (..., M, M) array_like | |
Hermitian (symmetric if all elements are real), positive-definite | |
input matrix. | |
Returns | |
------- | |
L : (..., M, M) array_like | |
Upper or lower-triangular Cholesky factor of `a`. Returns a | |
matrix object if `a` is a matrix object. | |
Raises | |
------ | |
LinAlgError | |
If the decomposition fails, for example, if `a` is not | |
positive-definite. | |
See Also | |
-------- | |
scipy.linalg.cholesky : Similar function in SciPy. | |
scipy.linalg.cholesky_banded : Cholesky decompose a banded Hermitian | |
positive-definite matrix. | |
scipy.linalg.cho_factor : Cholesky decomposition of a matrix, to use in | |
`scipy.linalg.cho_solve`. | |
Notes | |
----- | |
.. versionadded:: 1.8.0 | |
Broadcasting rules apply, see the `numpy.linalg` documentation for | |
details. | |
The Cholesky decomposition is often used as a fast way of solving | |
.. math:: A \\mathbf{x} = \\mathbf{b} | |
(when `A` is both Hermitian/symmetric and positive-definite). | |
First, we solve for :math:`\\mathbf{y}` in | |
.. math:: L \\mathbf{y} = \\mathbf{b}, | |
and then for :math:`\\mathbf{x}` in | |
.. math:: L.H \\mathbf{x} = \\mathbf{y}. | |
Examples | |
-------- | |
>>> A = np.array([[1,-2j],[2j,5]]) | |
>>> A | |
array([[ 1.+0.j, -0.-2.j], | |
[ 0.+2.j, 5.+0.j]]) | |
>>> L = np.linalg.cholesky(A) | |
>>> L | |
array([[1.+0.j, 0.+0.j], | |
[0.+2.j, 1.+0.j]]) | |
>>> np.dot(L, L.T.conj()) # verify that L * L.H = A | |
array([[1.+0.j, 0.-2.j], | |
[0.+2.j, 5.+0.j]]) | |
>>> A = [[1,-2j],[2j,5]] # what happens if A is only array_like? | |
>>> np.linalg.cholesky(A) # an ndarray object is returned | |
array([[1.+0.j, 0.+0.j], | |
[0.+2.j, 1.+0.j]]) | |
>>> # But a matrix object is returned if A is a matrix object | |
>>> np.linalg.cholesky(np.matrix(A)) | |
matrix([[ 1.+0.j, 0.+0.j], | |
[ 0.+2.j, 1.+0.j]]) | |
""" | |
extobj = get_linalg_error_extobj(_raise_linalgerror_nonposdef) | |
gufunc = _umath_linalg.cholesky_lo | |
a, wrap = _makearray(a) | |
_assert_stacked_2d(a) | |
_assert_stacked_square(a) | |
t, result_t = _commonType(a) | |
signature = 'D->D' if isComplexType(t) else 'd->d' | |
r = gufunc(a, signature=signature, extobj=extobj) | |
return wrap(r.astype(result_t, copy=False)) | |
# QR decomposition | |
def _qr_dispatcher(a, mode=None): | |
return (a,) | |
def qr(a, mode='reduced'): | |
""" | |
Compute the qr factorization of a matrix. | |
Factor the matrix `a` as *qr*, where `q` is orthonormal and `r` is | |
upper-triangular. | |
Parameters | |
---------- | |
a : array_like, shape (M, N) | |
Matrix to be factored. | |
mode : {'reduced', 'complete', 'r', 'raw'}, optional | |
If K = min(M, N), then | |
* 'reduced' : returns q, r with dimensions (M, K), (K, N) (default) | |
* 'complete' : returns q, r with dimensions (M, M), (M, N) | |
* 'r' : returns r only with dimensions (K, N) | |
* 'raw' : returns h, tau with dimensions (N, M), (K,) | |
The options 'reduced', 'complete, and 'raw' are new in numpy 1.8, | |
see the notes for more information. The default is 'reduced', and to | |
maintain backward compatibility with earlier versions of numpy both | |
it and the old default 'full' can be omitted. Note that array h | |
returned in 'raw' mode is transposed for calling Fortran. The | |
'economic' mode is deprecated. The modes 'full' and 'economic' may | |
be passed using only the first letter for backwards compatibility, | |
but all others must be spelled out. See the Notes for more | |
explanation. | |
Returns | |
------- | |
q : ndarray of float or complex, optional | |
A matrix with orthonormal columns. When mode = 'complete' the | |
result is an orthogonal/unitary matrix depending on whether or not | |
a is real/complex. The determinant may be either +/- 1 in that | |
case. | |
r : ndarray of float or complex, optional | |
The upper-triangular matrix. | |
(h, tau) : ndarrays of np.double or np.cdouble, optional | |
The array h contains the Householder reflectors that generate q | |
along with r. The tau array contains scaling factors for the | |
reflectors. In the deprecated 'economic' mode only h is returned. | |
Raises | |
------ | |
LinAlgError | |
If factoring fails. | |
See Also | |
-------- | |
scipy.linalg.qr : Similar function in SciPy. | |
scipy.linalg.rq : Compute RQ decomposition of a matrix. | |
Notes | |
----- | |
This is an interface to the LAPACK routines ``dgeqrf``, ``zgeqrf``, | |
``dorgqr``, and ``zungqr``. | |
For more information on the qr factorization, see for example: | |
https://en.wikipedia.org/wiki/QR_factorization | |
Subclasses of `ndarray` are preserved except for the 'raw' mode. So if | |
`a` is of type `matrix`, all the return values will be matrices too. | |
New 'reduced', 'complete', and 'raw' options for mode were added in | |
NumPy 1.8.0 and the old option 'full' was made an alias of 'reduced'. In | |
addition the options 'full' and 'economic' were deprecated. Because | |
'full' was the previous default and 'reduced' is the new default, | |
backward compatibility can be maintained by letting `mode` default. | |
The 'raw' option was added so that LAPACK routines that can multiply | |
arrays by q using the Householder reflectors can be used. Note that in | |
this case the returned arrays are of type np.double or np.cdouble and | |
the h array is transposed to be FORTRAN compatible. No routines using | |
the 'raw' return are currently exposed by numpy, but some are available | |
in lapack_lite and just await the necessary work. | |
Examples | |
-------- | |
>>> a = np.random.randn(9, 6) | |
>>> q, r = np.linalg.qr(a) | |
>>> np.allclose(a, np.dot(q, r)) # a does equal qr | |
True | |
>>> r2 = np.linalg.qr(a, mode='r') | |
>>> np.allclose(r, r2) # mode='r' returns the same r as mode='full' | |
True | |
Example illustrating a common use of `qr`: solving of least squares | |
problems | |
What are the least-squares-best `m` and `y0` in ``y = y0 + mx`` for | |
the following data: {(0,1), (1,0), (1,2), (2,1)}. (Graph the points | |
and you'll see that it should be y0 = 0, m = 1.) The answer is provided | |
by solving the over-determined matrix equation ``Ax = b``, where:: | |
A = array([[0, 1], [1, 1], [1, 1], [2, 1]]) | |
x = array([[y0], [m]]) | |
b = array([[1], [0], [2], [1]]) | |
If A = qr such that q is orthonormal (which is always possible via | |
Gram-Schmidt), then ``x = inv(r) * (q.T) * b``. (In numpy practice, | |
however, we simply use `lstsq`.) | |
>>> A = np.array([[0, 1], [1, 1], [1, 1], [2, 1]]) | |
>>> A | |
array([[0, 1], | |
[1, 1], | |
[1, 1], | |
[2, 1]]) | |
>>> b = np.array([1, 0, 2, 1]) | |
>>> q, r = np.linalg.qr(A) | |
>>> p = np.dot(q.T, b) | |
>>> np.dot(np.linalg.inv(r), p) | |
array([ 1.1e-16, 1.0e+00]) | |
""" | |
if mode not in ('reduced', 'complete', 'r', 'raw'): | |
if mode in ('f', 'full'): | |
# 2013-04-01, 1.8 | |
msg = "".join(( | |
"The 'full' option is deprecated in favor of 'reduced'.\n", | |
"For backward compatibility let mode default.")) | |
warnings.warn(msg, DeprecationWarning, stacklevel=3) | |
mode = 'reduced' | |
elif mode in ('e', 'economic'): | |
# 2013-04-01, 1.8 | |
msg = "The 'economic' option is deprecated." | |
warnings.warn(msg, DeprecationWarning, stacklevel=3) | |
mode = 'economic' | |
else: | |
raise ValueError(f"Unrecognized mode '{mode}'") | |
a, wrap = _makearray(a) | |
_assert_2d(a) | |
m, n = a.shape | |
t, result_t = _commonType(a) | |
a = _fastCopyAndTranspose(t, a) | |
a = _to_native_byte_order(a) | |
mn = min(m, n) | |
tau = zeros((mn,), t) | |
if isComplexType(t): | |
lapack_routine = lapack_lite.zgeqrf | |
routine_name = 'zgeqrf' | |
else: | |
lapack_routine = lapack_lite.dgeqrf | |
routine_name = 'dgeqrf' | |
# calculate optimal size of work data 'work' | |
lwork = 1 | |
work = zeros((lwork,), t) | |
results = lapack_routine(m, n, a, max(1, m), tau, work, -1, 0) | |
if results['info'] != 0: | |
raise LinAlgError('%s returns %d' % (routine_name, results['info'])) | |
# do qr decomposition | |
lwork = max(1, n, int(abs(work[0]))) | |
work = zeros((lwork,), t) | |
results = lapack_routine(m, n, a, max(1, m), tau, work, lwork, 0) | |
if results['info'] != 0: | |
raise LinAlgError('%s returns %d' % (routine_name, results['info'])) | |
# handle modes that don't return q | |
if mode == 'r': | |
r = _fastCopyAndTranspose(result_t, a[:, :mn]) | |
return wrap(triu(r)) | |
if mode == 'raw': | |
return a, tau | |
if mode == 'economic': | |
if t != result_t : | |
a = a.astype(result_t, copy=False) | |
return wrap(a.T) | |
# generate q from a | |
if mode == 'complete' and m > n: | |
mc = m | |
q = empty((m, m), t) | |
else: | |
mc = mn | |
q = empty((n, m), t) | |
q[:n] = a | |
if isComplexType(t): | |
lapack_routine = lapack_lite.zungqr | |
routine_name = 'zungqr' | |
else: | |
lapack_routine = lapack_lite.dorgqr | |
routine_name = 'dorgqr' | |
# determine optimal lwork | |
lwork = 1 | |
work = zeros((lwork,), t) | |
results = lapack_routine(m, mc, mn, q, max(1, m), tau, work, -1, 0) | |
if results['info'] != 0: | |
raise LinAlgError('%s returns %d' % (routine_name, results['info'])) | |
# compute q | |
lwork = max(1, n, int(abs(work[0]))) | |
work = zeros((lwork,), t) | |
results = lapack_routine(m, mc, mn, q, max(1, m), tau, work, lwork, 0) | |
if results['info'] != 0: | |
raise LinAlgError('%s returns %d' % (routine_name, results['info'])) | |
q = _fastCopyAndTranspose(result_t, q[:mc]) | |
r = _fastCopyAndTranspose(result_t, a[:, :mc]) | |
return wrap(q), wrap(triu(r)) | |
# Eigenvalues | |
def eigvals(a): | |
""" | |
Compute the eigenvalues of a general matrix. | |
Main difference between `eigvals` and `eig`: the eigenvectors aren't | |
returned. | |
Parameters | |
---------- | |
a : (..., M, M) array_like | |
A complex- or real-valued matrix whose eigenvalues will be computed. | |
Returns | |
------- | |
w : (..., M,) ndarray | |
The eigenvalues, each repeated according to its multiplicity. | |
They are not necessarily ordered, nor are they necessarily | |
real for real matrices. | |
Raises | |
------ | |
LinAlgError | |
If the eigenvalue computation does not converge. | |
See Also | |
-------- | |
eig : eigenvalues and right eigenvectors of general arrays | |
eigvalsh : eigenvalues of real symmetric or complex Hermitian | |
(conjugate symmetric) arrays. | |
eigh : eigenvalues and eigenvectors of real symmetric or complex | |
Hermitian (conjugate symmetric) arrays. | |
scipy.linalg.eigvals : Similar function in SciPy. | |
Notes | |
----- | |
.. versionadded:: 1.8.0 | |
Broadcasting rules apply, see the `numpy.linalg` documentation for | |
details. | |
This is implemented using the ``_geev`` LAPACK routines which compute | |
the eigenvalues and eigenvectors of general square arrays. | |
Examples | |
-------- | |
Illustration, using the fact that the eigenvalues of a diagonal matrix | |
are its diagonal elements, that multiplying a matrix on the left | |
by an orthogonal matrix, `Q`, and on the right by `Q.T` (the transpose | |
of `Q`), preserves the eigenvalues of the "middle" matrix. In other words, | |
if `Q` is orthogonal, then ``Q * A * Q.T`` has the same eigenvalues as | |
``A``: | |
>>> from numpy import linalg as LA | |
>>> x = np.random.random() | |
>>> Q = np.array([[np.cos(x), -np.sin(x)], [np.sin(x), np.cos(x)]]) | |
>>> LA.norm(Q[0, :]), LA.norm(Q[1, :]), np.dot(Q[0, :],Q[1, :]) | |
(1.0, 1.0, 0.0) | |
Now multiply a diagonal matrix by ``Q`` on one side and by ``Q.T`` on the other: | |
>>> D = np.diag((-1,1)) | |
>>> LA.eigvals(D) | |
array([-1., 1.]) | |
>>> A = np.dot(Q, D) | |
>>> A = np.dot(A, Q.T) | |
>>> LA.eigvals(A) | |
array([ 1., -1.]) # random | |
""" | |
a, wrap = _makearray(a) | |
_assert_stacked_2d(a) | |
_assert_stacked_square(a) | |
_assert_finite(a) | |
t, result_t = _commonType(a) | |
extobj = get_linalg_error_extobj( | |
_raise_linalgerror_eigenvalues_nonconvergence) | |
signature = 'D->D' if isComplexType(t) else 'd->D' | |
w = _umath_linalg.eigvals(a, signature=signature, extobj=extobj) | |
if not isComplexType(t): | |
if all(w.imag == 0): | |
w = w.real | |
result_t = _realType(result_t) | |
else: | |
result_t = _complexType(result_t) | |
return w.astype(result_t, copy=False) | |
def _eigvalsh_dispatcher(a, UPLO=None): | |
return (a,) | |
def eigvalsh(a, UPLO='L'): | |
""" | |
Compute the eigenvalues of a complex Hermitian or real symmetric matrix. | |
Main difference from eigh: the eigenvectors are not computed. | |
Parameters | |
---------- | |
a : (..., M, M) array_like | |
A complex- or real-valued matrix whose eigenvalues are to be | |
computed. | |
UPLO : {'L', 'U'}, optional | |
Specifies whether the calculation is done with the lower triangular | |
part of `a` ('L', default) or the upper triangular part ('U'). | |
Irrespective of this value only the real parts of the diagonal will | |
be considered in the computation to preserve the notion of a Hermitian | |
matrix. It therefore follows that the imaginary part of the diagonal | |
will always be treated as zero. | |
Returns | |
------- | |
w : (..., M,) ndarray | |
The eigenvalues in ascending order, each repeated according to | |
its multiplicity. | |
Raises | |
------ | |
LinAlgError | |
If the eigenvalue computation does not converge. | |
See Also | |
-------- | |
eigh : eigenvalues and eigenvectors of real symmetric or complex Hermitian | |
(conjugate symmetric) arrays. | |
eigvals : eigenvalues of general real or complex arrays. | |
eig : eigenvalues and right eigenvectors of general real or complex | |
arrays. | |
scipy.linalg.eigvalsh : Similar function in SciPy. | |
Notes | |
----- | |
.. versionadded:: 1.8.0 | |
Broadcasting rules apply, see the `numpy.linalg` documentation for | |
details. | |
The eigenvalues are computed using LAPACK routines ``_syevd``, ``_heevd``. | |
Examples | |
-------- | |
>>> from numpy import linalg as LA | |
>>> a = np.array([[1, -2j], [2j, 5]]) | |
>>> LA.eigvalsh(a) | |
array([ 0.17157288, 5.82842712]) # may vary | |
>>> # demonstrate the treatment of the imaginary part of the diagonal | |
>>> a = np.array([[5+2j, 9-2j], [0+2j, 2-1j]]) | |
>>> a | |
array([[5.+2.j, 9.-2.j], | |
[0.+2.j, 2.-1.j]]) | |
>>> # with UPLO='L' this is numerically equivalent to using LA.eigvals() | |
>>> # with: | |
>>> b = np.array([[5.+0.j, 0.-2.j], [0.+2.j, 2.-0.j]]) | |
>>> b | |
array([[5.+0.j, 0.-2.j], | |
[0.+2.j, 2.+0.j]]) | |
>>> wa = LA.eigvalsh(a) | |
>>> wb = LA.eigvals(b) | |
>>> wa; wb | |
array([1., 6.]) | |
array([6.+0.j, 1.+0.j]) | |
""" | |
UPLO = UPLO.upper() | |
if UPLO not in ('L', 'U'): | |
raise ValueError("UPLO argument must be 'L' or 'U'") | |
extobj = get_linalg_error_extobj( | |
_raise_linalgerror_eigenvalues_nonconvergence) | |
if UPLO == 'L': | |
gufunc = _umath_linalg.eigvalsh_lo | |
else: | |
gufunc = _umath_linalg.eigvalsh_up | |
a, wrap = _makearray(a) | |
_assert_stacked_2d(a) | |
_assert_stacked_square(a) | |
t, result_t = _commonType(a) | |
signature = 'D->d' if isComplexType(t) else 'd->d' | |
w = gufunc(a, signature=signature, extobj=extobj) | |
return w.astype(_realType(result_t), copy=False) | |
def _convertarray(a): | |
t, result_t = _commonType(a) | |
a = _fastCT(a.astype(t)) | |
return a, t, result_t | |
# Eigenvectors | |
def eig(a): | |
""" | |
Compute the eigenvalues and right eigenvectors of a square array. | |
Parameters | |
---------- | |
a : (..., M, M) array | |
Matrices for which the eigenvalues and right eigenvectors will | |
be computed | |
Returns | |
------- | |
w : (..., M) array | |
The eigenvalues, each repeated according to its multiplicity. | |
The eigenvalues are not necessarily ordered. The resulting | |
array will be of complex type, unless the imaginary part is | |
zero in which case it will be cast to a real type. When `a` | |
is real the resulting eigenvalues will be real (0 imaginary | |
part) or occur in conjugate pairs | |
v : (..., M, M) array | |
The normalized (unit "length") eigenvectors, such that the | |
column ``v[:,i]`` is the eigenvector corresponding to the | |
eigenvalue ``w[i]``. | |
Raises | |
------ | |
LinAlgError | |
If the eigenvalue computation does not converge. | |
See Also | |
-------- | |
eigvals : eigenvalues of a non-symmetric array. | |
eigh : eigenvalues and eigenvectors of a real symmetric or complex | |
Hermitian (conjugate symmetric) array. | |
eigvalsh : eigenvalues of a real symmetric or complex Hermitian | |
(conjugate symmetric) array. | |
scipy.linalg.eig : Similar function in SciPy that also solves the | |
generalized eigenvalue problem. | |
scipy.linalg.schur : Best choice for unitary and other non-Hermitian | |
normal matrices. | |
Notes | |
----- | |
.. versionadded:: 1.8.0 | |
Broadcasting rules apply, see the `numpy.linalg` documentation for | |
details. | |
This is implemented using the ``_geev`` LAPACK routines which compute | |
the eigenvalues and eigenvectors of general square arrays. | |
The number `w` is an eigenvalue of `a` if there exists a vector | |
`v` such that ``a @ v = w * v``. Thus, the arrays `a`, `w`, and | |
`v` satisfy the equations ``a @ v[:,i] = w[i] * v[:,i]`` | |
for :math:`i \\in \\{0,...,M-1\\}`. | |
The array `v` of eigenvectors may not be of maximum rank, that is, some | |
of the columns may be linearly dependent, although round-off error may | |
obscure that fact. If the eigenvalues are all different, then theoretically | |
the eigenvectors are linearly independent and `a` can be diagonalized by | |
a similarity transformation using `v`, i.e, ``inv(v) @ a @ v`` is diagonal. | |
For non-Hermitian normal matrices the SciPy function `scipy.linalg.schur` | |
is preferred because the matrix `v` is guaranteed to be unitary, which is | |
not the case when using `eig`. The Schur factorization produces an | |
upper triangular matrix rather than a diagonal matrix, but for normal | |
matrices only the diagonal of the upper triangular matrix is needed, the | |
rest is roundoff error. | |
Finally, it is emphasized that `v` consists of the *right* (as in | |
right-hand side) eigenvectors of `a`. A vector `y` satisfying | |
``y.T @ a = z * y.T`` for some number `z` is called a *left* | |
eigenvector of `a`, and, in general, the left and right eigenvectors | |
of a matrix are not necessarily the (perhaps conjugate) transposes | |
of each other. | |
References | |
---------- | |
G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando, FL, | |
Academic Press, Inc., 1980, Various pp. | |
Examples | |
-------- | |
>>> from numpy import linalg as LA | |
(Almost) trivial example with real e-values and e-vectors. | |
>>> w, v = LA.eig(np.diag((1, 2, 3))) | |
>>> w; v | |
array([1., 2., 3.]) | |
array([[1., 0., 0.], | |
[0., 1., 0.], | |
[0., 0., 1.]]) | |
Real matrix possessing complex e-values and e-vectors; note that the | |
e-values are complex conjugates of each other. | |
>>> w, v = LA.eig(np.array([[1, -1], [1, 1]])) | |
>>> w; v | |
array([1.+1.j, 1.-1.j]) | |
array([[0.70710678+0.j , 0.70710678-0.j ], | |
[0. -0.70710678j, 0. +0.70710678j]]) | |
Complex-valued matrix with real e-values (but complex-valued e-vectors); | |
note that ``a.conj().T == a``, i.e., `a` is Hermitian. | |
>>> a = np.array([[1, 1j], [-1j, 1]]) | |
>>> w, v = LA.eig(a) | |
>>> w; v | |
array([2.+0.j, 0.+0.j]) | |
array([[ 0. +0.70710678j, 0.70710678+0.j ], # may vary | |
[ 0.70710678+0.j , -0. +0.70710678j]]) | |
Be careful about round-off error! | |
>>> a = np.array([[1 + 1e-9, 0], [0, 1 - 1e-9]]) | |
>>> # Theor. e-values are 1 +/- 1e-9 | |
>>> w, v = LA.eig(a) | |
>>> w; v | |
array([1., 1.]) | |
array([[1., 0.], | |
[0., 1.]]) | |
""" | |
a, wrap = _makearray(a) | |
_assert_stacked_2d(a) | |
_assert_stacked_square(a) | |
_assert_finite(a) | |
t, result_t = _commonType(a) | |
extobj = get_linalg_error_extobj( | |
_raise_linalgerror_eigenvalues_nonconvergence) | |
signature = 'D->DD' if isComplexType(t) else 'd->DD' | |
w, vt = _umath_linalg.eig(a, signature=signature, extobj=extobj) | |
if not isComplexType(t) and all(w.imag == 0.0): | |
w = w.real | |
vt = vt.real | |
result_t = _realType(result_t) | |
else: | |
result_t = _complexType(result_t) | |
vt = vt.astype(result_t, copy=False) | |
return w.astype(result_t, copy=False), wrap(vt) | |
def eigh(a, UPLO='L'): | |
""" | |
Return the eigenvalues and eigenvectors of a complex Hermitian | |
(conjugate symmetric) or a real symmetric matrix. | |
Returns two objects, a 1-D array containing the eigenvalues of `a`, and | |
a 2-D square array or matrix (depending on the input type) of the | |
corresponding eigenvectors (in columns). | |
Parameters | |
---------- | |
a : (..., M, M) array | |
Hermitian or real symmetric matrices whose eigenvalues and | |
eigenvectors are to be computed. | |
UPLO : {'L', 'U'}, optional | |
Specifies whether the calculation is done with the lower triangular | |
part of `a` ('L', default) or the upper triangular part ('U'). | |
Irrespective of this value only the real parts of the diagonal will | |
be considered in the computation to preserve the notion of a Hermitian | |
matrix. It therefore follows that the imaginary part of the diagonal | |
will always be treated as zero. | |
Returns | |
------- | |
w : (..., M) ndarray | |
The eigenvalues in ascending order, each repeated according to | |
its multiplicity. | |
v : {(..., M, M) ndarray, (..., M, M) matrix} | |
The column ``v[:, i]`` is the normalized eigenvector corresponding | |
to the eigenvalue ``w[i]``. Will return a matrix object if `a` is | |
a matrix object. | |
Raises | |
------ | |
LinAlgError | |
If the eigenvalue computation does not converge. | |
See Also | |
-------- | |
eigvalsh : eigenvalues of real symmetric or complex Hermitian | |
(conjugate symmetric) arrays. | |
eig : eigenvalues and right eigenvectors for non-symmetric arrays. | |
eigvals : eigenvalues of non-symmetric arrays. | |
scipy.linalg.eigh : Similar function in SciPy (but also solves the | |
generalized eigenvalue problem). | |
Notes | |
----- | |
.. versionadded:: 1.8.0 | |
Broadcasting rules apply, see the `numpy.linalg` documentation for | |
details. | |
The eigenvalues/eigenvectors are computed using LAPACK routines ``_syevd``, | |
``_heevd``. | |
The eigenvalues of real symmetric or complex Hermitian matrices are | |
always real. [1]_ The array `v` of (column) eigenvectors is unitary | |
and `a`, `w`, and `v` satisfy the equations | |
``dot(a, v[:, i]) = w[i] * v[:, i]``. | |
References | |
---------- | |
.. [1] G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando, | |
FL, Academic Press, Inc., 1980, pg. 222. | |
Examples | |
-------- | |
>>> from numpy import linalg as LA | |
>>> a = np.array([[1, -2j], [2j, 5]]) | |
>>> a | |
array([[ 1.+0.j, -0.-2.j], | |
[ 0.+2.j, 5.+0.j]]) | |
>>> w, v = LA.eigh(a) | |
>>> w; v | |
array([0.17157288, 5.82842712]) | |
array([[-0.92387953+0.j , -0.38268343+0.j ], # may vary | |
[ 0. +0.38268343j, 0. -0.92387953j]]) | |
>>> np.dot(a, v[:, 0]) - w[0] * v[:, 0] # verify 1st e-val/vec pair | |
array([5.55111512e-17+0.0000000e+00j, 0.00000000e+00+1.2490009e-16j]) | |
>>> np.dot(a, v[:, 1]) - w[1] * v[:, 1] # verify 2nd e-val/vec pair | |
array([0.+0.j, 0.+0.j]) | |
>>> A = np.matrix(a) # what happens if input is a matrix object | |
>>> A | |
matrix([[ 1.+0.j, -0.-2.j], | |
[ 0.+2.j, 5.+0.j]]) | |
>>> w, v = LA.eigh(A) | |
>>> w; v | |
array([0.17157288, 5.82842712]) | |
matrix([[-0.92387953+0.j , -0.38268343+0.j ], # may vary | |
[ 0. +0.38268343j, 0. -0.92387953j]]) | |
>>> # demonstrate the treatment of the imaginary part of the diagonal | |
>>> a = np.array([[5+2j, 9-2j], [0+2j, 2-1j]]) | |
>>> a | |
array([[5.+2.j, 9.-2.j], | |
[0.+2.j, 2.-1.j]]) | |
>>> # with UPLO='L' this is numerically equivalent to using LA.eig() with: | |
>>> b = np.array([[5.+0.j, 0.-2.j], [0.+2.j, 2.-0.j]]) | |
>>> b | |
array([[5.+0.j, 0.-2.j], | |
[0.+2.j, 2.+0.j]]) | |
>>> wa, va = LA.eigh(a) | |
>>> wb, vb = LA.eig(b) | |
>>> wa; wb | |
array([1., 6.]) | |
array([6.+0.j, 1.+0.j]) | |
>>> va; vb | |
array([[-0.4472136 +0.j , -0.89442719+0.j ], # may vary | |
[ 0. +0.89442719j, 0. -0.4472136j ]]) | |
array([[ 0.89442719+0.j , -0. +0.4472136j], | |
[-0. +0.4472136j, 0.89442719+0.j ]]) | |
""" | |
UPLO = UPLO.upper() | |
if UPLO not in ('L', 'U'): | |
raise ValueError("UPLO argument must be 'L' or 'U'") | |
a, wrap = _makearray(a) | |
_assert_stacked_2d(a) | |
_assert_stacked_square(a) | |
t, result_t = _commonType(a) | |
extobj = get_linalg_error_extobj( | |
_raise_linalgerror_eigenvalues_nonconvergence) | |
if UPLO == 'L': | |
gufunc = _umath_linalg.eigh_lo | |
else: | |
gufunc = _umath_linalg.eigh_up | |
signature = 'D->dD' if isComplexType(t) else 'd->dd' | |
w, vt = gufunc(a, signature=signature, extobj=extobj) | |
w = w.astype(_realType(result_t), copy=False) | |
vt = vt.astype(result_t, copy=False) | |
return w, wrap(vt) | |
# Singular value decomposition | |
def _svd_dispatcher(a, full_matrices=None, compute_uv=None, hermitian=None): | |
return (a,) | |
def svd(a, full_matrices=True, compute_uv=True, hermitian=False): | |
""" | |
Singular Value Decomposition. | |
When `a` is a 2D array, it is factorized as ``u @ np.diag(s) @ vh | |
= (u * s) @ vh``, where `u` and `vh` are 2D unitary arrays and `s` is a 1D | |
array of `a`'s singular values. When `a` is higher-dimensional, SVD is | |
applied in stacked mode as explained below. | |
Parameters | |
---------- | |
a : (..., M, N) array_like | |
A real or complex array with ``a.ndim >= 2``. | |
full_matrices : bool, optional | |
If True (default), `u` and `vh` have the shapes ``(..., M, M)`` and | |
``(..., N, N)``, respectively. Otherwise, the shapes are | |
``(..., M, K)`` and ``(..., K, N)``, respectively, where | |
``K = min(M, N)``. | |
compute_uv : bool, optional | |
Whether or not to compute `u` and `vh` in addition to `s`. True | |
by default. | |
hermitian : bool, optional | |
If True, `a` is assumed to be Hermitian (symmetric if real-valued), | |
enabling a more efficient method for finding singular values. | |
Defaults to False. | |
.. versionadded:: 1.17.0 | |
Returns | |
------- | |
u : { (..., M, M), (..., M, K) } array | |
Unitary array(s). The first ``a.ndim - 2`` dimensions have the same | |
size as those of the input `a`. The size of the last two dimensions | |
depends on the value of `full_matrices`. Only returned when | |
`compute_uv` is True. | |
s : (..., K) array | |
Vector(s) with the singular values, within each vector sorted in | |
descending order. The first ``a.ndim - 2`` dimensions have the same | |
size as those of the input `a`. | |
vh : { (..., N, N), (..., K, N) } array | |
Unitary array(s). The first ``a.ndim - 2`` dimensions have the same | |
size as those of the input `a`. The size of the last two dimensions | |
depends on the value of `full_matrices`. Only returned when | |
`compute_uv` is True. | |
Raises | |
------ | |
LinAlgError | |
If SVD computation does not converge. | |
See Also | |
-------- | |
scipy.linalg.svd : Similar function in SciPy. | |
scipy.linalg.svdvals : Compute singular values of a matrix. | |
Notes | |
----- | |
.. versionchanged:: 1.8.0 | |
Broadcasting rules apply, see the `numpy.linalg` documentation for | |
details. | |
The decomposition is performed using LAPACK routine ``_gesdd``. | |
SVD is usually described for the factorization of a 2D matrix :math:`A`. | |
The higher-dimensional case will be discussed below. In the 2D case, SVD is | |
written as :math:`A = U S V^H`, where :math:`A = a`, :math:`U= u`, | |
:math:`S= \\mathtt{np.diag}(s)` and :math:`V^H = vh`. The 1D array `s` | |
contains the singular values of `a` and `u` and `vh` are unitary. The rows | |
of `vh` are the eigenvectors of :math:`A^H A` and the columns of `u` are | |
the eigenvectors of :math:`A A^H`. In both cases the corresponding | |
(possibly non-zero) eigenvalues are given by ``s**2``. | |
If `a` has more than two dimensions, then broadcasting rules apply, as | |
explained in :ref:`routines.linalg-broadcasting`. This means that SVD is | |
working in "stacked" mode: it iterates over all indices of the first | |
``a.ndim - 2`` dimensions and for each combination SVD is applied to the | |
last two indices. The matrix `a` can be reconstructed from the | |
decomposition with either ``(u * s[..., None, :]) @ vh`` or | |
``u @ (s[..., None] * vh)``. (The ``@`` operator can be replaced by the | |
function ``np.matmul`` for python versions below 3.5.) | |
If `a` is a ``matrix`` object (as opposed to an ``ndarray``), then so are | |
all the return values. | |
Examples | |
-------- | |
>>> a = np.random.randn(9, 6) + 1j*np.random.randn(9, 6) | |
>>> b = np.random.randn(2, 7, 8, 3) + 1j*np.random.randn(2, 7, 8, 3) | |
Reconstruction based on full SVD, 2D case: | |
>>> u, s, vh = np.linalg.svd(a, full_matrices=True) | |
>>> u.shape, s.shape, vh.shape | |
((9, 9), (6,), (6, 6)) | |
>>> np.allclose(a, np.dot(u[:, :6] * s, vh)) | |
True | |
>>> smat = np.zeros((9, 6), dtype=complex) | |
>>> smat[:6, :6] = np.diag(s) | |
>>> np.allclose(a, np.dot(u, np.dot(smat, vh))) | |
True | |
Reconstruction based on reduced SVD, 2D case: | |
>>> u, s, vh = np.linalg.svd(a, full_matrices=False) | |
>>> u.shape, s.shape, vh.shape | |
((9, 6), (6,), (6, 6)) | |
>>> np.allclose(a, np.dot(u * s, vh)) | |
True | |
>>> smat = np.diag(s) | |
>>> np.allclose(a, np.dot(u, np.dot(smat, vh))) | |
True | |
Reconstruction based on full SVD, 4D case: | |
>>> u, s, vh = np.linalg.svd(b, full_matrices=True) | |
>>> u.shape, s.shape, vh.shape | |
((2, 7, 8, 8), (2, 7, 3), (2, 7, 3, 3)) | |
>>> np.allclose(b, np.matmul(u[..., :3] * s[..., None, :], vh)) | |
True | |
>>> np.allclose(b, np.matmul(u[..., :3], s[..., None] * vh)) | |
True | |
Reconstruction based on reduced SVD, 4D case: | |
>>> u, s, vh = np.linalg.svd(b, full_matrices=False) | |
>>> u.shape, s.shape, vh.shape | |
((2, 7, 8, 3), (2, 7, 3), (2, 7, 3, 3)) | |
>>> np.allclose(b, np.matmul(u * s[..., None, :], vh)) | |
True | |
>>> np.allclose(b, np.matmul(u, s[..., None] * vh)) | |
True | |
""" | |
import numpy as _nx | |
a, wrap = _makearray(a) | |
if hermitian: | |
# note: lapack svd returns eigenvalues with s ** 2 sorted descending, | |
# but eig returns s sorted ascending, so we re-order the eigenvalues | |
# and related arrays to have the correct order | |
if compute_uv: | |
s, u = eigh(a) | |
sgn = sign(s) | |
s = abs(s) | |
sidx = argsort(s)[..., ::-1] | |
sgn = _nx.take_along_axis(sgn, sidx, axis=-1) | |
s = _nx.take_along_axis(s, sidx, axis=-1) | |
u = _nx.take_along_axis(u, sidx[..., None, :], axis=-1) | |
# singular values are unsigned, move the sign into v | |
vt = transpose(u * sgn[..., None, :]).conjugate() | |
return wrap(u), s, wrap(vt) | |
else: | |
s = eigvalsh(a) | |
s = s[..., ::-1] | |
s = abs(s) | |
return sort(s)[..., ::-1] | |
_assert_stacked_2d(a) | |
t, result_t = _commonType(a) | |
extobj = get_linalg_error_extobj(_raise_linalgerror_svd_nonconvergence) | |
m, n = a.shape[-2:] | |
if compute_uv: | |
if full_matrices: | |
if m < n: | |
gufunc = _umath_linalg.svd_m_f | |
else: | |
gufunc = _umath_linalg.svd_n_f | |
else: | |
if m < n: | |
gufunc = _umath_linalg.svd_m_s | |
else: | |
gufunc = _umath_linalg.svd_n_s | |
signature = 'D->DdD' if isComplexType(t) else 'd->ddd' | |
u, s, vh = gufunc(a, signature=signature, extobj=extobj) | |
u = u.astype(result_t, copy=False) | |
s = s.astype(_realType(result_t), copy=False) | |
vh = vh.astype(result_t, copy=False) | |
return wrap(u), s, wrap(vh) | |
else: | |
if m < n: | |
gufunc = _umath_linalg.svd_m | |
else: | |
gufunc = _umath_linalg.svd_n | |
signature = 'D->d' if isComplexType(t) else 'd->d' | |
s = gufunc(a, signature=signature, extobj=extobj) | |
s = s.astype(_realType(result_t), copy=False) | |
return s | |
def _cond_dispatcher(x, p=None): | |
return (x,) | |
def cond(x, p=None): | |
""" | |
Compute the condition number of a matrix. | |
This function is capable of returning the condition number using | |
one of seven different norms, depending on the value of `p` (see | |
Parameters below). | |
Parameters | |
---------- | |
x : (..., M, N) array_like | |
The matrix whose condition number is sought. | |
p : {None, 1, -1, 2, -2, inf, -inf, 'fro'}, optional | |
Order of the norm: | |
===== ============================ | |
p norm for matrices | |
===== ============================ | |
None 2-norm, computed directly using the ``SVD`` | |
'fro' Frobenius norm | |
inf max(sum(abs(x), axis=1)) | |
-inf min(sum(abs(x), axis=1)) | |
1 max(sum(abs(x), axis=0)) | |
-1 min(sum(abs(x), axis=0)) | |
2 2-norm (largest sing. value) | |
-2 smallest singular value | |
===== ============================ | |
inf means the numpy.inf object, and the Frobenius norm is | |
the root-of-sum-of-squares norm. | |
Returns | |
------- | |
c : {float, inf} | |
The condition number of the matrix. May be infinite. | |
See Also | |
-------- | |
numpy.linalg.norm | |
Notes | |
----- | |
The condition number of `x` is defined as the norm of `x` times the | |
norm of the inverse of `x` [1]_; the norm can be the usual L2-norm | |
(root-of-sum-of-squares) or one of a number of other matrix norms. | |
References | |
---------- | |
.. [1] G. Strang, *Linear Algebra and Its Applications*, Orlando, FL, | |
Academic Press, Inc., 1980, pg. 285. | |
Examples | |
-------- | |
>>> from numpy import linalg as LA | |
>>> a = np.array([[1, 0, -1], [0, 1, 0], [1, 0, 1]]) | |
>>> a | |
array([[ 1, 0, -1], | |
[ 0, 1, 0], | |
[ 1, 0, 1]]) | |
>>> LA.cond(a) | |
1.4142135623730951 | |
>>> LA.cond(a, 'fro') | |
3.1622776601683795 | |
>>> LA.cond(a, np.inf) | |
2.0 | |
>>> LA.cond(a, -np.inf) | |
1.0 | |
>>> LA.cond(a, 1) | |
2.0 | |
>>> LA.cond(a, -1) | |
1.0 | |
>>> LA.cond(a, 2) | |
1.4142135623730951 | |
>>> LA.cond(a, -2) | |
0.70710678118654746 # may vary | |
>>> min(LA.svd(a, compute_uv=False))*min(LA.svd(LA.inv(a), compute_uv=False)) | |
0.70710678118654746 # may vary | |
""" | |
x = asarray(x) # in case we have a matrix | |
if _is_empty_2d(x): | |
raise LinAlgError("cond is not defined on empty arrays") | |
if p is None or p == 2 or p == -2: | |
s = svd(x, compute_uv=False) | |
with errstate(all='ignore'): | |
if p == -2: | |
r = s[..., -1] / s[..., 0] | |
else: | |
r = s[..., 0] / s[..., -1] | |
else: | |
# Call inv(x) ignoring errors. The result array will | |
# contain nans in the entries where inversion failed. | |
_assert_stacked_2d(x) | |
_assert_stacked_square(x) | |
t, result_t = _commonType(x) | |
signature = 'D->D' if isComplexType(t) else 'd->d' | |
with errstate(all='ignore'): | |
invx = _umath_linalg.inv(x, signature=signature) | |
r = norm(x, p, axis=(-2, -1)) * norm(invx, p, axis=(-2, -1)) | |
r = r.astype(result_t, copy=False) | |
# Convert nans to infs unless the original array had nan entries | |
r = asarray(r) | |
nan_mask = isnan(r) | |
if nan_mask.any(): | |
nan_mask &= ~isnan(x).any(axis=(-2, -1)) | |
if r.ndim > 0: | |
r[nan_mask] = Inf | |
elif nan_mask: | |
r[()] = Inf | |
# Convention is to return scalars instead of 0d arrays | |
if r.ndim == 0: | |
r = r[()] | |
return r | |
def _matrix_rank_dispatcher(M, tol=None, hermitian=None): | |
return (M,) | |
def matrix_rank(M, tol=None, hermitian=False): | |
""" | |
Return matrix rank of array using SVD method | |
Rank of the array is the number of singular values of the array that are | |
greater than `tol`. | |
.. versionchanged:: 1.14 | |
Can now operate on stacks of matrices | |
Parameters | |
---------- | |
M : {(M,), (..., M, N)} array_like | |
Input vector or stack of matrices. | |
tol : (...) array_like, float, optional | |
Threshold below which SVD values are considered zero. If `tol` is | |
None, and ``S`` is an array with singular values for `M`, and | |
``eps`` is the epsilon value for datatype of ``S``, then `tol` is | |
set to ``S.max() * max(M.shape) * eps``. | |
.. versionchanged:: 1.14 | |
Broadcasted against the stack of matrices | |
hermitian : bool, optional | |
If True, `M` is assumed to be Hermitian (symmetric if real-valued), | |
enabling a more efficient method for finding singular values. | |
Defaults to False. | |
.. versionadded:: 1.14 | |
Returns | |
------- | |
rank : (...) array_like | |
Rank of M. | |
Notes | |
----- | |
The default threshold to detect rank deficiency is a test on the magnitude | |
of the singular values of `M`. By default, we identify singular values less | |
than ``S.max() * max(M.shape) * eps`` as indicating rank deficiency (with | |
the symbols defined above). This is the algorithm MATLAB uses [1]. It also | |
appears in *Numerical recipes* in the discussion of SVD solutions for linear | |
least squares [2]. | |
This default threshold is designed to detect rank deficiency accounting for | |
the numerical errors of the SVD computation. Imagine that there is a column | |
in `M` that is an exact (in floating point) linear combination of other | |
columns in `M`. Computing the SVD on `M` will not produce a singular value | |
exactly equal to 0 in general: any difference of the smallest SVD value from | |
0 will be caused by numerical imprecision in the calculation of the SVD. | |
Our threshold for small SVD values takes this numerical imprecision into | |
account, and the default threshold will detect such numerical rank | |
deficiency. The threshold may declare a matrix `M` rank deficient even if | |
the linear combination of some columns of `M` is not exactly equal to | |
another column of `M` but only numerically very close to another column of | |
`M`. | |
We chose our default threshold because it is in wide use. Other thresholds | |
are possible. For example, elsewhere in the 2007 edition of *Numerical | |
recipes* there is an alternative threshold of ``S.max() * | |
np.finfo(M.dtype).eps / 2. * np.sqrt(m + n + 1.)``. The authors describe | |
this threshold as being based on "expected roundoff error" (p 71). | |
The thresholds above deal with floating point roundoff error in the | |
calculation of the SVD. However, you may have more information about the | |
sources of error in `M` that would make you consider other tolerance values | |
to detect *effective* rank deficiency. The most useful measure of the | |
tolerance depends on the operations you intend to use on your matrix. For | |
example, if your data come from uncertain measurements with uncertainties | |
greater than floating point epsilon, choosing a tolerance near that | |
uncertainty may be preferable. The tolerance may be absolute if the | |
uncertainties are absolute rather than relative. | |
References | |
---------- | |
.. [1] MATLAB reference documention, "Rank" | |
https://www.mathworks.com/help/techdoc/ref/rank.html | |
.. [2] W. H. Press, S. A. Teukolsky, W. T. Vetterling and B. P. Flannery, | |
"Numerical Recipes (3rd edition)", Cambridge University Press, 2007, | |
page 795. | |
Examples | |
-------- | |
>>> from numpy.linalg import matrix_rank | |
>>> matrix_rank(np.eye(4)) # Full rank matrix | |
4 | |
>>> I=np.eye(4); I[-1,-1] = 0. # rank deficient matrix | |
>>> matrix_rank(I) | |
3 | |
>>> matrix_rank(np.ones((4,))) # 1 dimension - rank 1 unless all 0 | |
1 | |
>>> matrix_rank(np.zeros((4,))) | |
0 | |
""" | |
M = asarray(M) | |
if M.ndim < 2: | |
return int(not all(M==0)) | |
S = svd(M, compute_uv=False, hermitian=hermitian) | |
if tol is None: | |
tol = S.max(axis=-1, keepdims=True) * max(M.shape[-2:]) * finfo(S.dtype).eps | |
else: | |
tol = asarray(tol)[..., newaxis] | |
return count_nonzero(S > tol, axis=-1) | |
# Generalized inverse | |
def _pinv_dispatcher(a, rcond=None, hermitian=None): | |
return (a,) | |
def pinv(a, rcond=1e-15, hermitian=False): | |
""" | |
Compute the (Moore-Penrose) pseudo-inverse of a matrix. | |
Calculate the generalized inverse of a matrix using its | |
singular-value decomposition (SVD) and including all | |
*large* singular values. | |
.. versionchanged:: 1.14 | |
Can now operate on stacks of matrices | |
Parameters | |
---------- | |
a : (..., M, N) array_like | |
Matrix or stack of matrices to be pseudo-inverted. | |
rcond : (...) array_like of float | |
Cutoff for small singular values. | |
Singular values less than or equal to | |
``rcond * largest_singular_value`` are set to zero. | |
Broadcasts against the stack of matrices. | |
hermitian : bool, optional | |
If True, `a` is assumed to be Hermitian (symmetric if real-valued), | |
enabling a more efficient method for finding singular values. | |
Defaults to False. | |
.. versionadded:: 1.17.0 | |
Returns | |
------- | |
B : (..., N, M) ndarray | |
The pseudo-inverse of `a`. If `a` is a `matrix` instance, then so | |
is `B`. | |
Raises | |
------ | |
LinAlgError | |
If the SVD computation does not converge. | |
See Also | |
-------- | |
scipy.linalg.pinv : Similar function in SciPy. | |
scipy.linalg.pinv2 : Similar function in SciPy (SVD-based). | |
scipy.linalg.pinvh : Compute the (Moore-Penrose) pseudo-inverse of a | |
Hermitian matrix. | |
Notes | |
----- | |
The pseudo-inverse of a matrix A, denoted :math:`A^+`, is | |
defined as: "the matrix that 'solves' [the least-squares problem] | |
:math:`Ax = b`," i.e., if :math:`\\bar{x}` is said solution, then | |
:math:`A^+` is that matrix such that :math:`\\bar{x} = A^+b`. | |
It can be shown that if :math:`Q_1 \\Sigma Q_2^T = A` is the singular | |
value decomposition of A, then | |
:math:`A^+ = Q_2 \\Sigma^+ Q_1^T`, where :math:`Q_{1,2}` are | |
orthogonal matrices, :math:`\\Sigma` is a diagonal matrix consisting | |
of A's so-called singular values, (followed, typically, by | |
zeros), and then :math:`\\Sigma^+` is simply the diagonal matrix | |
consisting of the reciprocals of A's singular values | |
(again, followed by zeros). [1]_ | |
References | |
---------- | |
.. [1] G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando, | |
FL, Academic Press, Inc., 1980, pp. 139-142. | |
Examples | |
-------- | |
The following example checks that ``a * a+ * a == a`` and | |
``a+ * a * a+ == a+``: | |
>>> a = np.random.randn(9, 6) | |
>>> B = np.linalg.pinv(a) | |
>>> np.allclose(a, np.dot(a, np.dot(B, a))) | |
True | |
>>> np.allclose(B, np.dot(B, np.dot(a, B))) | |
True | |
""" | |
a, wrap = _makearray(a) | |
rcond = asarray(rcond) | |
if _is_empty_2d(a): | |
m, n = a.shape[-2:] | |
res = empty(a.shape[:-2] + (n, m), dtype=a.dtype) | |
return wrap(res) | |
a = a.conjugate() | |
u, s, vt = svd(a, full_matrices=False, hermitian=hermitian) | |
# discard small singular values | |
cutoff = rcond[..., newaxis] * amax(s, axis=-1, keepdims=True) | |
large = s > cutoff | |
s = divide(1, s, where=large, out=s) | |
s[~large] = 0 | |
res = matmul(transpose(vt), multiply(s[..., newaxis], transpose(u))) | |
return wrap(res) | |
# Determinant | |
def slogdet(a): | |
""" | |
Compute the sign and (natural) logarithm of the determinant of an array. | |
If an array has a very small or very large determinant, then a call to | |
`det` may overflow or underflow. This routine is more robust against such | |
issues, because it computes the logarithm of the determinant rather than | |
the determinant itself. | |
Parameters | |
---------- | |
a : (..., M, M) array_like | |
Input array, has to be a square 2-D array. | |
Returns | |
------- | |
sign : (...) array_like | |
A number representing the sign of the determinant. For a real matrix, | |
this is 1, 0, or -1. For a complex matrix, this is a complex number | |
with absolute value 1 (i.e., it is on the unit circle), or else 0. | |
logdet : (...) array_like | |
The natural log of the absolute value of the determinant. | |
If the determinant is zero, then `sign` will be 0 and `logdet` will be | |
-Inf. In all cases, the determinant is equal to ``sign * np.exp(logdet)``. | |
See Also | |
-------- | |
det | |
Notes | |
----- | |
.. versionadded:: 1.8.0 | |
Broadcasting rules apply, see the `numpy.linalg` documentation for | |
details. | |
.. versionadded:: 1.6.0 | |
The determinant is computed via LU factorization using the LAPACK | |
routine ``z/dgetrf``. | |
Examples | |
-------- | |
The determinant of a 2-D array ``[[a, b], [c, d]]`` is ``ad - bc``: | |
>>> a = np.array([[1, 2], [3, 4]]) | |
>>> (sign, logdet) = np.linalg.slogdet(a) | |
>>> (sign, logdet) | |
(-1, 0.69314718055994529) # may vary | |
>>> sign * np.exp(logdet) | |
-2.0 | |
Computing log-determinants for a stack of matrices: | |
>>> a = np.array([ [[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]] ]) | |
>>> a.shape | |
(3, 2, 2) | |
>>> sign, logdet = np.linalg.slogdet(a) | |
>>> (sign, logdet) | |
(array([-1., -1., -1.]), array([ 0.69314718, 1.09861229, 2.07944154])) | |
>>> sign * np.exp(logdet) | |
array([-2., -3., -8.]) | |
This routine succeeds where ordinary `det` does not: | |
>>> np.linalg.det(np.eye(500) * 0.1) | |
0.0 | |
>>> np.linalg.slogdet(np.eye(500) * 0.1) | |
(1, -1151.2925464970228) | |
""" | |
a = asarray(a) | |
_assert_stacked_2d(a) | |
_assert_stacked_square(a) | |
t, result_t = _commonType(a) | |
real_t = _realType(result_t) | |
signature = 'D->Dd' if isComplexType(t) else 'd->dd' | |
sign, logdet = _umath_linalg.slogdet(a, signature=signature) | |
sign = sign.astype(result_t, copy=False) | |
logdet = logdet.astype(real_t, copy=False) | |
return sign, logdet | |
def det(a): | |
""" | |
Compute the determinant of an array. | |
Parameters | |
---------- | |
a : (..., M, M) array_like | |
Input array to compute determinants for. | |
Returns | |
------- | |
det : (...) array_like | |
Determinant of `a`. | |
See Also | |
-------- | |
slogdet : Another way to represent the determinant, more suitable | |
for large matrices where underflow/overflow may occur. | |
scipy.linalg.det : Similar function in SciPy. | |
Notes | |
----- | |
.. versionadded:: 1.8.0 | |
Broadcasting rules apply, see the `numpy.linalg` documentation for | |
details. | |
The determinant is computed via LU factorization using the LAPACK | |
routine ``z/dgetrf``. | |
Examples | |
-------- | |
The determinant of a 2-D array [[a, b], [c, d]] is ad - bc: | |
>>> a = np.array([[1, 2], [3, 4]]) | |
>>> np.linalg.det(a) | |
-2.0 # may vary | |
Computing determinants for a stack of matrices: | |
>>> a = np.array([ [[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]] ]) | |
>>> a.shape | |
(3, 2, 2) | |
>>> np.linalg.det(a) | |
array([-2., -3., -8.]) | |
""" | |
a = asarray(a) | |
_assert_stacked_2d(a) | |
_assert_stacked_square(a) | |
t, result_t = _commonType(a) | |
signature = 'D->D' if isComplexType(t) else 'd->d' | |
r = _umath_linalg.det(a, signature=signature) | |
r = r.astype(result_t, copy=False) | |
return r | |
# Linear Least Squares | |
def _lstsq_dispatcher(a, b, rcond=None): | |
return (a, b) | |
def lstsq(a, b, rcond="warn"): | |
r""" | |
Return the least-squares solution to a linear matrix equation. | |
Computes the vector `x` that approximatively solves the equation | |
``a @ x = b``. The equation may be under-, well-, or over-determined | |
(i.e., the number of linearly independent rows of `a` can be less than, | |
equal to, or greater than its number of linearly independent columns). | |
If `a` is square and of full rank, then `x` (but for round-off error) | |
is the "exact" solution of the equation. Else, `x` minimizes the | |
Euclidean 2-norm :math:`||b - ax||`. If there are multiple minimizing | |
solutions, the one with the smallest 2-norm :math:`||x||` is returned. | |
Parameters | |
---------- | |
a : (M, N) array_like | |
"Coefficient" matrix. | |
b : {(M,), (M, K)} array_like | |
Ordinate or "dependent variable" values. If `b` is two-dimensional, | |
the least-squares solution is calculated for each of the `K` columns | |
of `b`. | |
rcond : float, optional | |
Cut-off ratio for small singular values of `a`. | |
For the purposes of rank determination, singular values are treated | |
as zero if they are smaller than `rcond` times the largest singular | |
value of `a`. | |
.. versionchanged:: 1.14.0 | |
If not set, a FutureWarning is given. The previous default | |
of ``-1`` will use the machine precision as `rcond` parameter, | |
the new default will use the machine precision times `max(M, N)`. | |
To silence the warning and use the new default, use ``rcond=None``, | |
to keep using the old behavior, use ``rcond=-1``. | |
Returns | |
------- | |
x : {(N,), (N, K)} ndarray | |
Least-squares solution. If `b` is two-dimensional, | |
the solutions are in the `K` columns of `x`. | |
residuals : {(1,), (K,), (0,)} ndarray | |
Sums of squared residuals: Squared Euclidean 2-norm for each column in | |
``b - a @ x``. | |
If the rank of `a` is < N or M <= N, this is an empty array. | |
If `b` is 1-dimensional, this is a (1,) shape array. | |
Otherwise the shape is (K,). | |
rank : int | |
Rank of matrix `a`. | |
s : (min(M, N),) ndarray | |
Singular values of `a`. | |
Raises | |
------ | |
LinAlgError | |
If computation does not converge. | |
See Also | |
-------- | |
scipy.linalg.lstsq : Similar function in SciPy. | |
Notes | |
----- | |
If `b` is a matrix, then all array results are returned as matrices. | |
Examples | |
-------- | |
Fit a line, ``y = mx + c``, through some noisy data-points: | |
>>> x = np.array([0, 1, 2, 3]) | |
>>> y = np.array([-1, 0.2, 0.9, 2.1]) | |
By examining the coefficients, we see that the line should have a | |
gradient of roughly 1 and cut the y-axis at, more or less, -1. | |
We can rewrite the line equation as ``y = Ap``, where ``A = [[x 1]]`` | |
and ``p = [[m], [c]]``. Now use `lstsq` to solve for `p`: | |
>>> A = np.vstack([x, np.ones(len(x))]).T | |
>>> A | |
array([[ 0., 1.], | |
[ 1., 1.], | |
[ 2., 1.], | |
[ 3., 1.]]) | |
>>> m, c = np.linalg.lstsq(A, y, rcond=None)[0] | |
>>> m, c | |
(1.0 -0.95) # may vary | |
Plot the data along with the fitted line: | |
>>> import matplotlib.pyplot as plt | |
>>> _ = plt.plot(x, y, 'o', label='Original data', markersize=10) | |
>>> _ = plt.plot(x, m*x + c, 'r', label='Fitted line') | |
>>> _ = plt.legend() | |
>>> plt.show() | |
""" | |
a, _ = _makearray(a) | |
b, wrap = _makearray(b) | |
is_1d = b.ndim == 1 | |
if is_1d: | |
b = b[:, newaxis] | |
_assert_2d(a, b) | |
m, n = a.shape[-2:] | |
m2, n_rhs = b.shape[-2:] | |
if m != m2: | |
raise LinAlgError('Incompatible dimensions') | |
t, result_t = _commonType(a, b) | |
# FIXME: real_t is unused | |
real_t = _linalgRealType(t) | |
result_real_t = _realType(result_t) | |
# Determine default rcond value | |
if rcond == "warn": | |
# 2017-08-19, 1.14.0 | |
warnings.warn("`rcond` parameter will change to the default of " | |
"machine precision times ``max(M, N)`` where M and N " | |
"are the input matrix dimensions.\n" | |
"To use the future default and silence this warning " | |
"we advise to pass `rcond=None`, to keep using the old, " | |
"explicitly pass `rcond=-1`.", | |
FutureWarning, stacklevel=3) | |
rcond = -1 | |
if rcond is None: | |
rcond = finfo(t).eps * max(n, m) | |
if m <= n: | |
gufunc = _umath_linalg.lstsq_m | |
else: | |
gufunc = _umath_linalg.lstsq_n | |
signature = 'DDd->Ddid' if isComplexType(t) else 'ddd->ddid' | |
extobj = get_linalg_error_extobj(_raise_linalgerror_lstsq) | |
if n_rhs == 0: | |
# lapack can't handle n_rhs = 0 - so allocate the array one larger in that axis | |
b = zeros(b.shape[:-2] + (m, n_rhs + 1), dtype=b.dtype) | |
x, resids, rank, s = gufunc(a, b, rcond, signature=signature, extobj=extobj) | |
if m == 0: | |
x[...] = 0 | |
if n_rhs == 0: | |
# remove the item we added | |
x = x[..., :n_rhs] | |
resids = resids[..., :n_rhs] | |
# remove the axis we added | |
if is_1d: | |
x = x.squeeze(axis=-1) | |
# we probably should squeeze resids too, but we can't | |
# without breaking compatibility. | |
# as documented | |
if rank != n or m <= n: | |
resids = array([], result_real_t) | |
# coerce output arrays | |
s = s.astype(result_real_t, copy=False) | |
resids = resids.astype(result_real_t, copy=False) | |
x = x.astype(result_t, copy=True) # Copying lets the memory in r_parts be freed | |
return wrap(x), wrap(resids), rank, s | |
def _multi_svd_norm(x, row_axis, col_axis, op): | |
"""Compute a function of the singular values of the 2-D matrices in `x`. | |
This is a private utility function used by `numpy.linalg.norm()`. | |
Parameters | |
---------- | |
x : ndarray | |
row_axis, col_axis : int | |
The axes of `x` that hold the 2-D matrices. | |
op : callable | |
This should be either numpy.amin or `numpy.amax` or `numpy.sum`. | |
Returns | |
------- | |
result : float or ndarray | |
If `x` is 2-D, the return values is a float. | |
Otherwise, it is an array with ``x.ndim - 2`` dimensions. | |
The return values are either the minimum or maximum or sum of the | |
singular values of the matrices, depending on whether `op` | |
is `numpy.amin` or `numpy.amax` or `numpy.sum`. | |
""" | |
y = moveaxis(x, (row_axis, col_axis), (-2, -1)) | |
result = op(svd(y, compute_uv=False), axis=-1) | |
return result | |
def _norm_dispatcher(x, ord=None, axis=None, keepdims=None): | |
return (x,) | |
def norm(x, ord=None, axis=None, keepdims=False): | |
""" | |
Matrix or vector norm. | |
This function is able to return one of eight different matrix norms, | |
or one of an infinite number of vector norms (described below), depending | |
on the value of the ``ord`` parameter. | |
Parameters | |
---------- | |
x : array_like | |
Input array. If `axis` is None, `x` must be 1-D or 2-D, unless `ord` | |
is None. If both `axis` and `ord` are None, the 2-norm of | |
``x.ravel`` will be returned. | |
ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional | |
Order of the norm (see table under ``Notes``). inf means numpy's | |
`inf` object. The default is None. | |
axis : {None, int, 2-tuple of ints}, optional. | |
If `axis` is an integer, it specifies the axis of `x` along which to | |
compute the vector norms. If `axis` is a 2-tuple, it specifies the | |
axes that hold 2-D matrices, and the matrix norms of these matrices | |
are computed. If `axis` is None then either a vector norm (when `x` | |
is 1-D) or a matrix norm (when `x` is 2-D) is returned. The default | |
is None. | |
.. versionadded:: 1.8.0 | |
keepdims : bool, optional | |
If this is set to True, the axes which are normed over are left in the | |
result as dimensions with size one. With this option the result will | |
broadcast correctly against the original `x`. | |
.. versionadded:: 1.10.0 | |
Returns | |
------- | |
n : float or ndarray | |
Norm of the matrix or vector(s). | |
See Also | |
-------- | |
scipy.linalg.norm : Similar function in SciPy. | |
Notes | |
----- | |
For values of ``ord < 1``, the result is, strictly speaking, not a | |
mathematical 'norm', but it may still be useful for various numerical | |
purposes. | |
The following norms can be calculated: | |
===== ============================ ========================== | |
ord norm for matrices norm for vectors | |
===== ============================ ========================== | |
None Frobenius norm 2-norm | |
'fro' Frobenius norm -- | |
'nuc' nuclear norm -- | |
inf max(sum(abs(x), axis=1)) max(abs(x)) | |
-inf min(sum(abs(x), axis=1)) min(abs(x)) | |
0 -- sum(x != 0) | |
1 max(sum(abs(x), axis=0)) as below | |
-1 min(sum(abs(x), axis=0)) as below | |
2 2-norm (largest sing. value) as below | |
-2 smallest singular value as below | |
other -- sum(abs(x)**ord)**(1./ord) | |
===== ============================ ========================== | |
The Frobenius norm is given by [1]_: | |
:math:`||A||_F = [\\sum_{i,j} abs(a_{i,j})^2]^{1/2}` | |
The nuclear norm is the sum of the singular values. | |
Both the Frobenius and nuclear norm orders are only defined for | |
matrices and raise a ValueError when ``x.ndim != 2``. | |
References | |
---------- | |
.. [1] G. H. Golub and C. F. Van Loan, *Matrix Computations*, | |
Baltimore, MD, Johns Hopkins University Press, 1985, pg. 15 | |
Examples | |
-------- | |
>>> from numpy import linalg as LA | |
>>> a = np.arange(9) - 4 | |
>>> a | |
array([-4, -3, -2, ..., 2, 3, 4]) | |
>>> b = a.reshape((3, 3)) | |
>>> b | |
array([[-4, -3, -2], | |
[-1, 0, 1], | |
[ 2, 3, 4]]) | |
>>> LA.norm(a) | |
7.745966692414834 | |
>>> LA.norm(b) | |
7.745966692414834 | |
>>> LA.norm(b, 'fro') | |
7.745966692414834 | |
>>> LA.norm(a, np.inf) | |
4.0 | |
>>> LA.norm(b, np.inf) | |
9.0 | |
>>> LA.norm(a, -np.inf) | |
0.0 | |
>>> LA.norm(b, -np.inf) | |
2.0 | |
>>> LA.norm(a, 1) | |
20.0 | |
>>> LA.norm(b, 1) | |
7.0 | |
>>> LA.norm(a, -1) | |
-4.6566128774142013e-010 | |
>>> LA.norm(b, -1) | |
6.0 | |
>>> LA.norm(a, 2) | |
7.745966692414834 | |
>>> LA.norm(b, 2) | |
7.3484692283495345 | |
>>> LA.norm(a, -2) | |
0.0 | |
>>> LA.norm(b, -2) | |
1.8570331885190563e-016 # may vary | |
>>> LA.norm(a, 3) | |
5.8480354764257312 # may vary | |
>>> LA.norm(a, -3) | |
0.0 | |
Using the `axis` argument to compute vector norms: | |
>>> c = np.array([[ 1, 2, 3], | |
... [-1, 1, 4]]) | |
>>> LA.norm(c, axis=0) | |
array([ 1.41421356, 2.23606798, 5. ]) | |
>>> LA.norm(c, axis=1) | |
array([ 3.74165739, 4.24264069]) | |
>>> LA.norm(c, ord=1, axis=1) | |
array([ 6., 6.]) | |
Using the `axis` argument to compute matrix norms: | |
>>> m = np.arange(8).reshape(2,2,2) | |
>>> LA.norm(m, axis=(1,2)) | |
array([ 3.74165739, 11.22497216]) | |
>>> LA.norm(m[0, :, :]), LA.norm(m[1, :, :]) | |
(3.7416573867739413, 11.224972160321824) | |
""" | |
x = asarray(x) | |
if not issubclass(x.dtype.type, (inexact, object_)): | |
x = x.astype(float) | |
# Immediately handle some default, simple, fast, and common cases. | |
if axis is None: | |
ndim = x.ndim | |
if ((ord is None) or | |
(ord in ('f', 'fro') and ndim == 2) or | |
(ord == 2 and ndim == 1)): | |
x = x.ravel(order='K') | |
if isComplexType(x.dtype.type): | |
sqnorm = dot(x.real, x.real) + dot(x.imag, x.imag) | |
else: | |
sqnorm = dot(x, x) | |
ret = sqrt(sqnorm) | |
if keepdims: | |
ret = ret.reshape(ndim*[1]) | |
return ret | |
# Normalize the `axis` argument to a tuple. | |
nd = x.ndim | |
if axis is None: | |
axis = tuple(range(nd)) | |
elif not isinstance(axis, tuple): | |
try: | |
axis = int(axis) | |
except Exception as e: | |
raise TypeError("'axis' must be None, an integer or a tuple of integers") from e | |
axis = (axis,) | |
if len(axis) == 1: | |
if ord == Inf: | |
return abs(x).max(axis=axis, keepdims=keepdims) | |
elif ord == -Inf: | |
return abs(x).min(axis=axis, keepdims=keepdims) | |
elif ord == 0: | |
# Zero norm | |
return (x != 0).astype(x.real.dtype).sum(axis=axis, keepdims=keepdims) | |
elif ord == 1: | |
# special case for speedup | |
return add.reduce(abs(x), axis=axis, keepdims=keepdims) | |
elif ord is None or ord == 2: | |
# special case for speedup | |
s = (x.conj() * x).real | |
return sqrt(add.reduce(s, axis=axis, keepdims=keepdims)) | |
# None of the str-type keywords for ord ('fro', 'nuc') | |
# are valid for vectors | |
elif isinstance(ord, str): | |
raise ValueError(f"Invalid norm order '{ord}' for vectors") | |
else: | |
absx = abs(x) | |
absx **= ord | |
ret = add.reduce(absx, axis=axis, keepdims=keepdims) | |
ret **= (1 / ord) | |
return ret | |
elif len(axis) == 2: | |
row_axis, col_axis = axis | |
row_axis = normalize_axis_index(row_axis, nd) | |
col_axis = normalize_axis_index(col_axis, nd) | |
if row_axis == col_axis: | |
raise ValueError('Duplicate axes given.') | |
if ord == 2: | |
ret = _multi_svd_norm(x, row_axis, col_axis, amax) | |
elif ord == -2: | |
ret = _multi_svd_norm(x, row_axis, col_axis, amin) | |
elif ord == 1: | |
if col_axis > row_axis: | |
col_axis -= 1 | |
ret = add.reduce(abs(x), axis=row_axis).max(axis=col_axis) | |
elif ord == Inf: | |
if row_axis > col_axis: | |
row_axis -= 1 | |
ret = add.reduce(abs(x), axis=col_axis).max(axis=row_axis) | |
elif ord == -1: | |
if col_axis > row_axis: | |
col_axis -= 1 | |
ret = add.reduce(abs(x), axis=row_axis).min(axis=col_axis) | |
elif ord == -Inf: | |
if row_axis > col_axis: | |
row_axis -= 1 | |
ret = add.reduce(abs(x), axis=col_axis).min(axis=row_axis) | |
elif ord in [None, 'fro', 'f']: | |
ret = sqrt(add.reduce((x.conj() * x).real, axis=axis)) | |
elif ord == 'nuc': | |
ret = _multi_svd_norm(x, row_axis, col_axis, sum) | |
else: | |
raise ValueError("Invalid norm order for matrices.") | |
if keepdims: | |
ret_shape = list(x.shape) | |
ret_shape[axis[0]] = 1 | |
ret_shape[axis[1]] = 1 | |
ret = ret.reshape(ret_shape) | |
return ret | |
else: | |
raise ValueError("Improper number of dimensions to norm.") | |
# multi_dot | |
def _multidot_dispatcher(arrays, *, out=None): | |
yield from arrays | |
yield out | |
def multi_dot(arrays, *, out=None): | |
""" | |
Compute the dot product of two or more arrays in a single function call, | |
while automatically selecting the fastest evaluation order. | |
`multi_dot` chains `numpy.dot` and uses optimal parenthesization | |
of the matrices [1]_ [2]_. Depending on the shapes of the matrices, | |
this can speed up the multiplication a lot. | |
If the first argument is 1-D it is treated as a row vector. | |
If the last argument is 1-D it is treated as a column vector. | |
The other arguments must be 2-D. | |
Think of `multi_dot` as:: | |
def multi_dot(arrays): return functools.reduce(np.dot, arrays) | |
Parameters | |
---------- | |
arrays : sequence of array_like | |
If the first argument is 1-D it is treated as row vector. | |
If the last argument is 1-D it is treated as column vector. | |
The other arguments must be 2-D. | |
out : ndarray, optional | |
Output argument. This must have the exact kind that would be returned | |
if it was not used. In particular, it must have the right type, must be | |
C-contiguous, and its dtype must be the dtype that would be returned | |
for `dot(a, b)`. This is a performance feature. Therefore, if these | |
conditions are not met, an exception is raised, instead of attempting | |
to be flexible. | |
.. versionadded:: 1.19.0 | |
Returns | |
------- | |
output : ndarray | |
Returns the dot product of the supplied arrays. | |
See Also | |
-------- | |
numpy.dot : dot multiplication with two arguments. | |
References | |
---------- | |
.. [1] Cormen, "Introduction to Algorithms", Chapter 15.2, p. 370-378 | |
.. [2] https://en.wikipedia.org/wiki/Matrix_chain_multiplication | |
Examples | |
-------- | |
`multi_dot` allows you to write:: | |
>>> from numpy.linalg import multi_dot | |
>>> # Prepare some data | |
>>> A = np.random.random((10000, 100)) | |
>>> B = np.random.random((100, 1000)) | |
>>> C = np.random.random((1000, 5)) | |
>>> D = np.random.random((5, 333)) | |
>>> # the actual dot multiplication | |
>>> _ = multi_dot([A, B, C, D]) | |
instead of:: | |
>>> _ = np.dot(np.dot(np.dot(A, B), C), D) | |
>>> # or | |
>>> _ = A.dot(B).dot(C).dot(D) | |
Notes | |
----- | |
The cost for a matrix multiplication can be calculated with the | |
following function:: | |
def cost(A, B): | |
return A.shape[0] * A.shape[1] * B.shape[1] | |
Assume we have three matrices | |
:math:`A_{10x100}, B_{100x5}, C_{5x50}`. | |
The costs for the two different parenthesizations are as follows:: | |
cost((AB)C) = 10*100*5 + 10*5*50 = 5000 + 2500 = 7500 | |
cost(A(BC)) = 10*100*50 + 100*5*50 = 50000 + 25000 = 75000 | |
""" | |
n = len(arrays) | |
# optimization only makes sense for len(arrays) > 2 | |
if n < 2: | |
raise ValueError("Expecting at least two arrays.") | |
elif n == 2: | |
return dot(arrays[0], arrays[1], out=out) | |
arrays = [asanyarray(a) for a in arrays] | |
# save original ndim to reshape the result array into the proper form later | |
ndim_first, ndim_last = arrays[0].ndim, arrays[-1].ndim | |
# Explicitly convert vectors to 2D arrays to keep the logic of the internal | |
# _multi_dot_* functions as simple as possible. | |
if arrays[0].ndim == 1: | |
arrays[0] = atleast_2d(arrays[0]) | |
if arrays[-1].ndim == 1: | |
arrays[-1] = atleast_2d(arrays[-1]).T | |
_assert_2d(*arrays) | |
# _multi_dot_three is much faster than _multi_dot_matrix_chain_order | |
if n == 3: | |
result = _multi_dot_three(arrays[0], arrays[1], arrays[2], out=out) | |
else: | |
order = _multi_dot_matrix_chain_order(arrays) | |
result = _multi_dot(arrays, order, 0, n - 1, out=out) | |
# return proper shape | |
if ndim_first == 1 and ndim_last == 1: | |
return result[0, 0] # scalar | |
elif ndim_first == 1 or ndim_last == 1: | |
return result.ravel() # 1-D | |
else: | |
return result | |
def _multi_dot_three(A, B, C, out=None): | |
""" | |
Find the best order for three arrays and do the multiplication. | |
For three arguments `_multi_dot_three` is approximately 15 times faster | |
than `_multi_dot_matrix_chain_order` | |
""" | |
a0, a1b0 = A.shape | |
b1c0, c1 = C.shape | |
# cost1 = cost((AB)C) = a0*a1b0*b1c0 + a0*b1c0*c1 | |
cost1 = a0 * b1c0 * (a1b0 + c1) | |
# cost2 = cost(A(BC)) = a1b0*b1c0*c1 + a0*a1b0*c1 | |
cost2 = a1b0 * c1 * (a0 + b1c0) | |
if cost1 < cost2: | |
return dot(dot(A, B), C, out=out) | |
else: | |
return dot(A, dot(B, C), out=out) | |
def _multi_dot_matrix_chain_order(arrays, return_costs=False): | |
""" | |
Return a np.array that encodes the optimal order of mutiplications. | |
The optimal order array is then used by `_multi_dot()` to do the | |
multiplication. | |
Also return the cost matrix if `return_costs` is `True` | |
The implementation CLOSELY follows Cormen, "Introduction to Algorithms", | |
Chapter 15.2, p. 370-378. Note that Cormen uses 1-based indices. | |
cost[i, j] = min([ | |
cost[prefix] + cost[suffix] + cost_mult(prefix, suffix) | |
for k in range(i, j)]) | |
""" | |
n = len(arrays) | |
# p stores the dimensions of the matrices | |
# Example for p: A_{10x100}, B_{100x5}, C_{5x50} --> p = [10, 100, 5, 50] | |
p = [a.shape[0] for a in arrays] + [arrays[-1].shape[1]] | |
# m is a matrix of costs of the subproblems | |
# m[i,j]: min number of scalar multiplications needed to compute A_{i..j} | |
m = zeros((n, n), dtype=double) | |
# s is the actual ordering | |
# s[i, j] is the value of k at which we split the product A_i..A_j | |
s = empty((n, n), dtype=intp) | |
for l in range(1, n): | |
for i in range(n - l): | |
j = i + l | |
m[i, j] = Inf | |
for k in range(i, j): | |
q = m[i, k] + m[k+1, j] + p[i]*p[k+1]*p[j+1] | |
if q < m[i, j]: | |
m[i, j] = q | |
s[i, j] = k # Note that Cormen uses 1-based index | |
return (s, m) if return_costs else s | |
def _multi_dot(arrays, order, i, j, out=None): | |
"""Actually do the multiplication with the given order.""" | |
if i == j: | |
# the initial call with non-None out should never get here | |
assert out is None | |
return arrays[i] | |
else: | |
return dot(_multi_dot(arrays, order, i, order[i, j]), | |
_multi_dot(arrays, order, order[i, j] + 1, j), | |
out=out) | |