MATH-336 · Lesson 11 of 12 · 22 min

Finite difference methods and stability

Separation of variables needs simple geometry, constant coefficients and linear equations. Real problems, a turbine blade, a layered soil, a nonlinear reaction, rarely oblige. Numerical methods fill the gap. The finite difference method replaces derivatives by differences on a grid and the PDE by a system of algebraic equations, which a computer solves. The catch is stability: a perfectly consistent scheme can produce garbage that grows exponentially if the time step is too large. This lesson builds explicit and implicit schemes for the heat equation and the leapfrog scheme for the wave equation, analyses their accuracy and stability by the method of von Neumann, and states the Courant–Friedrichs–Lewy condition that every explicit scheme for waves must obey.

Difference quotients

On a grid \(x_j = j\,\Delta x\), Taylor's theorem gives \(u(x + h) = u + hu' + \frac{h^2}{2}u'' + \frac{h^3}{6}u''' + \frac{h^4}{24}u'''' + \cdots\). Combining expansions at \(x \pm h\) yields the standard approximations

\[\frac{u_{j+1} - u_j}{h} = u' + O(h), \qquad \frac{u_{j+1} - u_{j-1}}{2h} = u' + O(h^2), \qquad \frac{u_{j+1} - 2u_j + u_{j-1}}{h^2} = u'' + \frac{h^2}{12}u'''' + O(h^4)\]

The forward difference is first-order accurate; the centred differences are second order, meaning the error falls by a factor of four when \(h\) is halved. The leading error term is the truncation error. In practice it competes with rounding error, which grows as \(h\) shrinks because of cancellation in \(u_{j+1} - u_j\); the program in lesson 1 chose its step sizes with this trade-off in mind.

The explicit scheme for the heat equation

For \(u_t = \alpha u_{xx}\) on \(0 \le x \le L\), use a grid with \(\Delta x = L/J\) and time levels \(t_n = n\,\Delta t\), and write \(U_j^n\) for the approximation to \(u(x_j, t_n)\). A forward difference in time and a centred difference in space give the forward-time centred-space (FTCS) scheme

\[U_j^{n+1} = U_j^n + r\big(U_{j+1}^n - 2U_j^n + U_{j-1}^n\big), \qquad r = \frac{\alpha\,\Delta t}{\Delta x^2}\]

Each new value is computed directly from three old values, so the scheme is explicit and very cheap per step. Its truncation error is \(O(\Delta t) + O(\Delta x^2)\): substituting the exact solution leaves a residual of that size, so the scheme is consistent with the PDE. Boundary values are imposed directly at \(j = 0\) and \(j = J\) for Dirichlet conditions; a Neumann condition \(u_x = 0\) is handled with a ghost point, setting \(U_{-1} = U_1\).

When \(r \le \frac12\), the coefficients \(r\), \(1 - 2r\) and \(r\) are all non-negative and sum to 1, so each new value is a weighted average of old ones. This gives a discrete maximum principle: the numerical solution never exceeds its initial and boundary maxima, and errors cannot grow. When \(r > \frac12\), the middle coefficient is negative and the averaging argument fails. The next section shows that the scheme then genuinely blows up.

Three grid diagrams with space index across and time level up. Explicit heat: one red point at the new level above three green known points at the old level. Implicit heat: three red unknown points at the new level above one green known point. Wave leapfrog: one red new point above three green points at the current level and one blue point two levels down.
Stencils: the explicit heat scheme computes one new value from three old ones; the implicit scheme couples three new values to one old one, so a linear system must be solved; the leapfrog wave scheme uses two previous levels

Von Neumann stability analysis

John von Neumann's method, developed at Los Alamos in the 1940s and published by Crank and Nicolson in 1947 and by Charney, Fjørtoft and von Neumann in 1950, analyses how each Fourier mode is amplified per step. Substitute \(U_j^n = G^ne^{ik j\Delta x}\) into the scheme. The common factor cancels, leaving the amplification factor

\[G(k) = 1 + r\big(e^{ik\Delta x} - 2 + e^{-ik\Delta x}\big) = 1 - 4r\sin^2\frac{k\Delta x}{2}\]

Stability requires \(|G| \le 1\) for every wavenumber. Since \(\sin^2\) ranges over \([0, 1]\), \(G\) ranges over \([1 - 4r, 1]\), and \(|G| \le 1\) exactly when \(r \le \frac12\). For \(r > \frac12\), the shortest grid wave, with \(k\Delta x = \pi\) (alternating signs from point to point), has \(G = 1 - 4r < -1\): it flips sign and grows every step. Rounding errors contain this mode, so it appears and explodes even if the initial data are smooth. The stability limit \(\Delta t \le \Delta x^2/(2\alpha)\) is severe: halving \(\Delta x\) for accuracy forces four times as many time steps.

Compare with the exact solution: mode \(k\) decays by \(e^{-\alpha k^2\Delta t}\) per step. For small \(k\Delta x\), \(G \approx 1 - \alpha k^2\Delta t\), which matches. For high wavenumbers the scheme damps less accurately, but as long as \(|G| \le 1\), the error stays bounded. The Lax equivalence theorem of Peter Lax and Robert Richtmyer (1956) makes this precise: for a well-posed linear problem, a consistent scheme converges if and only if it is stable.

Two panels after 60 explicit time steps starting from a sine arch with a tiny alternating perturbation. Left, r equals 0.45: the green numerical solution lies on top of the dashed exact sine arch. Right, r equals 0.55: the red numerical solution is a violent zigzag of alternating spikes far larger than the dashed exact curve.
The explicit heat scheme after 60 steps from a sine arch with a perturbation of size 0.01 in the shortest grid mode. At \(r = 0.45\) the perturbation decays; at \(r = 0.55\) it is amplified by \(|1 - 4r| = 1.2\) each step, a factor of about 56,000 in 60 steps

Implicit schemes and Crank–Nicolson

Evaluating the space difference at the new time level gives the backward Euler, or fully implicit, scheme \(-rU_{j-1}^{n+1} + (1 + 2r)U_j^{n+1} - rU_{j+1}^{n+1} = U_j^n\). Its amplification factor is \(G = 1/(1 + 4r\sin^2(k\Delta x/2))\), which lies in \((0, 1]\) for every \(r > 0\): the scheme is unconditionally stable. Each step requires solving a tridiagonal linear system, which the Thomas algorithm, a streamlined Gaussian elimination, does in about \(8J\) operations, so the cost per step is still proportional to the number of grid points. The accuracy is still only first order in time.

John Crank and Phyllis Nicolson, working on heat conduction problems, published in 1947 the scheme that averages the explicit and implicit space differences:

\[U_j^{n+1} - U_j^n = \frac r2\big(\delta^2U_j^{n+1} + \delta^2U_j^n\big), \qquad \delta^2U_j = U_{j+1} - 2U_j + U_{j-1}\]

centred at the half step. Its truncation error is \(O(\Delta t^2) + O(\Delta x^2)\), and its amplification factor \(G = (1 - 2r\sin^2(k\Delta x/2))/(1 + 2r\sin^2(k\Delta x/2))\) satisfies \(|G| \le 1\) for all \(r\). It is the standard workhorse for diffusion problems. One caveat: for large \(r\), \(G \to -1\) for the highest modes, so sharp initial data produce slowly decaying oscillations; a few initial backward Euler steps, a remedy due to Rannacher, remove them.

The wave equation and the CFL condition

For \(u_{tt} = c^2u_{xx}\), centred differences in both variables give the leapfrog scheme

\[U_j^{n+1} = 2U_j^n - U_j^{n-1} + \sigma^2\big(U_{j+1}^n - 2U_j^n + U_{j-1}^n\big), \qquad \sigma = \frac{c\,\Delta t}{\Delta x}\]

The first step uses the initial velocity: \(U_j^1 = U_j^0 + \Delta t\,g_j + \frac{\sigma^2}{2}\delta^2U_j^0\), which is second-order accurate. The von Neumann substitution gives \(G^2 - 2(1 - 2\sigma^2s^2)G + 1 = 0\) with \(s = \sin(k\Delta x/2)\). The two roots have product 1, so stability requires both to lie on the unit circle, which happens when \(|1 - 2\sigma^2s^2| \le 1\) for all \(s\), that is, when

\[\sigma = \frac{c\,\Delta t}{\Delta x} \le 1\]

This is the Courant–Friedrichs–Lewy condition, from the 1928 paper of Richard Courant, Kurt Friedrichs and Hans Lewy, which used difference equations to prove existence theorems for PDEs. Its meaning is geometric. The numerical solution at \((x_j, t_n)\) depends on grid data in \([x_j - n\Delta x, x_j + n\Delta x]\), its numerical domain of dependence. The true solution depends on data in \([x_j - ct_n, x_j + ct_n]\). If the numerical domain does not contain the true one, the scheme cannot possibly converge, because it never sees data that affect the answer. Requiring \(c\,\Delta t \le \Delta x\) is exactly this containment. Remarkably, for \(\sigma = 1\) the leapfrog scheme is exact for the one-dimensional wave equation: the update becomes \(U_j^{n+1} = U_{j+1}^n + U_{j-1}^n - U_j^{n-1}\), a discrete version of d'Alembert's formula.

Solvers in code

The program implements the explicit heat scheme at two values of \(r\) and compares with the exact series solution, then solves the same problem by Crank–Nicolson with large time steps using the Thomas algorithm, and finally runs the leapfrog scheme for a plucked string at Courant numbers 0.9, 1.0 and 1.05, checking the discrete energy.

Explicit heat steps on either side of r = 1/2, Crank–Nicolson with large steps, and the leapfrog wave scheme below, at and above the CFL limit
import math

J = 20
dx = 1.0 / J
xs = [j * dx for j in range(J + 1)]
f = lambda x: math.sin(math.pi * x) + 0.5 * math.sin(3 * math.pi * x)
exact = lambda x, t: math.exp(-math.pi ** 2 * t) * math.sin(math.pi * x) + 0.5 * math.exp(-9 * math.pi ** 2 * t) * math.sin(3 * math.pi * x)

def ftcs(r, T):
    dt = r * dx * dx
    U = [f(x) + 1e-12 * (-1) ** j for j, x in enumerate(xs)]   # rounding-sized noise
    U[0] = U[-1] = 0.0
    steps = round(T / dt)
    for _ in range(steps):
        U = [0.0] + [U[j] + r * (U[j + 1] - 2 * U[j] + U[j - 1]) for j in range(1, J)] + [0.0]
    return U, steps * dt

def thomas(a, b, c, d):
    n = len(d); cp, dp = [0.0] * n, [0.0] * n
    cp[0], dp[0] = c[0] / b[0], d[0] / b[0]
    for i in range(1, n):
        m = b[i] - a[i] * cp[i - 1]
        cp[i] = c[i] / m if i < n - 1 else 0.0
        dp[i] = (d[i] - a[i] * dp[i - 1]) / m
    x = [0.0] * n; x[-1] = dp[-1]
    for i in range(n - 2, -1, -1):
        x[i] = dp[i] - cp[i] * x[i + 1]
    return x

def crank_nicolson(r, T):
    dt = r * dx * dx
    U = [f(x) for x in xs]; U[0] = U[-1] = 0.0
    n = J - 1
    a, b, c = [-r / 2] * n, [1 + r] * n, [-r / 2] * n
    steps = round(T / dt)
    for _ in range(steps):
        d = [r / 2 * U[j - 1] + (1 - r) * U[j] + r / 2 * U[j + 1] for j in range(1, J)]
        U = [0.0] + thomas(a, b, c, d) + [0.0]
    return U, steps * dt

for r in (0.45, 0.5, 0.55):
    U, t = ftcs(r, 0.3)
    err = max(abs(U[j] - exact(xs[j], t)) for j in range(J + 1))
    print(f"explicit r = {r:4.2f}: t = {t:.4f}, max error {err:.3e}")
for r in (0.5, 5.0, 20.0):
    U, t = crank_nicolson(r, 0.1)
    err = max(abs(U[j] - exact(xs[j], t)) for j in range(J + 1))
    print(f"Crank-Nicolson r = {r:4.1f}: {round(0.1 / (r * dx * dx)):4d} steps, max error {err:.3e}")

# leapfrog for a plucked string, c = 1, fixed ends, released from rest
p0 = lambda x: 0.02 * (x / 0.3 if x < 0.3 else (1 - x) / 0.7)
def leapfrog(sigma, T, N=50):
    h = 1.0 / N; dt = sigma * h; s2 = sigma * sigma
    X = [j * h for j in range(N + 1)]
    old = [p0(x) for x in X]
    cur = [0.0] + [old[j] + s2 / 2 * (old[j + 1] - 2 * old[j] + old[j - 1]) for j in range(1, N)] + [0.0]
    steps = round(T / dt)
    for _ in range(steps - 1):
        new = [0.0] + [2 * cur[j] - old[j] + s2 * (cur[j + 1] - 2 * cur[j] + cur[j - 1]) for j in range(1, N)] + [0.0]
        old, cur = cur, new
    # discrete energy at the half step between old and cur
    E = sum(0.5 * ((cur[j] - old[j]) / dt) ** 2 * h for j in range(N + 1)) + \
        sum(0.5 * (cur[j + 1] - cur[j]) * (old[j + 1] - old[j]) / h for j in range(N))
    return cur, X, E
for sigma in (0.9, 1.0, 1.05):
    U, X, E2 = leapfrog(sigma, 2.0)
    err = max(abs(U[j] - p0(X[j])) for j in range(len(X)))
    line = f"leapfrog sigma = {sigma:4.2f}: after one period max |U - initial| = {err:.3e}"
    if err < 1:
        E1 = leapfrog(sigma, 0.54)[2]
        line += f", discrete energy {E1:.6e} at t = 0.54 and {E2:.6e} at t = 2"
    print(line)
print(f"continuous energy of the initial shape {0.5 * 0.02 ** 2 * (1 / 0.3 + 1 / 0.7):.6e}")
Output
explicit r = 0.45: t = 0.3004, max error 5.348e-04
explicit r = 0.50: t = 0.3000, max error 6.307e-04
explicit r = 0.55: t = 0.2998, max error 1.954e+04
Crank-Nicolson r =  0.5:   80 steps, max error 7.403e-04
Crank-Nicolson r =  5.0:    8 steps, max error 3.322e-04
Crank-Nicolson r = 20.0:    2 steps, max error 7.565e-02
leapfrog sigma = 0.90: after one period max |U - initial| = 6.848e-04, discrete energy 9.340136e-04 at t = 0.54 and 9.340136e-04 at t = 2
leapfrog sigma = 1.00: after one period max |U - initial| = 1.735e-17, discrete energy 9.297052e-04 at t = 0.54 and 9.297052e-04 at t = 2
leapfrog sigma = 1.05: after one period max |U - initial| = 6.985e+20
continuous energy of the initial shape 9.523810e-04

The initial data carry noise of size \(10^{-12}\) in the alternating mode, standing in for rounding error. The explicit scheme is accurate at \(r = 0.45\) and \(r = 0.5\) and destroyed at \(r = 0.55\), where that noise has been multiplied by nearly 1.2 at each of 218 steps. Crank–Nicolson stays accurate with time steps ten times larger than the explicit limit. With steps forty times larger it remains stable, but two steps are too few to follow the fast decay of the \(\sin 3\pi x\) mode, whose amplification factor is about \(-0.37\) per step instead of the true 0.012: unconditional stability is not unconditional accuracy. The leapfrog scheme returns the plucked string to its initial shape after one period exactly, to rounding, at \(\sigma = 1\), with a small dispersive error at \(\sigma = 0.9\), and blows up at \(\sigma = 1.05\). Its discrete energy, a grid version of \(\frac12\int(u_t^2 + c^2u_x^2)\), is the same at both times sampled; it differs slightly from the continuous energy because the grid cannot resolve the corner of the pluck.

Beyond one dimension

In two dimensions the explicit heat scheme on a square grid requires \(r \le \frac14\), and in three \(r \le \frac16\), so implicit methods become even more attractive; the alternating direction implicit method of Peaceman and Rachford (1955) solves only tridiagonal systems by treating one direction implicitly at a time. For waves the CFL condition becomes \(c\,\Delta t\sqrt{1/\Delta x^2 + 1/\Delta y^2} \le 1\). Complicated geometries are usually handled by finite element methods, which approximate the solution by piecewise polynomials on triangles or tetrahedra and grew out of structural engineering in the 1950s and 1960s. The stability ideas of this lesson, amplification factors, domains of dependence and the Lax equivalence theorem, carry over to all of them.

Common mistakes

  • MistakeHalving \(\Delta x\) in the explicit heat scheme without reducing \(\Delta t\).

    InsteadStability needs \(\Delta t \le \Delta x^2/(2\alpha)\); halving \(\Delta x\) requires quartering \(\Delta t\).

  • MistakeJudging stability by a short run with smooth data.

    InsteadUnstable modes start at rounding level and need many steps to become visible; use von Neumann analysis.

  • MistakeAssuming an unconditionally stable scheme is accurate for any time step.

    InsteadStability prevents blow-up; accuracy still requires \(\Delta t\) small compared with the time scales of interest.

  • MistakeRunning an explicit wave scheme with \(c\,\Delta t > \Delta x\).

    InsteadThe CFL condition is necessary for convergence of any explicit scheme: the numerical domain of dependence must contain the true one.

  • MistakeForgetting to start leapfrog with a special first step.

    InsteadThe scheme needs two time levels; use \(U^1 = U^0 + \Delta t\,g + \frac{\sigma^2}{2}\delta^2U^0\).

Summary and key terms

  • Centred differences approximate \(u''\) with error \(O(h^2)\); the FTCS heat scheme is \(U^{n+1}_j = U^n_j + r\,\delta^2U^n_j\) with \(r = \alpha\Delta t/\Delta x^2\).
  • Von Neumann analysis gives \(G = 1 - 4r\sin^2(k\Delta x/2)\), so the explicit scheme is stable exactly when \(r \le \frac12\).
  • Implicit and Crank–Nicolson schemes are unconditionally stable and need a tridiagonal solve per step; Crank–Nicolson (1947) is second order in time.
  • Leapfrog for waves is stable under the CFL condition \(c\,\Delta t/\Delta x \le 1\) (1928), and exact at equality in one dimension.
  • Lax equivalence: for linear well-posed problems, consistency plus stability is equivalent to convergence.
truncation error
The residual left when the exact solution is substituted into a difference scheme.
explicit scheme
A scheme that computes new values directly from old ones.
implicit scheme
A scheme in which new values are coupled and found by solving a linear system.
amplification factor
The factor by which a Fourier mode is multiplied in one time step.
CFL condition
The requirement \(c\,\Delta t \le \Delta x\) that the numerical domain of dependence contain the true one.
Lax equivalence theorem
Consistency and stability together are equivalent to convergence for linear problems.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. For \(\alpha = 10^{-5}\) m²/s and \(\Delta x = 1\) cm, what is the largest stable explicit time step?

    Show answer

    \(\Delta t \le \Delta x^2/(2\alpha) = 10^{-4}/(2 \times 10^{-5}) = 5\) s.

  2. Show that the centred second difference has truncation error \(\frac{h^2}{12}u''''\).

    Show answer

    Adding the Taylor series at \(x \pm h\) cancels odd terms: \(u_{j+1} + u_{j-1} = 2u + h^2u'' + \frac{h^4}{12}u'''' + \cdots\); subtract \(2u\) and divide by \(h^2\).

  3. Find the amplification factor of the backward Euler heat scheme.

    Show answer

    Substituting \(G^ne^{ikj\Delta x}\): \(G(1 + 4r\sin^2(k\Delta x/2)) = 1\), so \(G = 1/(1 + 4r\sin^2(k\Delta x/2))\in(0, 1]\).

  4. A wave on a string has \(c = 300\) m/s and the grid has \(\Delta x = 1\) cm. What time step does CFL allow?

    Show answer

    \(\Delta t \le \Delta x/c = 3.3 \times 10^{-5}\) s.

  5. Verify that with \(\sigma = 1\) the leapfrog update is \(U_j^{n+1} = U_{j+1}^n + U_{j-1}^n - U_j^{n-1}\).

    Show answer

    With \(\sigma^2 = 1\): \(2U_j^n - U_j^{n-1} + U_{j+1}^n - 2U_j^n + U_{j-1}^n\), and the \(2U_j^n\) terms cancel.

  6. Write the explicit scheme with an insulated right end \(u_x(L, t) = 0\).

    Show answer

    Use a ghost point \(U_{J+1} = U_{J-1}\); at \(j = J\), \(U_J^{n+1} = U_J^n + 2r(U_{J-1}^n - U_J^n)\).

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz