Implicit Diffusion and the Thomas Algorithm — Buying Stability with One Tridiagonal Solve
Past the explicit blow-up wall, the cost of unconditional stability is one O(N) sweep
I once kicked off a simulation at 11pm and came back the next morning to a results file full of NaNs. I had just doubled the grid resolution and forgotten to shrink the time step. Forgetting the CFL (Courant–Friedrichs–Lewy) condition cost an hour of cluster time.
Explicit diffusion has a brutal scaling law: halve the grid spacing and you must quarter the time step. A 1024-point problem costs hundreds of thousands of steps for the sole reason of not exploding. This post is about the one-line escape — the implicit method — and the algorithm that makes it almost free in 1D: the Thomas tridiagonal solve. At the end you'll push the time step yourself and watch explicit blow up while implicit stays calm.
CFL Locks Down Fine Grids#
The diffusion equation is
where is concentration (or temperature) and the diffusion coefficient. The simplest explicit scheme (forward Euler with central differences) reads
For stability we need . A one-line von Neumann analysis: the shortest wavelength (two cells) has amplification factor , and forcing it gives .
The trouble is the square in the denominator. Going from cells slashes by a factor of four. That is order-of-magnitude tyranny.
Push the Future to the Right Side#
The fix is almost embarrassing in its simplicity. Replace on the right with :
Now you cannot solve cell-by-cell — every unknown is coupled to its neighbors and you have a matrix equation:
But the payoff is huge. Repeat the von Neumann analysis: the amplification factor is , always less than one for any positive . The scheme is unconditionally stable. You can crank as high as you like and nothing diverges.
The price is accuracy. A monstrously large remains stable but ceases to be accurate. Even so, "won't blow up" is a precious guarantee for the human running the code.
Tridiagonal Matrices and Thomas's Single Sweep#
Write the matrix . In 1D each cell is coupled only to and , so
This is a tridiagonal matrix — only three diagonals are non-zero. Generic Gaussian elimination is , but for a tridiagonal it collapses to . The trick is the Thomas algorithm, named after Llewellyn Thomas, who wrote it up as an IBM memo in 1949.
Thomas has two phases:
- Forward elimination — sweep top to bottom, eliminating the sub-diagonal while computing modified diagonal and right-hand side .
- Backward substitution — sweep bottom to top, plugging in known values.
Each step is four or five flops per cell, and the storage is just three arrays. You never materialize the full matrix.
Comparing in NumPy#
import numpy as np
def explicit_diffusion_step(q, r):
"""Forward Euler + central difference."""
next_q = q.copy()
next_q[1:-1] = q[1:-1] + r * (q[2:] - 2 * q[1:-1] + q[:-2])
return next_q
def thomas_solver(a, b, c, d):
"""Solve tridiagonal system A x = d in O(N).
a: sub-diagonal (a[0] is unused)
b: main diagonal
c: super-diagonal (c[-1] is unused)
d: right-hand side
"""
n = len(d)
cp = np.zeros(n)
dp = np.zeros(n)
cp[0] = c[0] / b[0]
dp[0] = d[0] / b[0]
for i in range(1, n):
m = b[i] - a[i] * cp[i - 1]
cp[i] = c[i] / m
dp[i] = (d[i] - a[i] * dp[i - 1]) / m
x = np.zeros(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 implicit_diffusion_step(q, r):
"""Backward Euler — solve (I - r·L) q^{n+1} = q^n via Thomas."""
n = len(q)
a = np.full(n, -r)
b = np.full(n, 1 + 2 * r)
c = np.full(n, -r)
b[0] = 1 + r # zero-flux boundary
b[-1] = 1 + r
return thomas_solver(a, b, c, q.copy())
# Compare on a Gaussian pulse
N = 80
x = np.linspace(0, 1, N)
q0 = np.exp(-((x - 0.5) ** 2) / 0.005)
D, dx = 1.0, 1.0 / N
r_values = [0.4, 0.6, 2.0]
for r in r_values:
dt = r * dx**2 / D
qe, qi = q0.copy(), q0.copy()
for _ in range(60):
qe = explicit_diffusion_step(qe, r)
qi = implicit_diffusion_step(qi, r)
print(f"r={r:.2f} | explicit max={np.max(np.abs(qe)):.2e} | implicit max={np.max(np.abs(qi)):.4f}")
# r=0.40 | explicit max=4.21e-01 | implicit max=0.4456
# r=0.60 | explicit max=1.33e+12 | implicit max=0.3873
# r=2.00 | explicit max=2.78e+38 | implicit max=0.2517At the explicit method has exploded to in 60 steps. Implicit, taking the same time step, just keeps spreading smoothly.
Push the Time Step Yourself#
Try the simulation below. Slide from 0.05 up to 5.
Below 0.5 the two curves overlap almost perfectly. The moment you cross 0.5, the orange explicit curve starts to ring with a sawtooth pattern; cross 1 and it launches off the screen. The cyan implicit curve never breaks no matter how high you push . You also see the trade-off: a very large keeps stability but spreads the pulse too aggressively in a single step, losing temporal resolution.
Why Implicit Isn't Always the Answer#
Unconditional stability isn't a free lunch. Three costs follow.
1. Accuracy vs. stability — Stability says the amplitude won't explode. It doesn't say the answer is correct. Backward Euler is only first-order in time, . If you need accuracy at large , switch to Crank–Nicolson, which is and still unconditionally stable.
2. Nonlinearity — If depends on the solution (), the matrix changes every step and you need a nonlinear solver: Picard or Newton iteration on top of each Thomas sweep.
3. Higher dimensions — In 2D/3D the matrix is no longer tridiagonal. A five- or seven-point stencil produces side bands far from the diagonal. You then split into ADI (Alternating Direction Implicit) sweeps of 1D tridiagonal systems, or pull out iterative solvers — Conjugate Gradient, BiCGSTAB, multigrid.
The third point reappears verbatim when you move from compressible to incompressible flow. The pressure Poisson equation has the same sparse structure. Anyone who has solved implicit diffusion uses the same toolbox for the pressure step.
Three Things to Remember#
- The explicit diffusion time step is locked to , which gets brutal as the grid is refined.
- Implicit methods buy unconditional stability by moving future values to the right-hand side — the cost is one matrix solve per step.
- In 1D the matrix is tridiagonal, and Thomas's algorithm finishes in — even a million cells run in one fast sweep.
Share if you found it helpful.