3×3 Matrix{Int64}:
2 -1 0
-1 2 -1
0 -1 2
Question: if \(\mathbf{A}\) has extra structure — symmetry and positive definiteness — can we exploit it to do less work than LU, and can we avoid pivoting altogether?
Answer: Yes! The algorithm requires roughly half the flops, and no pivoting is ever needed.
Definition: Positive definite matrix
A symmetric matrix \(\mathbf{A}\) is positive definite if \[ x^\top \mathbf{A} x > 0 \qquad \text{for every nonzero } x \in \mathbb{R}^n. \]
We call \(\mathbf{A}\) symmetric positive definite (SPD) if it is both positive definite and symmetric.
Useful equivalent characterizations of positive definiteness:
You can check the leading principal minors to determine if a matrix is SPD.
3×3 Matrix{Int64}:
2 -1 0
-1 2 -1
0 -1 2
Theorem: Cholesky factorization
Any SPD matrix \(\mathbf{A} \in \mathbb{R}^{n\times n}\) may be factored as \[\mathbf{A} = \mathbf{R}^\top \mathbf{R},\] where \(\mathbf{R} \in \mathbb{R}^{n\times n}\) is an upper triangular matrix with positive diagonal elements. This is called the Cholesky factorization.
Just like lufact, we build \(\mathbf{R}\) one row at a time by outer-product deflation:
At every step, computing the next diagonal entry of \(\mathbf{R}\) requires a square root — so the algorithm can only proceed if the quantity under each square root is positive.
In the homework, you’ll dig into why that’s guaranteed when \(\mathbf{A}\) is SPD, and can fail when \(\mathbf{A}\) is merely symmetric.
cholfact Algorithm%%julia
"""
cholfact(A)
Compute the Cholesky factorization of a symmetric positive definite matrix `A`,
returning the upper triangular factor `R` such that `A ≈ R'*R`.
"""
function cholfact(A)
n = size(A,1)
R = zeros(n,n)
Ak = float(copy(A)) # will be overwritten at each step
for k in 1:n
akk = Ak[k,k]
@assert akk > 0 "matrix is not positive definite"
R[k,k] = sqrt(akk)
v = Ak[k,k+1:n] / R[k,k]
R[k,k+1:n] = v
Ak[k+1:n,k+1:n] -= v*v'
end
return UpperTriangular(R)
endcholfact
Let’s walk through this by hand on a small SPD example.
3×3 Matrix{Float64}:
2.0 1.0 -1.0
0.0 0.0 0.0
0.0 0.0 0.0
3×3 UpperTriangular{Float64, Matrix{Float64}}:
2.0 1.0 -1.0
⋅ 3.0 1.0
⋅ ⋅ 1.73205
Compared to lufact:
v*v' replaces the two-sided outer product L[:,k]*U[k,:]'.@assert doubles as a SPD test: if it ever fails, \(\mathbf{A}\) was not SPD.Theorem: Efficiency of Cholesky factorization
Cholesky factorization of an \(n\times n\) SPD matrix takes \(\mathcal{O}(n^3/3)\) flops as \(n \to \infty\) — about half the cost of general LU factorization (\(\mathcal{O}(2n^3/3)\) flops).
%%julia
M = randn(3,3);
cholesky(Symmetric(M*M' + 3I)); lu(M); #throwaway to force compilation
ns = 400:400:4000
t_chol, t_lu = [], []
for n in ns
M = randn(n,n)
A = M*M' + n*I # a well-conditioned SPD test matrix
time_chol = @elapsed for j in 1:12; cholesky(A); end
push!(t_chol, time_chol)
time_lu = @elapsed for j in 1:12; lu(A); end
push!(t_lu, time_lu)
endBoth curves scale like \(n^3\), but cholesky has roughly half the constant of lu. In practice, use the built-in cholesky(A) from LinearAlgebra; just like lu/\.
In the homework, you’ll work through an entry-wise centric derivation of the Cholesky factorization. One can instead see the factorization through a matrix factorization lens:
After the first step of the algorithm, the matrix \(\mathbf{R}\) shares the same first row as \(\tilde{\mathbf{R}}\), which satisfies: \[\mathbf{A} = \begin{bmatrix} \sqrt{A_{11}} & \mathbf{0}^\top \\ \frac{1}{\sqrt{A_{11}}}\mathbf{A}_{1,2:n}^\top & \mathbf{I} \end{bmatrix} \begin{bmatrix} 1 & \mathbf{0}^\top \\ \mathbf{0} & \mathbf{A}_{2:n,2:n} - \frac{1}{A_{11}}\mathbf{A}_{1,2:n}^\top \mathbf{A}_{1,2:n} \end{bmatrix} \begin{bmatrix} \sqrt{A_{11}} & \frac{1}{\sqrt{A_{11}}}\mathbf{A}_{1,2:n} \\ \mathbf{0} & \mathbf{I} \end{bmatrix}\] where \(\tilde{\mathbf{R}} = \begin{bmatrix} \sqrt{A_{11}} & \frac{1}{\sqrt{A_{11}}}\mathbf{A}_{1,2:n} \\ \mathbf{0} & \mathbf{I} \end{bmatrix}.\)
The algorithm progresses, filling rows of \(\mathbf{R}\) and \(\tilde{\mathbf{R}}\) until the middle matrix is \(\mathbf{I}\).
One can show using induction that this process works using the following lemma.
Lemma:
If \(\mathbf{A}\) is SPD and \(\mathbf{B}\) is non-singular, then \(\mathbf{B}^\top \mathbf{A}\mathbf{B}\) is also SPD.
Applying this with \(\tilde{\mathbf{R}}^{-1}\) yields that \(\mathbf{A}_{2:n,2:n} - \frac{1}{A_{11}}\mathbf{A}_{1,2:n}^\top\) is SPD.
Note that the matrix \(\mathbf{A}_{2:n,2:n} - \frac{1}{A_{11}}\mathbf{A}_{1,2:n}^\top\) is called the Schur complement of entry \(A_{11}\) in \(\mathbf{A}\).
Definition: Schur complement
Assume \[\mathbf{A} = \begin{bmatrix} \mathbf{A}_{11} & \mathbf{A}_{12} \\ \mathbf{A}_{21} & \mathbf{A}_{22} \end{bmatrix}\] is given in \(2 \times 2\) block form. Then \[\mathbf{A}_{22} - \mathbf{A}_{21} \mathbf{A}_{11}^{-1} \mathbf{A}_{12}\] is called the Schur complement of \(\mathbf{A}_{11}\) for matrix \(\mathbf{A}\).
If \(\mathbf{A}\) is SPD, then the Schur complement of \(\mathbf{A}_{11}\) is also SPD.
Recall our previous example: unpivoted LU on \(\begin{bmatrix} -\epsilon & 1 \\ 1 & -1 \end{bmatrix}\) lost almost all accuracy as \(\epsilon \to 0\), because it divided by a tiny pivot. Row pivoting (PLU) fixed it completely.
Fact: unconditional stability of Cholesky
Cholesky factorization of an SPD matrix is backward stable, with no pivoting required.
As with the previous example, let’s separate the algorithm’s stability from the problem’s conditioning:
(true, 1.602489743907797e13)
Even though \(\mathbf{H}\) is severely ill-conditioned (its condition number is enormous), Cholesky still runs to completion and returns a tiny residual — the algorithm is stable even when the problem is ill-conditioned.
A large residual is not automatically evidence of an unstable algorithm – it can just as well be an ill-conditioned problem. Here, despite the ill-conditioning, Cholesky handles \(\mathbf{H}\) just fine. It’s solving a system with \(\mathbf{H}\) that becomes sensitive to error as \(\kappa(\mathbf{H})\) grows.
We can solve \(\mathbf{A}x = b\) in three steps, just as with LU:
Once \(\mathbf{R}\) is computed, solving for additional right-hand sides costs only \(\mathcal{O}(n^2)\) each — the same reuse benefit as LU.
3-element Vector{Float64}:
0.21740121365988105
-0.0864022588826147
0.28138031434223865