mathmat.factor

MathMat Matrix Factorization Toolbox.

  1"""MathMat Matrix Factorization Toolbox."""
  2
  3from .mathmat import MatrixTypeException, Matrix, DiagonalMatrix, Identity
  4import numpy as np
  5from scipy.linalg import lu as scipy_lu
  6from scipy.sparse.linalg import svds as sparse_svd, \
  7    splu as sparse_lu
  8from scipy.sparse import csc_array
  9
 10
 11def cholesky(M: Matrix):
 12    """Compute the Cholesky factorization $M = L L^H$."""
 13    if not M.is_hermitian():
 14        raise MatrixTypeException("Cholesky factorization", "Hermitian")
 15    if not M.is_posdef():
 16        raise MatrixTypeException(
 17            "Cholesky factorization", "positive-definite")
 18    L = np.linalg.cholesky(M.entries)
 19    L = Matrix(csc_array(L))
 20    L.set("dense", Matrix(L))
 21    L.set("triangular_L", True)
 22    M.set("cholesky", L)
 23    M.set("plu", (Identity(M.nc), L, L.transpose()))
 24    return L
 25
 26
 27def diagonalization(M: Matrix):
 28    """Compute the eigenvalue decomposition $M = P D P^{-1}$. Densifies."""
 29    if not M.is_diagonalizable():
 30        raise MatrixTypeException("diagonalize", "diagonalizable")
 31    if M.is_sparse():
 32        M = M.to_dense()
 33    P = Matrix(M.eigenvectors())
 34    D = DiagonalMatrix(M.eigenvalues())
 35
 36    M.set("diagonalization", (D, P))
 37    return D, P
 38
 39
 40def lu(M: Matrix, densify=False):
 41    """Compute the pivoted LU factorization $M = P L U$.
 42    If the matrix is sparse, returns an object that can be used to solve
 43    linear systems."""
 44    if not M.is_square():
 45        raise MatrixTypeException("LU factorization", "square")
 46    if M.is_sparse():
 47        if densify:
 48            M = M.to_dense()
 49        else:
 50            fct = sparse_lu(M.entries)
 51            M.set("lu_solver", fct.solve)
 52            return fct
 53    P, L, U = scipy_lu(M.entries)
 54    P = Matrix(P)
 55    L = Matrix(csc_array(L))
 56    L.set("dense", Matrix(L))
 57    L.set("triangular_L", True)
 58    U = Matrix(csc_array(U))
 59    U.set("dense", Matrix(U))
 60    U.set("triangular_R", True)
 61
 62    P.set("unitary", True)
 63    P.set("inverse", lambda: U.conjugate().transpose())
 64    M.set("plu", (P, L, U))
 65    return P, L, U
 66
 67
 68def qr(M: Matrix):
 69    """Compute the QR factorization $M = QR$."""
 70    Q, R = np.linalg.qr(M.entries, mode="reduced")
 71    Q = Matrix(Q)
 72    R = Matrix(R)
 73    R.set("triangular_U", True)
 74    # Store known properties of Q and R
 75    if M.is_complex():
 76        Q._computed.update({
 77            "unitary": True,
 78            "inverse": lambda: Q.conjugate().transpose()
 79        })
 80    else:
 81        Q._computed.update({
 82            "orthogonal": True,
 83            "inverse": lambda: Q.transpose()
 84        })
 85
 86    M.set("qr", (Q, R))
 87    return Q, R
 88
 89
 90def svd(M: Matrix, tolerance=1e-10, sp_k="max"):
 91    """Compute the SVD $M = U \\Sigma V^H$.
 92
 93    Note that for sparse matrices, there is a limit set by sparse.svds
 94    as to how many singular values can be found! `max` is set to half
 95    of the minimum dimension"""
 96    if M.is_sparse():
 97        # Use sparse SVD
 98        U, sigmas, V = sparse_svd(
 99            M.entries,
100            k=max(2, int(0.1*min(M.nr, M.nc)) if sp_k == "max" else sp_k),
101            solver="lobpcg")
102        order = np.flip(np.argsort(sigmas))
103        U = U[:, order]
104        sigmas = sigmas[order]
105        V = V[order, :]
106    else:
107        # Use dense SVD
108        U, sigmas, V = np.linalg.svd(M.entries)
109    U = Matrix(U)
110    if M.is_square():
111        # Use a sparse diagonal
112        S = DiagonalMatrix(sigmas)
113    else:
114        # Place the sigmas into the right shape
115        vals = np.zeros(shape=M.size)
116        vals[:len(sigmas), :len(sigmas)] = np.diag(sigmas)
117        S = Matrix(vals)
118    V = Matrix(V)
119    # Store the known properties of U, S, and V.
120    if M.is_complex():
121        U._computed.update({
122            "unitary": True,
123            "inverse": lambda: U.conjugate().transpose()
124        })
125        V._computed.update({
126            "unitary": True,
127            "inverse": lambda: V.conjugate().transpose()
128        })
129    else:
130        U._computed.update({
131            "orthogonal": True,
132            "inverse": lambda: U.transpose()
133        })
134        V._computed.update({
135            "orthogonal": True,
136            "inverse": lambda: V.transpose()
137        })
138
139    M.set("svd", (U, S, V))
140    M.set("sigmas", sigmas)
141    return U, S, V
def cholesky(M: mathmat.mathmat.Matrix):
12def cholesky(M: Matrix):
13    """Compute the Cholesky factorization $M = L L^H$."""
14    if not M.is_hermitian():
15        raise MatrixTypeException("Cholesky factorization", "Hermitian")
16    if not M.is_posdef():
17        raise MatrixTypeException(
18            "Cholesky factorization", "positive-definite")
19    L = np.linalg.cholesky(M.entries)
20    L = Matrix(csc_array(L))
21    L.set("dense", Matrix(L))
22    L.set("triangular_L", True)
23    M.set("cholesky", L)
24    M.set("plu", (Identity(M.nc), L, L.transpose()))
25    return L

