Numerical PDEs (Finite Differences)

Solve heat, wave and Laplace equations on a grid. Learn grid discretisation, finite-difference stencils, explicit (FTCS) and implicit schemes, Crank–Nicolson, stability conditions (r = αΔt/Δx² ≤ ½, CFL), iterative solution of Laplace's equation, and verification against exact solutions — with a demonstration of instability.

When geometry or boundary conditions defeat separation of variables, engineers discretise the domain into a grid and replace derivatives with finite differences — the foundation of CFD and thermal simulation codes.

An analogy

Finite differences turn a smooth field into a chessboard: each square's temperature is updated from its neighbours' values, like people adjusting to the average opinion of those around them.

Discretisation

Replace the domain by nodes; derivatives by differences (central in space). Boundary conditions set edge nodes (Dirichlet) or ghost nodes (Neumann).

Explicit vs implicit

Explicit schemes update each node directly (cheap per step) but have stability limits. Implicit schemes solve a linear system each step (tridiagonal in 1D — Thomas algorithm) but allow large steps.

Stability conditions

Heat FTCS: r ≤ ½. Wave: CFL C ≤ 1 (information cannot travel more than one cell per step). Von Neumann analysis derives these limits.

Verification and beyond

Compare with exact solutions and refine grids (error ∝ Δx², Δt for FTCS). Finite volumes (conservation, CFD) and finite elements (complex geometry) extend the ideas.

Formulas

Worked example: Stable time step

Given: α = 1.11 × 10⁻⁴ m²/s (copper), Δx = 1 cm

Find: Max explicit Δt.

Answer: Δt ≤ 0.45 s

Worked example: Laplace iteration

Given: Square plate: top at 100 °C, others 0 °C; one interior node

Find: Centre temperature.

Answer: 25 °C (finer grids converge to the same value by symmetry)

Explicit heat equation: stable vs unstable time step

import numpy as np

# Explicit FTCS for u_t = a u_xx, u(0)=u(1)=0, u(x,0)=sin(pi x)
a, nx = 1.0, 21
dx = 1.0 / (nx - 1)
for r in (0.4, 0.6):                       # r = a dt / dx^2, stable if r <= 0.5
    dt = r * dx**2 / a
    x = np.linspace(0, 1, nx)
    u = np.sin(np.pi * x)
    u[nx // 2] += 1e-6                    # tiny disturbance (e.g. round-off)
    steps = int(round(0.2 / dt))
    for _ in range(steps):
        u[1:-1] = u[1:-1] + r * (u[2:] - 2 * u[1:-1] + u[:-2])
    exact = np.exp(-np.pi**2 * 0.2) * np.sin(np.pi * 0.5)
    print(f"r = {r}: u(0.5, 0.2) = {u[nx // 2]: .4e}   exact {exact:.4e}")
# r = 0.4: u(0.5, 0.2) =  1.3812e-01   exact 1.3891e-01
# r = 0.6: u(0.5, 0.2) = -6.6412e+11   exact 1.3891e-01

With r = 0.4 the scheme matches the exact solution to 0.6 %. With r = 0.6 a one-in-a-million disturbance grows to 10¹¹ — violating the stability limit destroys the solution no matter how accurate the formula looks.

Common mistakes

Applications

Key takeaways

Going further