Projection Method for Incompressible Navier–Stokes: Step by Step
A clear derivation of Chorin's fractional step (projection) method for incompressible flow — showing how to split the momentum and pressure equations and enforce the divergence-free constraint.
The incompressible Navier–Stokes equations present a fundamental challenge: the pressure appears in the momentum equation but has no independent evolution equation of its own. Chorin's projection method (1968) resolves this by splitting the time advance into two fractional steps.
The Equations
Find the velocity field and pressure satisfying:
The divergence-free constraint (continuity) is the difficulty: acts as a Lagrange multiplier to enforce it, but solving for and simultaneously requires a coupled system.
Step 1: Intermediate Velocity (Ignore Pressure)
Advance the velocity field from time to an intermediate state using only advection and diffusion — ignoring the pressure gradient entirely:
This is a simple explicit update. The result will generally not satisfy .
Step 2: Pressure Poisson Equation
We want to find a pressure correction such that the corrected velocity:
satisfies . Taking the divergence:
This gives the pressure Poisson equation:
The right-hand side is computable from . Solving this elliptic equation yields , which acts as (or is proportional to) the pressure.
Step 3: Project onto Divergence-Free Space
Correct the velocity:
Update the pressure: (in the simplest formulation).
This projection step is why the method is called the projection method — it projects onto the space of divergence-free vector fields.
Python Implementation (Lid-Driven Cavity)
import numpy as np
def build_rhs(u, v, dx, dy, dt, rho):
"""RHS of pressure Poisson equation."""
b = np.zeros_like(u)
b[1:-1, 1:-1] = (rho / dt) * (
(u[1:-1, 2:] - u[1:-1, :-2]) / (2 * dx) +
(v[2:, 1:-1] - v[:-2, 1:-1]) / (2 * dy)
)
return b
def pressure_poisson(p, b, dx, dy, nit=50):
"""Solve pressure Poisson with Gauss-Seidel."""
for _ in range(nit):
pn = p.copy()
p[1:-1, 1:-1] = (
(pn[1:-1, 2:] + pn[1:-1, :-2]) * dy**2 +
(pn[2:, 1:-1] + pn[:-2, 1:-1]) * dx**2 -
b[1:-1, 1:-1] * dx**2 * dy**2
) / (2 * (dx**2 + dy**2))
# Neumann BC: dp/dn = 0 on walls
p[:, -1] = p[:, -2]
p[0, :] = p[1, :]
p[:, 0] = p[:, 1]
p[-1, :] = 0 # fixed pressure reference
return p
Why the Staggered Grid Matters
In the implementation above, storing , , and all at the same grid nodes leads to the checkerboard pressure instability — a spurious oscillation where pressure alternates sign on neighbouring cells without affecting the velocity.
The fix is a MAC (Marker-and-Cell) staggered grid:
- stored at left/right cell faces (staggered in )
- stored at top/bottom cell faces (staggered in )
- stored at cell centres
This arrangement means the discrete divergence of velocity and the discrete gradient of pressure are naturally paired — eliminating the instability. The SIMPLE algorithm, which I cover in a companion note, uses this grid by default.
Convergence and Stability
The explicit scheme above is conditionally stable. The CFL condition requires:
For flows at higher Reynolds number (smaller ), this forces very small time steps. The SIMPLE algorithm avoids this by treating the pressure implicitly.
What I Learned
Building this solver from scratch — rather than using a black-box tool — exposed something important: the pressure Poisson solve is the computational bottleneck. The elliptic solver (Gauss–Seidel above) converges slowly; in production codes, multigrid methods or conjugate gradient with preconditioning are used instead. This is one of the reasons OpenFOAM uses algebraic multigrid (GAMG) for pressure.