AI Knowledge Tree

LU, QR, and Cholesky factorizations

See how LU, QR, and Cholesky each solve linear systems by exploiting structure, and why factoring is numerically preferable to inverting.

Inverting a matrix is expensive and numerically fragile. Instead, numerical linear algebra solves Ax=bAx = b by factoring AA into simpler matrices, solving two easy triangular systems, and never forming A−1A^{-1}[1].

Why triangular systems are cheap

A triangular system Lx=bLx = b (lower triangular) is solved by forward substitution in O(n2)O(n^2) time:

x1=b1/l11,x2=(b2−l21x1)/l22,…x_1 = b_1 / l_{11}, \quad x_2 = (b_2 - l_{21}x_1)/l_{22}, \quad \ldots

Upper triangular Ux=bUx = b is solved by backward substitution, the same idea from bottom up. Factoring AA into two triangular matrices reduces the O(n3)O(n^3) work of building A−1A^{-1} to O(n3)O(n^3) work of factoring once, then O(n2)O(n^2) per right-hand side.

LU factorization

For a square AA (under mild conditions), Gaussian elimination produces

A=LU,A = LU,

where LL is unit lower-triangular (1s on the diagonal) and UU is upper triangular. In practice, partial pivoting permutes rows: PA=LUPA = LU.

Algorithm. At step kk, eliminate column kk below the diagonal: for each row i>ki > k, set multiplier mik=aik/akkm_{ik} = a_{ik}/a_{kk}, subtract mikm_{ik} times row kk from row ii. The multipliers mikm_{ik} fill the lower triangle of LL.

Cost: O(n3)O(n^3) to factor, O(n2)O(n^2) to solve for each new bb.

Determinant: det⁡A=±∏iuii\det A = \pm \prod_i u_{ii} (the sign from the permutation).

QR factorization

For any (possibly rectangular) A∈Rm×nA \in \mathbb{R}^{m \times n} with m≥nm \ge n:

A=QR,A = QR,

where Q∈Rm×mQ \in \mathbb{R}^{m \times m} is orthogonal and R∈Rm×nR \in \mathbb{R}^{m \times n} is upper triangular. The economy (thin) form uses Q∈Rm×nQ \in \mathbb{R}^{m \times n} with Q⊤Q=InQ^\top Q = I_n.

Construction via Gram–Schmidt (previous lesson): the jj-th column of RR has rij=⟨vj,qi⟩r_{ij} = \langle v_j, q_i\rangle for i≤ji \le j and 00 above; the process exactly builds QQ and RR simultaneously.

Solving Ax=bAx = b via QR: multiply both sides by Q⊤Q^\top: Rx=Q⊤bRx = Q^\top b, then backward-substitute. For least-squares problems (m>nm > n), QR avoids the formation of A⊤AA^\top A and is numerically more stable than the normal equations.

Cholesky factorization

For a symmetric positive definite matrix AA:

A=LL⊤,A = LL^\top,