Compute the Cholesky factorization $M = L L^H$.

def diagonalization(M: mathmat.mathmat.Matrix):
28def diagonalization(M: Matrix):
29    """Compute the eigenvalue decomposition $M = P D P^{-1}$. Densifies."""
30    if not M.is_diagonalizable():
31        raise MatrixTypeException("diagonalize", "diagonalizable")
32    if M.is_sparse():
33        M = M.to_dense()
34    P = Matrix(M.eigenvectors())
35    D = DiagonalMatrix(M.eigenvalues())
36
37    M.set("diagonalization", (D, P))
38    return D, P

Compute the eigenvalue decomposition $M = P D P^{-1}$. Densifies.

def lu(M: mathmat.mathmat.Matrix, densify=False):
41def lu(M: Matrix, densify=False):
42    """Compute the pivoted LU factorization $M = P L U$.
43    If the matrix is sparse, returns an object that can be used to solve
44    linear systems."""
45    if not M.is_square():
46        raise MatrixTypeException("LU factorization", "square")
47    if M.is_sparse():
48        if densify:
49            M = M.to_dense()
50        else:
51            fct = sparse_lu(M.entries)
52            M.set("lu_solver", fct.solve)
53            return fct
54    P, L, U = scipy_lu(M.entries)
55    P = Matrix(P)
56    L = Matrix(csc_array(L))
57    L.set("dense", Matrix(L))
58    L.set("triangular_L", True)
59    U = Matrix(csc_array(U))
60    U.set("dense", Matrix(U))
61    U.set("triangular_R", True)
62
63    P.set("unitary", True)
64    P.set("inverse", lambda: U.conjugate().transpose())
65    M.set("plu", (P, L, U))
66    return P, L, U

Compute the pivoted LU factorization $M = P L U$. If the matrix is sparse, returns an object that can be used to solve linear systems.

def qr(M: mathmat.mathmat.Matrix):
69def qr(M: Matrix):
70    """Compute the QR factorization $M = QR$."""
71    Q, R = np.linalg.qr(M.entries, mode="reduced")
72    Q = Matrix(Q)
73    R = Matrix(R)
74    R.set("triangular_U", True)
75    # Store known properties of Q and R
76    if M.is_complex():
77        Q._computed.update({
78            "unitary": True,
79            "inverse": lambda: Q.conjugate().transpose()
80        })
81    else:
82        Q._computed.update({
83            "orthogonal": True,
84            "inverse": lambda: Q.transpose()
85        })
86
87    M.set("qr", (Q, R))
88    return Q, R

Compute the QR factorization $M = QR$.

def svd(M: mathmat.mathmat.Matrix, tolerance=1e-10, sp_k='max'):
 91def svd(M: Matrix, tolerance=1e-10, sp_k="max"):
 92    """Compute the SVD $M = U \\Sigma V^H$.
 93
 94    Note that for sparse matrices, there is a limit set by sparse.svds
 95    as to how many singular values can be found! `max` is set to half
 96    of the minimum dimension"""
 97    if M.is_sparse():
 98        # Use sparse SVD
 99        U, sigmas, V = sparse_svd(
100            M.entries,
101            k=max(2, int(0.1*min(M.nr, M.nc)) if sp_k == "max" else sp_k),
102            solver="lobpcg")
103        order = np.flip(np.argsort(sigmas))
104        U = U[:, order]
105        sigmas = sigmas[order]
106        V = V[order, :]
107    else:
108        # Use dense SVD
109        U, sigmas, V = np.linalg.svd(M.entries)
110    U = Matrix(U)
111    if M.is_square():
112        # Use a sparse diagonal
113        S = DiagonalMatrix(sigmas)
114    else:
115        # Place the sigmas into the right shape
116        vals = np.zeros(shape=M.size)
117        vals[:len(sigmas), :len(sigmas)] = np.diag(sigmas)
118        S = Matrix(vals)
119    V = Matrix(V)
120    # Store the known properties of U, S, and V.
121    if M.is_complex():
122        U._computed.update({
123            "unitary": True,
124            "inverse": lambda: U.conjugate().transpose()
125        })
126        V._computed.update({
127            "unitary": True,
128            "inverse": lambda: V.conjugate().transpose()
129        })
130    else:
131        U._computed.update({
132            "orthogonal": True,
133            "inverse": lambda: U.transpose()
134        })
135        V._computed.update({
136            "orthogonal": True,
137            "inverse": lambda: V.transpose()
138        })
139
140    M.set("svd", (U, S, V))
141    M.set("sigmas", sigmas)
142    return U, S, V

Compute the SVD $M = U \Sigma V^H$.

Note that for sparse matrices, there is a limit set by sparse.svds as to how many singular values can be found! max is set to half of the minimum dimension