Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

The Lattice Boltzmann Method (LBM) is a methodological approach to the simulation of transport phenomena that differs from classical “finite-X” [1] methods in that it doesn’t solve the relevant PDEs directly; but does so through a statistical, mesoscopic approach. Its predominant application is the simulation of fluids following the Navier-Stokes Equations, for which it is derived from the Boltzmann equation.

Prerequesites: Lattice Boltzmann Method

If you are not yet familiar with the fundamentals of the LBM, we recommend that you take on these lessons in combination with studying LBM theory, using textbooks such as Krüger et al. (2017).

For computational fluid dynamics (CFD), the LBM is attractive due to its relative algorithmic simplicity. Due to the locality of its operations it is straightforwardly parallelizable and performs well on modern supercomputing hardware.

The term LBM nowadays covers a huge ecosystem of different models based on the common LBM discretization scheme. The Python package lbmpy, which we will get to know in this lesson, implements a significant subset of these in purely symbolic algebra, based on pystencils.

This lesson explores the basic functionalities of lbmpy by applying it to a popular benchmark problem of CFD: the Taylor-Green Vortex (TGV) Decay scenario.

Lesson Goals

By the end of this lesson, you will have created, run, and evaluated a numerical solver for the TGV decay scenario using pystencils and lbmpy. On the way, you will

  • define the basic algebraic ingredients of a simple D2Q9 BGK lattice Boltzmann method using lbmpy;

  • configure and derive the essential operations of the LBM, namely initialization, collision, streaming, and export of observables;

  • set up a fully periodic simulation domain and implement periodic streaming;

  • evaluate your simulation method’s correctness and accuracy with respect to an analytical solution.

Let’s start by importing what packages we need. When working in a Jupyter notebook, use the cells below to activate interactive plotting with PyVista.

Simulation Domain

The Taylor-Green Vortex Decay scenario we consider in this lesson lives on a fully periodic square domain with sidelengths 2π2 \pi; i.e. our simulation space will be Ω=[0,2π]2\Omega = [0, 2 \pi]^2. This space will be discretized uniformly using N×NN \times N cells, on which we will later run our lattice Boltzmann solver.

Notebook Cell

Initial State and Analytical Solution

The density and velocity fields of the two-dimensional Taylor-Green vortex is described by the following time-dependent equations:

ρTGV(x,t)=1−34u02exp⁡(−4νt)⋅(cos⁡(2x0)+cos⁡(2x1))uTGV(x,t)=u0exp⁡(−2νt)(sin⁡x0cos⁡x1−cos⁡x0sin⁡x1)\begin{align*} \rho^{\mathrm{TGV}} (\boldsymbol{x}, t) &= 1 - \frac{3}{4} u_0^2 \exp{\left( -4 \nu t \right)} \cdot \left( \cos{\left( 2x_0 \right)} + \cos{\left( 2x_1 \right)} \right) \\ \boldsymbol{u}^{\mathrm{TGV}} (\boldsymbol{x}, t) &= u_0 \exp{ \left( -2 \nu t \right) } \begin{pmatrix} \sin{ x_0 } \cos{ x_1 } \\ - \cos{ x_0 } \sin{ x_1 } \end{pmatrix} \end{align*}

The flow field depends on two free parameters:

Notebook Cell
Notebook Cell
Notebook Cell

You can use the function plot_u_streamlines in the following cell to visualize the velocity field’s streamlines.

If you did everything correctly, you should see a square of four counter-rotating vortices; the fluid remaining stationary at their centers, and moving with a maximum speed of u0u_0 at their contact points.

Notebook Cell
2026-09-30 07:07:55.588 (   4.058s) [    79A3255E0200]vtkXOpenGLRenderWindow.:1452  WARN| bad X server connection. DISPLAY=
<PIL.Image.Image image mode=RGB size=1024x768>

Figure 1:Velocity streamlines of the initial state.

The Lattice Boltzmann Solver

Our goal for this lesson is to simulate the time evolution of the TGV flow state with the lattice Boltzmann method. We will use lbmpy to construct the four central operations of our solver:

For each, lbmpy provides pre-defined operators which we merely have to configure with the details of our chosen lattice Boltzmann method. Before we get to them, we first have to define our discretization scheme by selecting a stencil and lattice structure. We then continue with the collision operator, as the derivations of the other operators depend on it.

Spatial Discretization: Stencil and Lattice

A wide range of different collision models has been developed under the umbrella of “lattice Boltzmann methods”, and many of these are implemented within lbmpy; from the most basic BGK scheme to advanced cumulant-based collision operators. For the purposes of this lesson, we will start small, using the classical single-relaxation time BGK method on a D2Q9 lattice.

The D2Q9 velocity set discretizes the density distribution function of the Boltzmann equation using nine velocities in two dimensions:

SD2Q9={(cxcy)  ∣  cx,cy∈{−1,0,1}}\mathcal{S}^{\mathrm{D2Q9}} = \left\{ \left. \begin{pmatrix} c_x \\ c_y \end{pmatrix} \; \right\vert \; c_x, c_y \in \{ -1, 0, 1 \} \right\}
The D2Q9 velocity set. The stationary velocity is marked in green; the primary velocities in blue,
and the secondary velocities in purple.
Directions are abbreviated according to compass directions (North, South, East, West) and Center.

