Numerical linear algebra#
The following operations are supported in-place and allocation-free
Matrix inversion in \(n^3\) flops. Recall that inversion by Cholesky costs \((5/3)n^3\) flops and needs to allocate a matrix of same size.
Computation of determinant
Check (strict) positive definiteness
[1]:
import sweepystats as sw
import numpy as np
def random_symmetric_matrix(n, eigmin=float('-inf')):
"""
Simulates a n*n symmetric matrix with minimum eigenvalue set to `eigmin`.
The resulting matrix is stored in column-major format.
"""
A = np.random.rand(n, n)
A = 0.5 * (A + A.T)
# force eigenvalues to be >= eigmin
if eigmin > float('-inf'):
eval, evec = np.linalg.eig(A)
eval[np.where(eval < 0)[0]] = eigmin
A = evec * np.diag(eval) * evec.T
# convert to column major
return np.array(A, order='F')
Matrix inverses#
If A is full rank, A.sweep() converts A.A into -inv(A.A):
[2]:
# simulated data
data = random_symmetric_matrix(100)
# true inverse
Ainv = np.linalg.inv(data)
# sweep
A = sw.SweepMatrix(data)
A.sweep()
# check answer is correct
np.allclose(A.A, -Ainv)
100%|███████████████████████████████████████| 100/100 [00:00<00:00, 13296.26it/s]
[2]:
True
Determinants#
Determinants are computed by A.det(). The original matrix is untouched by default.
[3]:
# simulate data
data = random_symmetric_matrix(100)
# true determinant
Adet = np.linalg.det(data)
# sweep
A = sw.SweepMatrix(data)
det = A.det()
# check answer is correct
np.allclose(det, Adet)
100%|██████████████████████████████████████| 100/100 [00:00<00:00, 151255.10it/s]
100%|██████████████████████████████████████| 100/100 [00:00<00:00, 144931.03it/s]
[3]:
True
Checking of positive definiteness#
This is a PD matrix:
[4]:
A = sw.SweepMatrix(random_symmetric_matrix(100, eigmin=0.00001)) # this is PD
A.isposdef(tol=1e-15, verbose=False)
[4]:
True
Simulate a matrix with negative eigenvalues:
[5]:
A = sw.SweepMatrix(random_symmetric_matrix(100, eigmin=-10)) # should return false
A.isposdef(verbose=False)
[5]:
False
Simulate a matrix with 0 as eigenvalue:
[6]:
A = sw.SweepMatrix(random_symmetric_matrix(100, eigmin=0.0)) # this is PSD, should return false
A.isposdef(verbose=False)
[6]:
False
Matrix rank:#
Less well known is that the sweep operator can also be used to compute the rank of a matrix, by counting how many diagonals are non-zero prior to sweeping.
[25]:
A = sw.SweepMatrix(random_symmetric_matrix(123, eigmin=0.0))
A.rank()
100%|███████████████████████████████████████| 123/123 [00:00<00:00, 57836.25it/s]
100%|███████████████████████████████████████| 123/123 [00:00<00:00, 56351.65it/s]
[25]:
62
Check answer:
[26]:
np.linalg.matrix_rank(A.A)
[26]:
np.int64(62)