AI Knowledge Tree

The conjugate gradient method

Solve symmetric positive-definite systems with conjugate gradient and understand its Krylov structure.

For a large symmetric positive-definite system Ax=bAx = b, direct factorization costs O(n3)O(n^3) and stores a dense factor. Conjugate gradient (CG) solves it iteratively using only matrix-vector products, finishing in at most nn steps in exact arithmetic and far fewer in practice[1]. It is the workhorse for sparse SPD systems and the template for Krylov methods.

CG as minimizing a quadratic

For SPD AA, solving Ax=bAx = b is equivalent to minimizing the quadratic

ϕ(x)=12x⊤Ax−b⊤x,\phi(x) = \tfrac{1}{2} x^\top A x - b^\top x,

whose gradient is ∇ϕ=Ax−b\nabla \phi = Ax - b (zero exactly at the solution). CG generates iterates x0,x1,…x_0, x_1, \dots each of which is the exact minimizer of ϕ\phi over a growing Krylov subspace, using only the gradients (residuals) rk=b−Axkr_k = b - A x_k.

AA-conjugate directions

Two directions pp and qq are conjugate with respect to AA if p⊤Aq=0p^\top A q = 0, the AA-inner-product orthogonality. CG builds an AA-conjugate set of search directions: each pkp_k is AA-orthogonal to all previous ones.

The payoff: minimizing ϕ\phi along AA-conjugate directions one at a time is the same as minimizing it over their span. There is no need to revisit a previous direction once you have stepped along it. After kk steps, xkx_k is the exact minimizer in the kk-dimensional subspace spanned by the first kk directions.

The CG update

Starting from x0x_0 with r0=b−Ax0r_0 = b - A x_0 and p0=r0p_0 = r_0, each step does:

  1. Step length along the current direction pkp_k that minimizes ϕ\phi: αk=rk⊤rkpk⊤Apk.\alpha_k = \frac{r_k^\top r_k}{p_k^\top A p_k}.
  2. Update xk+1=xk+αkpkx_{k+1} = x_k + \alpha_k p_k and rk+1=rk−αkApkr_{k+1} = r_k - \alpha_k A p_k.
  3. New direction AA-conjugate to all previous: βk=rk+1⊤rk+1rk⊤rk,pk+1=rk+1+βkpk.\beta_k = \frac{r_{k+1}^\top r_{k+1}}{r_k^\top r_k}, \qquad p_{k+1} = r_{k+1} + \beta_k p_k.

The cost per iteration is one matrix-vector product ApkA p_k and a handful of vector operations: O(n)O(n) memory plus the cost of multiplying by AA, which for sparse AA scales with the number of nonzeros.

Krylov subspaces and finite termination

The space spanned by {r0,Ar0,A2r0,…,Ak−1r0}\{r_0, A r_0, A^2 r_0, \dots, A^{k-1} r_0\} is the Krylov subspace Kk\mathcal{K}_k. CG's xkx_k minimizes ϕ\phi over x0+Kkx_0 + \mathcal{K}_k. In exact arithmetic, the dimension of the Krylov subspace grows by one each step until it contains the true solution, so CG terminates in at most nn steps with xn=x⋆x_n = x^\star[2].

Convergence rate

In practice CG converges much faster than nn when the eigenvalues of AA cluster. With condition number κ=λmax⁡/λmin⁡\kappa = \lambda_{\max}/\lambda_{\min}, the error satisfies

∥xk−x⋆∥A≤2(κ−1κ+1)k ∥x0−x⋆∥A,\lVert x_k - x^\star\rVert_A \le 2\left(\frac{\sqrt\kappa - 1}{\sqrt\kappa + 1}\right)^k\, \lVert x_0 - x^\star\rVert_A,

a rate of κ\sqrt\kappa like Nesterov's acceleration. Preconditioning (solving M−1Ax=M−1bM^{-1}A x = M^{-1}b for a cheap M≈AM \approx A) shrinks the effective condition number and is what makes CG practical for ill-conditioned systems[3].

Conjugate gradient builds A-orthogonal search directions and converges far faster than steepest descent on a symmetric positive-definite system.
Conjugate gradient builds A-orthogonal search directions and converges far faster than steepest descent on a symmetric positive-definite system.

Check it on arrays

python · numpy
import numpy as np

rng = np.random.default_rng(0)

# Random SPD system of moderate condition number.
n = 200
B = rng.normal(size=(n, n))
A = B @ B.T + 0.5 * np.eye(n)                              # SPD
b = rng.normal(size=n)

def cg(A, b, tol=1e-10, max_iter=None):
    x = np.zeros_like(b)
    r = b - A @ x
    p = r.copy()
    rs_old = r @ r
    history = []
    for k in range(max_iter or len(b) + 5):
        Ap = A @ p
        alpha = rs_old / (p @ Ap)
        x = x + alpha * p
        r = r - alpha * Ap
        rs_new = r @ r
        history.append(np.sqrt(rs_new))
        if rs_new < tol**2:
            break
        beta = rs_new / rs_old
        p = r + beta * p
        rs_old = rs_new
    return x, history

x_cg, hist = cg(A, b)
x_true = np.linalg.solve(A, b)
print(f"CG converged in {len(hist)} iterations")
print(f"final residual: {hist[-1]:.2e}")
print(f"error vs direct solve: {np.linalg.norm(x_cg - x_true):.2e}")
print(f"finite-termination bound: <= n = {n}")

CG reaches the solution in fewer iterations than the dimension when the spectrum is favorable, with residual decaying geometrically.

Exercises