Figure 2:The D2Q9 velocity set. The stationary velocity is marked in green; the primary velocities in blue, and the secondary velocities in purple. Directions are abbreviated according to compass directions (North, South, East, West) and Center.

In lbmpy, velocity sets are represented by the LBStencil class. Let’s create an LBStencil instance for D2Q9:

Loading...

When printing the stencil in a Jupyter notebook, you get an overview of its velocities and their numbering.

According to the D2Q9 velocity set, in the LBM we store and manipulate a local distribution vector f(x)∈R9\boldsymbol{f}(\boldsymbol{x}) \in \mathbb{R}^9 on each cell x\boldsymbol{x} of our grid Ω\Omega. There, the entry fi(x)f_i(\boldsymbol{x}) represents the relative number of particles at x\boldsymbol{x} moving with the velocity ci∈SD2Q9\boldsymbol{c_i} \in \mathcal{S}^{\mathrm{D2Q9}}. Assembling all these local distributions into a field f:Ω→R9f: \Omega \to \mathbb{R}^9, we get the underlying lattice for our simulation:

A 2D lattice using the D2Q9 velocity set. Each cell \boldsymbol{x} holds an ensemble \boldsymbol{f}(\boldsymbol{x}) of nine discrete populations.

Figure 3:A 2D lattice using the D2Q9 velocity set. Each cell x\boldsymbol{x} holds an ensemble f(x)\boldsymbol{f}(\boldsymbol{x}) of nine discrete populations.

The lattice we represent using the Lattice class from the lbmpy.lattice module. The following cell creates a lattice, and immediately adds it to Ω_data to prepare its data array:

Loading...

The Collision Operator

The classical BGK collision operator, which we will use for our simulation, is given by eq. 3:

f∗=(1−ω)f+ωfeq\boldsymbol{f}^{\ast} = (1 - \omega) \boldsymbol{f} + \omega \boldsymbol{f}^{\mathrm{eq}}

where feq\boldsymbol{f}^{\mathrm{eq}} is the equilibrium distribution, and ω∈(12,2)\omega \in (\frac{1}{2}, 2) designates the relaxation rate. For the equilibrium distribution, we are using the second-order Hermite expansion of the isothermal Maxwell-Boltzmann equilibrum (eq. 4):

fieq(ρ,u)=wiρ(1+2ci⊤u−u⊤u2cs2+(ci⊤u)22cs4)f_i^{\mathrm{eq}} (\rho, \boldsymbol{u}) = w_i \rho \left( 1 + \frac{ 2 \boldsymbol{c}_i^{\top} \boldsymbol{u} - \boldsymbol{u}^{\top} \boldsymbol{u} }{ 2 c_s^2 } + \frac{ \left( \boldsymbol{c}_i^{\top} \boldsymbol{u} \right)^2 }{ 2 c_s^4} \right)

with (wi)i=0,…,8(w_i)_{i = 0, \dots, 8} the lattice weights, and cs=13c_s = \sqrt{\frac{1}{3}} the lattice speed of sound. The local density ρ\rho and velocity uu are computed as moments of the pre-collision population vector f\boldsymbol{f}:

ρ=∑i=08fi,u=1ρ∑i=08ci⋅fi\rho = \sum_{i = 0}^{8} f_i, \qquad \boldsymbol{u} = \frac{1}{\rho} \sum_{i = 0}^{8} \boldsymbol{c}_i \cdot f_i

The relaxation rate is directly related to the kinematic viscosity ν\nu as

ω=13ν+12\omega = \frac{1}{3 \nu + \frac{1}{2}}

We could now go about implementing these equations manually as a pystencils operator. Luckily, lbmpy saves us that effort as these collision equations, and many more, are already realized via its algebraic derivation engine.

To define lattice Boltzmann collision schemes in lbmpy, we need to construct an object of the LBMConfig class. To its constructor we pass the various settings and ingredients of our methods:

Once our configuration is complete, we tell lbmpy to derive the collision equations as a pystencils flowgraph by creating an object of the class lattice.Collide.

Notebook Cell
Loading...
Notebook Cell

The Streaming Step

The collision operation is one of two operations that make up the explicit lattice Boltzmann time-stepping scheme. The second is the streaming step, where all post-collision populations move to their neighboring cells according to their lattice velocities:

fi(x+ci)=fi∗(x).f_i (\boldsymbol{x} + \boldsymbol{c}_i) = f_i^{\ast} (\boldsymbol{x}).

In lbmpy, the streaming step (7) is executed by the lattice.Advance operation.

Notebook Cell

Lattice Initialization and Export of Observables

Before we can begin the LBM time integration, the lattice f needs to be initialized to reflect the initial state given by (1). We will use an initialization to equilibrium, where all populations fi(x)f_i (\boldsymbol{x}) are set to the equilibrium values fieq(ρ(x),u(x))f^{\mathrm{eq}}_i (\rho(\boldsymbol{x}), \boldsymbol{u}(\boldsymbol{x})) (see (4)). This is realized by the SetEquilibrium graph, which is defined in the lbmpy.lattice module.

