Finite Difference Methods
Pdes
Finite difference methods discretize derivatives by replacing them with differences between nearby grid values. The simplest and oldest approach to PDEs: lay down a uniform mesh, write down a stencil that approximates each derivative to some order in the spacing, and assemble the resulting linear (or nonlinear) algebraic system. Three questions dominate the analysis: ACCURACY (how fast does error go to zero as the mesh refines?), STABILITY (do small perturbations grow without bound, or stay bounded?), and CONSISTENCY (does the discrete equation actually approximate the continuous one?). The famous Lax equivalence theorem says: for well-posed linear PDEs, CONSISTENCY + STABILITY ↔ CONVERGENCE. Get those two right and the discrete solution converges to the true one.
Discretizing derivatives
The CENTRAL second-derivative stencil:
Taylor expansion of around :
Summing gives , so the central second-derivative stencil is SECOND-ORDER ACCURATE: the leading error is . The first-derivative analogue is also second-order. ONE-SIDED differences are FIRST-order — useful for upwind discretizations of advection but pay a one-order accuracy penalty.
Worked example: 1D Poisson
For the elliptic problem on with Dirichlet boundary conditions , the central second-derivative stencil yields a tridiagonal linear system:
A symmetric positive definite tridiagonal system. The condition number of this matrix scales as — so refining the mesh by 10× makes the linear solve harder (in flop count for an iterative method). For 1D this is irrelevant; for 2D and 3D the conditioning is what motivates preconditioning and multigrid. See condition number for the analysis.
Time-stepping for parabolic equations
For the heat equation , the simplest scheme is FTCS (forward in time, central in space):
First-order in time, second-order in space, EXPLICIT (each new value computed in one pass, no linear solve). The von Neumann stability analysis: substitute (a single Fourier mode of growth factor ). Plug in:
Stability requires for ALL wave numbers . The most-unstable mode is (the highest representable frequency), giving . The condition is . EXACTLY at the boundary, gives : marginal stability. Above the boundary, the mode grows exponentially.
The constraint is restrictive: it forces . Refining the mesh by 10× makes the time step shrink by 100×, so total work scales as in 1D — punishing at fine resolutions. The standard fix is implicit time stepping: BACKWARD EULER or CRANK-NICOLSON. Both are unconditionally stable (any works) but require solving a linear system at every step. Crank-Nicolson is second-order in time as well; it's the standard default for parabolic problems.
The CFL condition for hyperbolic equations
For the linear transport equation with , the FIRST-ORDER UPWIND scheme is
Von Neumann growth factor: . The magnitude squared simplifies to . Stability requires — the CFL CONDITION named after Courant, Friedrichs, and Lewy (1928).
Geometrically: the discrete scheme has a finite domain of dependence ( depends only on ). The CFL condition says that this discrete domain of dependence must CONTAIN the continuous one (the characteristic line back to the previous time level). If , the foot of the characteristic falls outside the upwind stencil, the scheme is asking values "from the future" relative to its information, and any numerical error in the unobserved direction blows up.
Code: three demonstrations
# Finite difference methods for PDEs — three demonstrations:
# (a) 1D Poisson convergence at O(h²)
# (b) FTCS heat-equation stability boundary at r = 1/2
# (c) Upwind advection CFL boundary at CFL = 1
import numpy as np
# ─── (a) Convergence of central FD on 1D Poisson ────────────────────────
# -u''(x) = pi² sin(pi x), u(0) = u(1) = 0, exact u(x) = sin(pi x).
def poisson_convergence():
print(" (a) Poisson -u'' = pi² sin(pi x) — central FD, second order:")
print(f" {'N':>5s} {'h':>10s} {'||err||_inf':>14s} {'ratio':>8s}")
errs = []
for N in [11, 21, 41, 81, 161, 321]:
x = np.linspace(0, 1, N)
h = x[1] - x[0]
A = (np.diag(-2*np.ones(N-2)) + np.diag(np.ones(N-3), 1)
+ np.diag(np.ones(N-3), -1)) / h**2
rhs = -np.pi**2 * np.sin(np.pi * x[1:-1])
u = np.zeros(N)
u[1:-1] = np.linalg.solve(A, rhs)
err = np.max(np.abs(u - np.sin(np.pi * x)))
ratio = errs[-1] / err if errs else float('nan')
errs.append(err)
print(f" {N:>5d} {h:>10.4f} {err:>14.4e} {ratio:>8.2f}")
print(" (h halves → error /≈4: O(h²) confirmed)")
# ─── (b) Stability boundary for FTCS heat equation ──────────────────────
# Use random initial data so high-frequency modes are present.
def heat_stability():
print("\n (b) FTCS heat eqn: u^{n+1}_j = u^n_j + r (u^n_{j+1} - 2 u^n_j + u^n_{j-1})")
print(" Stability bound r = alpha dt/dx² <= 1/2.")
print(f" {'r':>6s} {'final max|u|':>16s} {'status':>10s}")
N = 51
h = 1.0 / (N - 1)
np.random.seed(1)
u0 = np.random.randn(N); u0[0] = u0[-1] = 0.0
for r in [0.40, 0.50, 0.51, 0.55, 0.60]:
dt = r * h**2
nsteps = int(0.05 / dt)
u = u0.copy()
for _ in range(nsteps):
u_new = u.copy()
u_new[1:-1] = u[1:-1] + r * (u[2:] - 2*u[1:-1] + u[:-2])
u_new[0] = u_new[-1] = 0.0
u = u_new
peak = np.max(np.abs(u))
status = "STABLE" if peak < 100 else "BLOWUP"
print(f" {r:>6.2f} {peak:>16.3e} {status:>10s}")
# ─── (c) CFL boundary for upwind advection ──────────────────────────────
def upwind_cfl():
print("\n (c) Upwind advection u_t + u_x = 0: CFL = c dt/dx must be <= 1.")
print(f" {'CFL':>6s} {'final max|u|':>16s} {'status':>10s}")
N = 401
h = 1.0 / N
np.random.seed(2)
u0 = np.random.randn(N)
for cfl in [0.5, 0.9, 1.0, 1.1, 1.5]:
dt = cfl * h
nsteps = int(0.4 / dt)
u = u0.copy()
for _ in range(nsteps):
u_new = u.copy()
u_new[1:] = u[1:] - cfl * (u[1:] - u[:-1])
u_new[0] = u[0] - cfl * (u[0] - u[-1])
u = u_new
peak = np.max(np.abs(u))
status = "STABLE" if peak < 100 else "BLOWUP"
print(f" {cfl:>6.2f} {peak:>16.3e} {status:>10s}")
poisson_convergence()
heat_stability()
upwind_cfl() Output:
(a) Poisson -u'' = pi² sin(pi x) — central FD, second order:
N h ||err||_inf ratio
11 0.1000 8.2654e-03 nan
21 0.0500 2.0587e-03 4.01
41 0.0250 5.1420e-04 4.00
81 0.0125 1.2852e-04 4.00
161 0.0063 3.2128e-05 4.00
321 0.0031 8.0319e-06 4.00
(h halves → error /≈4: O(h²) confirmed)
(b) FTCS heat eqn: u^{n+1}_j = u^n_j + r (u^n_{j+1} - 2 u^n_j + u^n_{j-1})
Stability bound r = alpha dt/dx² <= 1/2.
r final max|u| status
0.40 7.152e-02 STABLE
0.50 2.166e-01 STABLE
0.51 2.343e+03 BLOWUP
0.55 1.731e+17 BLOWUP
0.60 5.348e+29 BLOWUP
(c) Upwind advection u_t + u_x = 0: CFL = c dt/dx must be <= 1.
CFL final max|u| status
0.50 4.310e-01 STABLE
0.90 7.524e-01 STABLE
1.00 4.109e+00 STABLE
1.10 2.118e+11 BLOWUP
1.50 4.313e+31 BLOWUP Three things land cleanly. (1) O(h²) Poisson convergence: error ratio is 4.0 every time the mesh is doubled — exactly the predicted second-order behavior. By we have error from a 6th-grade-arithmetic discretization. (2) The r = 1/2 stability boundary is sharp: r=0.50 gives output, r=0.51 has already blown up to 2000, r=0.60 reaches — the high-frequency mode amplifying by per step. (3) The CFL boundary is equally sharp: CFL ≤ 1 stays bounded, CFL = 1.1 already explodes to , CFL = 1.5 reaches . These are not soft transitions; the stability criterion is a hard threshold.
Higher-order schemes and trade-offs
A few rule-of-thumb extensions worth knowing.
- 5-point stencil for : is fourth-order accurate. Doubles the stencil width but raises convergence from to : at fixed accuracy, the mesh can be coarser by a factor of .
- Compact / Padé differences give high-order accuracy with the same stencil width as the basic scheme, by treating the derivatives implicitly: . Lele's compact schemes hit 6th and 10th order.
- Crank-Nicolson: . Second-order in time, unconditionally stable, second-order in space. The default time integrator for parabolic problems.
- Runge-Kutta in time: discretize space first, then integrate the resulting ODE system with RK4 — fourth-order in time. See method of lines for the framework.
- Higher-order upwind / WENO: for hyperbolic problems with shocks, basic upwind is too diffusive; weighted essentially-non-oscillatory schemes give shock-capturing high-order accuracy.
When FD is the wrong tool
- Complex geometry: FD on a uniform grid struggles with curved boundaries. Use finite elements on an unstructured mesh, or boundary-fitted curvilinear coordinates.
- High-order accuracy on smooth solutions: spectral methods have super-exponential convergence and reach machine precision with vastly fewer degrees of freedom.
- Operator splitting required: when the PDE has multiple physical effects (advection + diffusion + reaction), operator splitting integrates each part with the method best suited to it.
Related
- The three classical equations — Poisson, heat, wave; the canonical FD targets.
- Spectral methods — the high-accuracy alternative for smooth solutions.
- Method of lines — discretize space with FD, then integrate the resulting ODE.
- Condition number — the discrete Laplacian has conditioning, motivating preconditioning at fine meshes.
- Finite element method — the alternative discretization strategy, especially for unstructured meshes.