where LL is lower triangular with positive diagonal. Cholesky exists if and only if AA is PD (converse: if Cholesky succeeds, every leading minor is positive, confirming PD by Sylvester's criterion)[2].

Cost: ≈n3/3\approx n^3/3, roughly half the cost of LU, exploiting symmetry.

Algorithm. Column by column, from j=1j = 1 to nn:

ljj=ajj−∑k=1j−1ljk2,lij=1ljj(aij−∑k=1j−1likljk)(i>j).l_{jj} = \sqrt{a_{jj} - \sum_{k=1}^{j-1} l_{jk}^2}, \qquad l_{ij} = \frac{1}{l_{jj}}\left(a_{ij} - \sum_{k=1}^{j-1} l_{ik} l_{jk}\right) \quad (i > j).

A negative value under the square root means AA is not PD.

Inverse: A−1=(L−1)⊤L−1A^{-1} = (L^{-1})^\top L^{-1}, but forming the inverse is usually unnecessary: solve Ly=bLy = b (forward substitution) then L⊤x=yL^\top x = y (backward substitution).

Check it on arrays

python · numpy
import numpy as np

A = np.array([[4.0, 3.0, 2.0],
              [3.0, 6.0, 1.0],
              [2.0, 1.0, 5.0]])   # symmetric PD

b = np.array([1.0, 2.0, 3.0])

# Triangular solves (the cheap O(n^2) step every factorization reduces to):
def forward_sub(L, b):       # L lower triangular
    x = np.zeros_like(b)
    for i in range(len(b)):
        x[i] = (b[i] - L[i, :i] @ x[:i]) / L[i, i]
    return x

def back_sub(U, b):          # U upper triangular
    x = np.zeros_like(b)
    for i in reversed(range(len(b))):
        x[i] = (b[i] - U[i, i + 1:] @ x[i + 1:]) / U[i, i]
    return x

# LU without pivoting (A is PD here, so no row swaps are needed):
def lu(A):
    n = A.shape[0]
    L, U = np.eye(n), A.astype(float).copy()
    for k in range(n):
        for i in range(k + 1, n):
            L[i, k] = U[i, k] / U[k, k]
            U[i, k:] -= L[i, k] * U[k, k:]
    return L, U

L, U = lu(A)
print("LU reconstructs A:", np.allclose(L @ U, A))
x_lu = back_sub(U, forward_sub(L, b))      # solve Ly=b, then Ux=y

# QR: A = QR, so Ax=b becomes Rx = Q^T b
Q, R = np.linalg.qr(A)
x_qr = back_sub(R, Q.T @ b)
print("LU == QR solution:", np.allclose(x_lu, x_qr))

# Cholesky: A = L L^T, solve Ly=b then L^T x=y
L_chol = np.linalg.cholesky(A)             # lower triangular
y = forward_sub(L_chol, b)
x_chol = back_sub(L_chol.T, y)
print("LU == Cholesky solution:", np.allclose(x_lu, x_chol))

# Never invert: compare condition number
print("cond(A):", np.linalg.cond(A))       # moderate -> solving is fine

All three paths give the same solution; the Cholesky factorization halves the arithmetic.

Exercises

Exercise 1. Solve the lower-triangular system Lx=bLx = b by hand using forward substitution, with L=(200130412)L = \begin{pmatrix} 2 & 0 & 0 \\ 1 & 3 & 0 \\ 4 & 1 & 2 \end{pmatrix} and b=(4,7,9)b = (4, 7, 9).

Solution

Step 1. Forward substitution solves top to bottom. Row 11: 2x1=42 x_1 = 4, so x1=2x_1 = 2.

Step 2. Row 22: 1⋅x1+3x2=71\cdot x_1 + 3 x_2 = 7, so 3x2=7−2=53 x_2 = 7 - 2 = 5 and x2=5/3x_2 = 5/3.

Step 3. Row 33: 4x1+1⋅x2+2x3=94 x_1 + 1\cdot x_2 + 2 x_3 = 9, so 2x3=9−8−5/3=−5/32 x_3 = 9 - 8 - 5/3 = -5/3, giving x3=−5/6x_3 = -5/6. The solution is x=(2, 5/3, −5/6)x = (2,\ 5/3,\ -5/6).

Exercise 2. Compute the Cholesky factor LL of A=(4225)A = \begin{pmatrix} 4 & 2 \\ 2 & 5 \end{pmatrix} by hand, and use it to confirm AA is positive definite.

Solution

Step 1. Apply the column formula. For j=1j = 1: l11=a11=4=2l_{11} = \sqrt{a_{11}} = \sqrt{4} = 2, and l21=a21/l11=2/2=1l_{21} = a_{21}/l_{11} = 2/2 = 1.

Step 2. For j=2j = 2: l22=a22−l212=5−1=2l_{22} = \sqrt{a_{22} - l_{21}^2} = \sqrt{5 - 1} = 2. So L=(2012)L = \begin{pmatrix} 2 & 0 \\ 1 & 2 \end{pmatrix}.

Step 3. The square roots were both of strictly positive numbers (44 and 44), so Cholesky succeeds and AA is positive definite. Verify LL⊤=(4225)=ALL^\top = \begin{pmatrix} 4 & 2 \\ 2 & 5 \end{pmatrix} = A.

Exercise 3 (code). For a random symmetric positive definite AA, solve Ax=bAx = b three ways (LU via np.linalg.solve, QR, and Cholesky) and assert all three solutions agree. Then confirm that the product of the squared Cholesky diagonal entries equals det⁡A\det A.

Solution
python · numpy
import numpy as np

rng = np.random.default_rng(0)
M = rng.normal(size=(4, 4))
A = M @ M.T + 4 * np.eye(4)        # symmetric positive definite
b = rng.normal(size=4)

x_lu = np.linalg.solve(A, b)       # LU with pivoting under the hood

Q, R = np.linalg.qr(A)
x_qr = np.linalg.solve(R, Q.T @ b) # Rx = Q^T b

L = np.linalg.cholesky(A)          # A = L L^T
y = np.linalg.solve(L, b)
x_chol = np.linalg.solve(L.T, y)

assert np.allclose(x_lu, x_qr)
assert np.allclose(x_lu, x_chol)

# det A = product of l_ii^2, since det A = det L * det L^T = (prod l_ii)^2.
assert np.isclose(np.prod(np.diag(L)**2), np.linalg.det(A))
print("solution:", np.round(x_lu, 6))

The three factorizations land on the same solution, and the squared product of the Cholesky diagonal reproduces det⁡A\det A because det⁡A=(det⁡L)2\det A = (\det L)^2 and LL is triangular.

The next lesson uses QR and the SVD together to handle the case where Ax=bAx = b has no exact solution.


References

  1. [1]Trefethen, L. N., & Bau, D. (1997). Numerical Linear Algebra. SIAM. LU, QR, and Cholesky factorizations and why factoring beats inverting (Lectures 7, 20, 23).↩
  2. [2]Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations (4th ed.). Johns Hopkins University Press. The canonical reference for matrix factorization algorithms and their costs (doi:10.56021/9781421407944).↩