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 Advection-Diffusion Equation models the distribution of a pollutant quantity cc in some medium (i.e. a fluid). It comprises two parts:

These two terms together describe the evolution in time of the concentration cc:

∂c∂t=∇⋅(D∇c)−∇⋅(vc).\frac{\partial c}{\partial t} = \nabla \cdot (D \nabla c) - \nabla \cdot (\boldsymbol{v} c).

This lesson is about using pystencils to simulate a simple two-dimensional advection-diffusion scenario. We will discretize the ADE using a second-order finite difference scheme in space, and first-order explicit Euler integration in time; set up boundary conditions and an integration loop, and visualize the results. As an extra perk, we will build the entire solver to be accellerated by your machine’s graphics processor (GPU) [1].

Lesson Goals

During this lesson, you will use pystencils to construct a solver for a simple two-dimensional, time-dependent problem governed by the advection-diffusion equation. In the process, you will

  • derive an integration rule for the ADE using second-order finite differences in space, and an explicit Euler scheme in time;

  • implement a Neumann boundary condition as a pystencils operator;

  • implement a localized mass source using a guarded operator;

  • accelerate your simulation by running it on the GPU;

  • and use PyVista to interactively visualize your simulation results.

Let’s start by importing the packages we will need.

GPU Target Selection

Up to this point, we have only generated non-optimized kernel code for CPU execution. Pystencils, however, is built to harness the parallel processing power that is now built into most computers, from your laptop up to exascale computing clusters.

One major driver of that parallelism are general-purpose graphics processing units (GPUs). If you have a GPU, you can use it to accelerate your pystencils operators with only minimal changes to your code - without the need to get into the details of GPU kernel programming.

Notebook Cell

Create Algebraic Objects

Let’s begin building our ADE simulation by setting up algebraic objects for our simulation domain, variables, and fields.

Notebook Cell

Implement the Integrator

Next up, we implement the time-integration operator for the advection-diffusion equation. To do so, we first need to take a look at our discretization scheme. We can discretize the three derivative terms separately, like follows:

Notebook Cell

Prepare the Simulation Scenario

Our next step is to set up the simulation data. This includes allocating the field arrays, as well as initializing the field v\boldsymbol{v} to a velocity profile along which the quantity cc will be advected.

If we want to run our simulation on the GPU, the data arrays must accordingly be allocated in GPU memory. The PatchData class takes care of this automatically if we tell it which target we are running on.

Notebook Cell

Initialize the Velocity Field

Notebook Cell

Run the following cell to initialize the velocity field:

Use PyVista for Visualization

In the following cell, we use PyVista to visualize the xx-component of v\boldsymbol{v}.

When PyVista is installed, you can use the viz() method of a patch data object to get its visualization bridge. You can then obtain a VTK mesh object that represents the patch’s simulation grid, and contains the data of its fields. Pass that mesh to a PyVista Plotter and use it to show the visualization:

2026-09-30 07:07:56.085 (   4.601s) [    7C8F0F232200]vtkXOpenGLRenderWindow.:1452  WARN| bad X server connection. DISPLAY=
<PIL.Image.Image image mode=RGB size=1024x768>

xx-component of the velocity field

Boundary Conditions and a Mass Source

Apart from the time integrator for the ADE, our simulation will still need boundary conditions at its walls. Also, for anything at all to happen, we would like to inject a fixed amount of the concentration cc into the medium at a certain point.

Define the Boundary Conditions

We would like to use two different kinds of boundary conditions in our simulation:

Interestingly, we can realize the Dirichlet boundaries simply by doing nothing at all. The PatchData has initialized the arrays for c and c_next to zero; and if you have correctly configured the iteration region of your time integrator, the values at the boundary will stay at zero for all time.

Notebook Cell

Inject a Mass Source

Last, to complete our simulation setup, we would like to inject some concentration cc into our system using a circular mass source. We do so by fixing the concentration to c(x,y)=1.0c(x, y) = 1.0 within the circle with radius r=Ly/10r = L_y / 10 around the point p=(Lx/4Ly/2)\boldsymbol{p} = \begin{pmatrix} L_x / 4 \\ L_y / 2 \end{pmatrix}. This leads us to a situation where we need to write an operator that performs this operation only on an irregularily shaped (circular) part of the domain. Restricting the operator’s iteration space to that circle using ispace is rather difficult, so we instead use another way: we are going to construct a guarded operator.

Pystencils allows us to guard parts of an operator according to given conditions; they will only be executed when these conditions evaluate to true. In our case, our operator has two parts:

We realize these parts by splitting our operator into blocks, and by connecting these blocks accordingly. We use the ps.flow.block and ps.flow.guarded_block decorators to create regular and guarded blocks, respectively.

Notebook Cell

Run the Simulation

Finally, all the ingredients are in place, and we can run our simulation. Let’s first define the full integration routine:

The integration loop is built to run a given number nsteps of time steps, and to extract snapshots of the field cc at a given frequency, in order to later visualize the time series:

Let’s run the simulation for 4000 time steps, and then visualize the result.

We again use PyVista to look at our simulation results. In the Jupyter notebook, you can use the following cell to create an interactive widget for exploring the captured time series:

<PIL.Image.Image image mode=RGB size=1024x768>

Concentration cc after kk time steps

If everything went well, you can see how a trail of advected material develops in the wake of the mass source. The larger the diffusivity DD, the wider the trail will grow. You can now experiment by running the simulation with different values for the diffusivity, but also different time intervals Δt\Delta t.

Conclusion

This concludes our lesson on implementing an explicit solver for the ADE in pystencils. We have seen how to configure operators to run on the GPU; how to bridge the patch data to PyVista for visualizaton, and how to use guarded kernels to apply operations only in certain regions of our domain. The end result is your first simulation of a time-dependent problem in pystencils; we will see many more of these in the following lessons.

Footnotes
  1. If you have one.