Exercise 1. For the SPD system with A=(4113)A = \left(\begin{smallmatrix} 4 & 1 \\ 1 & 3 \end{smallmatrix}\right), b=(1,2)⊤b = (1, 2)^\top, and x0=0x_0 = 0, compute the first CG step length α0\alpha_0 and the iterate x1x_1 by hand.

Solution

Step 1. With x0=0x_0 = 0, the initial residual is r0=b−Ax0=b=(1,2)⊤r_0 = b - A x_0 = b = (1, 2)^\top, and the first direction is p0=r0=(1,2)⊤p_0 = r_0 = (1, 2)^\top.

Step 2. The step length is α0=r0⊤r0p0⊤Ap0\alpha_0 = \dfrac{r_0^\top r_0}{p_0^\top A p_0}. The numerator is r0⊤r0=12+22=5r_0^\top r_0 = 1^2 + 2^2 = 5. For the denominator,

Ap0=(4113)(12)=(67),p0⊤Ap0=1⋅6+2⋅7=20.A p_0 = \begin{pmatrix} 4 & 1 \\ 1 & 3 \end{pmatrix}\begin{pmatrix} 1 \\ 2 \end{pmatrix} = \begin{pmatrix} 6 \\ 7 \end{pmatrix}, \qquad p_0^\top A p_0 = 1 \cdot 6 + 2 \cdot 7 = 20.

So α0=5/20=1/4\alpha_0 = 5/20 = 1/4.

Step 3. The new iterate is

x1=x0+α0p0=14(1,2)⊤=(0.25,0.5)⊤.x_1 = x_0 + \alpha_0 p_0 = \tfrac14 (1, 2)^\top = (0.25, 0.5)^\top.

This is the exact minimizer of ϕ\phi along the line x0+tp0x_0 + t p_0.

Exercise 2. With condition number κ=4\kappa = 4, evaluate the CG error-reduction factor κ−1κ+1\frac{\sqrt\kappa - 1}{\sqrt\kappa + 1} per step, and find how many steps drive the AA-norm error below 10−610^{-6} of its start (ignore the leading factor of 22).

Solution

Step 1. With κ=4\kappa = 4, κ=2\sqrt\kappa = 2, so the per-step factor is

κ−1κ+1=2−12+1=13.\frac{\sqrt\kappa - 1}{\sqrt\kappa + 1} = \frac{2 - 1}{2 + 1} = \frac{1}{3}.

Step 2. The error bound shrinks like (1/3)k(1/3)^k. Requiring (1/3)k≤10−6(1/3)^k \le 10^{-6} and taking logs:

k≥log⁡10−6log⁡(1/3)=−6log⁡10−log⁡3≈−13.816−1.0986≈12.6.k \ge \frac{\log 10^{-6}}{\log (1/3)} = \frac{-6 \log 10}{-\log 3} \approx \frac{-13.816}{-1.0986} \approx 12.6.

Step 3. The smallest integer is k=13k = 13. A well-conditioned spectrum (κ=4\kappa = 4) gives a κ\sqrt\kappa-rate of 1/31/3 per step, so a handful of iterations suffices, far below the dimension nn.

Exercise 3 (code). Implement CG for a random SPD system and verify two properties: the residuals rkr_k are mutually orthogonal, and the solution matches a direct solve.

Solution
python · numpy
import numpy as np

rng = np.random.default_rng(0)
n = 40
# Well-conditioned SPD matrix: eigenvalues spread evenly in [1, 10].
Q = np.linalg.qr(rng.normal(size=(n, n)))[0]
A = Q @ np.diag(np.linspace(1.0, 10.0, n)) @ Q.T
b = rng.normal(size=n)

x = np.zeros(n)
r = b - A @ x
p = r.copy()
rs_old = r @ r
residual_vecs = [r.copy()]
for _ in range(n):
    Ap = A @ p
    alpha = rs_old / (p @ Ap)
    x = x + alpha * p
    r = r - alpha * Ap
    rs_new = r @ r
    residual_vecs.append(r.copy())
    if rs_new < 1e-20:
        break
    p = r + (rs_new / rs_old) * p
    rs_old = rs_new

# Residuals are mutually orthogonal (a defining CG property). Check the early
# iterates, before the residuals shrink to the rounding floor.
R = np.array(residual_vecs[:10])
R = R / np.linalg.norm(R, axis=1, keepdims=True)     # unit-normalize each
G = R @ R.T
off_diag = G - np.diag(np.diag(G))
assert np.max(np.abs(off_diag)) < 1e-8

x_true = np.linalg.solve(A, b)
assert np.linalg.norm(x - x_true) < 1e-6
print("max off-diagonal residual inner product:", np.max(np.abs(off_diag)))

The off-diagonal Gram entries are negligible, confirming the residuals form an orthogonal set, which is the discrete reason CG never needs to revisit a direction and terminates in at most nn steps.

The next lesson generalizes Krylov solvers to nonsymmetric systems with GMRES.


References

  1. [1]Hestenes, M. R., & Stiefel, E. (1952). "Methods of conjugate gradients for solving linear systems". Journal of Research of the National Bureau of Standards, 49(6), 409-436. The original conjugate-gradient algorithm.↩
  2. [2]Trefethen, L. N., & Bau, D. (1997). Numerical Linear Algebra. SIAM. CG, Krylov subspaces, and finite termination (Lectures 38-40).↩
  3. [3]Saad, Y. (2003). Iterative Methods for Sparse Linear Systems (2nd ed.). SIAM. Preconditioned CG and the κ\sqrt\kappa convergence bound (doi:10.1137/1.9780898718003).↩