HomeArticlesComputational Fluid Dynamics

Lattice-Boltzmann Flow: Fluid Dynamics from Kinetic Theory

Skip solving Navier-Stokes directly — simulate colliding particle populations on a small lattice and let fluid motion fall out as an emergent result.

mysimulator teamUpdated July 2026≈ 12 min read▶ Open the simulation

A mesoscopic detour around Navier-Stokes

Most computational fluid dynamics codes attack the Navier-Stokes equations head-on: discretise the macroscopic fields — density ρ, velocity u, pressure p — on a mesh and march them forward, typically requiring a global pressure-Poisson solve every step to keep the flow divergence-free. The Lattice-Boltzmann Method (LBM) takes a different route entirely, borrowed from kinetic theory: instead of macroscopic fields, it tracks populations of fictitious particles hopping between neighbouring lattice cells and colliding locally. Density and velocity are recovered afterward as simple sums — fluid behaviour is not imposed, it emerges.

live demo · flow past an obstacle on a D2Q9 lattice● LIVE

The continuous Boltzmann transport equation, ∂f/∂t + ξ·∇f = Ω(f), splits fluid evolution into a streaming term (particles moving freely) and a collision term Ω that pushes the distribution f back toward local equilibrium. LBM's central trick is to discretise both space and velocity at once: space becomes a regular grid, and the continuous velocity space collapses to a handful of fixed directions per node. Despite the drastic simplification, a Chapman-Enskog expansion shows the resulting update recovers incompressible Navier-Stokes to second order in the Mach number.

D2Q9 — nine directions per node

The most common 2D lattice is called D2Q9: 2 dimensions, 9 discrete velocity directions per node — the eight compass directions plus staying still. Each direction carries a weight chosen so that the lattice correctly reproduces the mass and momentum moments of the continuous equilibrium distribution:

e0=(0,0) rest              w0 = 4/9
e1..4 = axis-aligned N/E/S/W    w1..4 = 1/9
e5..8 = diagonal NE/NW/SE/SW    w5..8 = 1/36

// Σ w_i = 1  → conserves mass
// speed of sound on the lattice: c_s² = 1/3

Collide, then stream

Every time step does exactly two things at every node. First, collision: each of the nine populations f_i relaxes toward its local equilibrium value f_i^eq at a rate set by the relaxation time τ — the BGK approximation (Bhatnagar-Gross-Krook), which replaces the full collision integral with a single linear relaxation:

f_i* = f_i − (1/τ) · (f_i − f_i^eq)         // collision

f_i^eq = w_i · ρ · [1 + 3(e_i·u) + 4.5(e_i·u)² − 1.5·u²]

kinematic viscosity:  ν = (τ − 0.5) / 3      // τ sets viscosity directly

Second, streaming: each post-collision population f_i* is shifted exactly one cell in its own direction e_i, so f_i(x + e_i, t+1) = f_i*(x, t). The collision step is entirely local — every node updates independently of its neighbours, using only its own nine numbers — while streaming needs only first-neighbour data. That locality is precisely why LBM parallelises so well on GPUs: there is no global linear system to solve at any point in the loop.

τ must stay above 0.5 for stability, and in practice the Mach number |u|/c_s should stay comfortably below about 0.3 too — push either limit and the low-Mach assumption underlying the whole scheme breaks down and the simulation blows up.

Recovering density and velocity

The macroscopic quantities that a user actually cares about — density and velocity — are just moments of the nine populations sitting at a node, computed with a single pass over local data:

ρ(x,t)    = Σ_i f_i(x,t)              // zeroth moment: mass
ρ·u_x(x,t) = Σ_i f_i(x,t) · e_ix       // first moment: momentum
ρ·u_y(x,t) = Σ_i f_i(x,t) · e_iy

pressure (ideal-gas form, lattice units): p = ρ·c_s² = ρ/3

Solid boundaries use the simplest no-slip trick available: bounce-back — a population arriving at a wall node is reflected straight back the direction it came from, which reproduces zero velocity at the wall using nothing but local operations. Inlets and outlets with a prescribed velocity or pressure use the more involved Zou-He scheme, which analytically solves for the unknown incoming populations at the boundary.

How the simulation here uses it

The simulation on this site runs the full collide-stream cycle on a D2Q9 grid every frame, with a solid obstacle placed in a uniform inflow and bounce-back applied at its boundary. At a modest grid resolution the entire loop — collision, streaming, boundary handling and a colour-by-speed render — runs comfortably at 60 fps in plain JavaScript on typed arrays; no WebGL compute pass is needed until the grid gets much larger. Watch the flow at moderate Reynolds number and you'll see the classic Kármán vortex street emerge behind the obstacle — alternating vortices peeling off, entirely as a consequence of the same local collision rule running at every node, with no vortex-shedding logic written anywhere in the code.

Frequently asked questions

Why does the Lattice-Boltzmann Method simulate fictitious particles instead of solving Navier-Stokes directly?

LBM works at a mesoscopic level, tracking particle-population distributions on a small fixed set of lattice directions and letting a simple local collision rule relax them toward equilibrium. The Chapman-Enskog expansion shows this recovers the incompressible Navier-Stokes equations as an emergent consequence, while the actual per-step computation is only local additions and lookups, which parallelises far more naturally than solving a global pressure-Poisson equation every step.

What does the relaxation time τ actually control in the BGK collision step?

τ sets the kinematic viscosity of the simulated fluid, via ν = (τ − 0.5)/3 in lattice units. Larger τ means a more viscous, syrupy fluid; τ approaching 0.5 means viscosity approaching zero. The simulation becomes numerically unstable as τ gets too close to 0.5, especially at higher flow velocities, which is why real implementations keep the Mach number small and τ comfortably above that bound.

Why is bounce-back used for solid walls instead of just setting velocity to zero?

Bounce-back reflects each incoming particle population straight back the direction it came from at the wall node, which reproduces a no-slip boundary condition (zero velocity at the wall) purely through local, first-neighbour operations that fit LBM's streaming structure. Directly imposing velocity would require solving for consistent distribution functions at the boundary, which the Zou-He scheme does when a specific inflow or outflow velocity needs to be enforced rather than a simple solid wall.

Try it live

Everything above runs in your browser — open Lattice-Boltzmann Flow and watch a vortex street form behind the obstacle in real time. Nothing is installed, nothing is uploaded, the whole model lives in one tab.

▶ Open Lattice-Boltzmann Flow simulation

What did you find?

Add reproduction steps (optional)