Skip to content

Commit 8019213

Browse files
committed
Add cholesky_decomposition
1 parent 3422ebc commit 8019213

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

maths/cholesky_decomposition.py

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import numpy as np
2+
3+
4+
def cholesky_decomposition(a: np.ndarray) -> np.ndarray:
5+
"""Return a Cholesky decomposition of the matrix A.
6+
7+
The Cholesky decomposition decomposes the square, positive definite matrix A
8+
into a lower triangular matrix L such that A = L L^T.
9+
10+
https://en.wikipedia.org/wiki/Cholesky_decomposition
11+
12+
Arguments:
13+
A -- a numpy.ndarray of shape (n, n)
14+
15+
>>> A = np.array([[4, 12, -16], [12, 37, -43], [-16, -43, 98]], dtype=float)
16+
>>> L = cholesky_decomposition(A)
17+
>>> np.allclose(L, np.array([[2, 0, 0], [6, 1, 0], [-8, 5, 3]]))
18+
True
19+
20+
>>> # check that the decomposition is correct
21+
>>> np.allclose(L @ L.T, A)
22+
True
23+
24+
>>> # check that L is lower triangular
25+
>>> np.allclose(np.tril(L), L)
26+
True
27+
28+
The Cholesky decomposition can be used to solve the system of equations A x = y.
29+
30+
>>> x_true = np.array([1, 2, 3], dtype=float)
31+
>>> y = A @ x_true
32+
>>> x = solve_cholesky(L, y)
33+
>>> np.allclose(x, x_true)
34+
True
35+
36+
It can also be used to solve multiple equations A X = Y simultaneously.
37+
38+
>>> X_true = np.random.rand(3, 3)
39+
>>> Y = A @ X_true
40+
>>> X = solve_cholesky(L, Y)
41+
>>> np.allclose(X, X_true)
42+
True
43+
"""
44+
assert a.shape[0] == a.shape[1]
45+
n = a.shape[0]
46+
lo = np.tril(a)
47+
48+
for i in range(n):
49+
for j in range(i):
50+
lo[i, j] = (lo[i, j] - np.sum(lo[i, :j] * lo[j, :j])) / lo[j, j]
51+
52+
s = lo[i, i] - np.sum(lo[i, :i] * lo[i, :i])
53+
54+
if s <= 0:
55+
raise ValueError("Matrix A is not positive definite")
56+
57+
lo[i, i] = np.sqrt(s)
58+
59+
return lo
60+
61+
62+
def solve_cholesky(lo: np.ndarray, y: np.ndarray) -> np.ndarray:
63+
"""Given a Cholesky decomposition L L^T = A of a matrix A, solve the
64+
system of equations A X = Y where B is either a matrix or a vector.
65+
66+
>>> L = np.array([[2, 0], [3, 4]], dtype=float)
67+
>>> Y = np.array([[22, 54], [81, 193]], dtype=float)
68+
>>> X = solve_cholesky(L, Y)
69+
>>> np.allclose(X, np.array([[1, 3], [3, 7]], dtype=float))
70+
True
71+
"""
72+
73+
# Handle vector case by reshaping to matrix and then flattening again
74+
if len(y.shape) == 1:
75+
return solve_cholesky(lo, y.reshape(-1, 1)).ravel()
76+
77+
n, m = y.shape
78+
79+
# Backsubstitute L X = B
80+
x = y.copy()
81+
for i in range(n):
82+
for j in range(i):
83+
x[i, :] -= lo[i, j] * x[j, :]
84+
85+
for k in range(m):
86+
x[i, k] /= lo[i, i]
87+
88+
# Backsubstitute L^T
89+
for i in reversed(range(n)):
90+
for j in range(i + 1, n):
91+
x[i, :] -= lo[j, i] * x[j, :]
92+
93+
for k in range(m):
94+
x[i, k] /= lo[i, i]
95+
96+
return x
97+
98+
99+
if __name__ == "__main__":
100+
import doctest
101+
102+
doctest.testmod()

0 commit comments

Comments
 (0)