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.

When FD is the wrong tool

Related