The following cell creates a pytencils operator from an instance of SetEquilibrium. The SetEquilibrium graph takes four arguments:

Run the following cell to create and print the initialization graph.

Output
Loading...

SetEquilibrium is used to initialize the LBM’s populations from an initial state, turning density and velocity values to associated distribution functions. However, to evaluate the results of our simulation, we also need to go in the other direction: extract the momentary density and velocity values from the LBM’s distributions, and write them out to the fields rho and u. This is done by the ExportMacroscopics flowgraph from the lattice module.

Notebook Cell
Loading...

Periodic Streaming

As mentioned in the beginning of this lesson, the Taylor-Green vortex scenario is fully periodic. For the LBM, this means that during the streaming step, populations moving across the edges of Ω\Omega must re-enter the domain on its opposite side, as illustrated in figure 4:

Periodic streaming in x-direction. Populations leaving the domain on the western edge must re-enter in the east,
and populations leaving at the eastern border come back in at the western border.
In the TGV scenario, the same needs to happen in y-direction.

Figure 4:Periodic streaming in xx-direction. Populations leaving the domain on the western edge must re-enter in the east, and populations leaving at the eastern border come back in at the western border. In the TGV scenario, the same needs to happen in yy-direction.

Periodic streaming is realized by the lbmpy operator lattice.PeriodicStream.

Notebook Cell

LBM Integration Loop

Finally, all ingredients are in place to create our lattice Boltzmann solver loop. Here’s a few basic rules for implementing an LBM time stepping scheme in lbmpy:

Notebook Cell

Evaluation: Kinetic Energy

When running the simulation, we’d like a quantitative assessment of its results and their accuracy. Here, the TGV scenario comes in very handy, as it is one of very few known analytical solutions to the Navier-Stokes equations. We know its desired behavior exactly (eq. 1) and can thus use it to evaluate the correctness of our numerical method.

Instead of checking against the solution directly, in this lesson we will instead evaluate the total kinetic energy in the TGV system. As the vortices slow down each other due to viscous friction, the system’s momentum, and therefore kinetic energy, decay exponentially over time.

The total kinetic energy of the discrete TGV system is given as

Ekin=∑x∈ΩV(x)ρ(x)2(ux(x)2+uy(x)2).E^{\mathrm{kin}} = \sum_{\boldsymbol{x} \in \Omega} \frac{V(\boldsymbol{x}) \rho( \boldsymbol{x} )}{2} \left( u_x (\boldsymbol{x})^2 + u_y (\boldsymbol{x})^2 \right).

where V(x)V(\boldsymbol{x}) is the volume of the cell x\boldsymbol{x}. We will later compare this to the analytical solution for EkinE^{\mathrm{kin}}, which by integration is found to be

Ekin∗(t)=u02π2exp⁡(−4ν(2πN)2t).E^{\mathrm{kin \ast}} (t) = u_0^2 \pi^2 \exp\left( - 4 \nu \left( \frac{2 \pi}{N} \right)^2 t \right).
Notebook Cell

Run the Simulation

Now all that’s left is to run the simulation and evaluate its results.

Notebook Cell

Now it is finally time to initialize all data structures and run the simulation.

Notebook Cell
<PIL.Image.Image image mode=RGB size=1024x768>
Notebook Cell
<Figure size 640x480 with 1 Axes>

Figure 5:Progression of the kinetic energy, compared to the analytical solution.

If everything was done correctly, you should see the same streamlines profile as in the beginning - just with a uniformly lower velocity. Plotting the kinetic energy, you will see that the values from your simulation soon depart slightly from the analytical solution, but follow the same trend of exponential decay.

Conclusion

In this lesson, you have established first contact with the lbmpy library for lattice Boltzmann fluid simulations. You have set up the fundamental algebraic objects of the LBM (namely the stencil and lattice); and have configured the basic operations SetEquilibrium, Collide, Advance, ExportMacroscopics and PeriodicStream for the traditional BGK LBM scheme. These you used to prepare and simulate the Taylor-Green Vortex Decay flow scenario, for which you finally evaluated the progression of kinetic energy to evaluate your method’s correctness. Congratulations!

Footnotes
  1. Finite Differences, Finite Volumes, Finite Elements

References
  1. Krüger, T., Kusumaatmaja, H., Kuzmin, A., Shardt, O., Silva, G., & Viggen, E. M. (2017). The Lattice Boltzmann Method: Principles and Practice. In Graduate Texts in Physics. Springer International Publishing. 10.1007/978-3-319-44649-3
  2. Hennig, F., Holzer, M., & Rüde, U. (2023). Advanced Automatic Code Generation for Multiple Relaxation-Time Lattice Boltzmann Methods. SIAM Journal on Scientific Computing, 45(4), C233–C254. 10.1137/22m1531348
  3. Geier, M., & Schönherr, M. (2017). Esoteric Twist: An Efficient in-Place Streaming Algorithmus for the Lattice Boltzmann Method on Massively Parallel Hardware. Computation, 5(2), 19. 10.3390/computation5020019