v2026.11.16174
Tutorials / 6 · Incompressible flow

Incompressible flow

What is in this tutorial?

This tutorial solves the flow in a lid-driven cavity: a closed square box of fluid whose top wall, the lid, slides sideways at a constant speed. The moving lid drags the fluid beneath it round into one large swirl, or vortex, with two small vortices turning the other way in the bottom corners. The case is one of the most widely used checks of a CFD code, because accurate published results exist for it: here, the velocities that Ghia, Ghia and Shin (1982) tabulate along the two lines through the middle of the box, the centrelines.

The flow is set by its Reynolds number, the ratio of the fluid's inertia to its viscosity: low values give slow, smooth, syrupy flow, high values fast, turbulent flow. At the Reynolds number used here, 400, the flow settles to a steady pattern that stays the same over time.

The tutorial uses zCFD's incompressible solver. An incompressible solver assumes the density of the fluid never changes, and solves only for its velocity and pressure. That holds for liquids such as water, and for gases moving well below the speed of sound: as a rule of thumb, below Mach 0.3, where the Mach number is the flow speed divided by the speed of sound. The compressible solver, used in Simple aerofoil, lets the density change. Use it instead where a flow is mostly slow but the density changes somewhere, through heating or a fast region.

What you need?

Work through Simple aerofoil first: this tutorial relies on it for running zCFD, reading the log and monitoring a run in zMon. The run takes about fifteen seconds on an NVIDIA RTX 3060.

Step 1. Download the files

The tutorial bundle can be downloaded here. It contains a mesh file (cavity.h5), a zCFD control file (cavity.py), and Ghia's tabulated centreline velocities at Re 400 (ghia_re400.csv).

cd ~
wget https://zcfd.zenotech.com/v2026.11.16174/tutorial_6_lid_driven_cavity.tgz
tar xzf tutorial_6_lid_driven_cavity.tgz
cd tutorial_6_lid_driven_cavity

The cavity is 1 m square. The mesh has 127 × 127 cells, clustered towards the walls where the velocity changes fastest, and is one cell thick: the flow is two-dimensional, with symmetry conditions on the front and back faces.

Step 2. Walk through the input

The control file, cavity.py, is a short Python script. All it does is build one Python dictionary, called parameters, that tells zCFD everything about the run: the physics, the numerical method, what to write out, and the mesh with its boundaries. This step walks through that dictionary from top to bottom. Each heading below is the name of a key, written exactly as it appears in the file, and the headings nest the same way the dictionary does: numerical scheme, for example, sits inside solver. Under each heading you'll find what that part controls, what each setting means, and why it has the value it has here. Each section ends with a link to that part's reference page, which lists every setting it accepts and its default; you don't need it to follow the tutorial. On a wide screen the whole control file sits on the right, with the part being described highlighted as you scroll.

parameters = {
    "config version": 2,
    "solver": {
        "solver settings": {"type": "incompressible"},
        "equations": {"type": "viscous"},
        "fluid properties": {
            "material": "ideal gas",
            "gamma": 1.4,
            "gas constant": 287.0,
            "sutherlands const": 110.4,
            "prandtl no": 0.72,
            "turbulent prandtl no": 0.9,
        },
        "reference conditions": {
            "IC_1": {
                "temperature": 273.15,
                "pressure": 101325.0,
                "v": {"vector": [1.0, 0.0, 0.0]},
                "reynolds no": 400.0,
                "reference length": 1.0,
            },
            "IC_2": {
                "temperature": 273.15,
                "pressure": 101325.0,
                "v": {"vector": [0.0, 0.0, 0.0]},
                "reynolds no": 400.0,
            },
        },
        "initialisation": {"reference": "IC_1", "initial conditions": "IC_2"},
        "numerical scheme": {"order": "second"},
        "time settings": {"type": "steady"},
        "convergence control": {
            "scheme": {"name": "coupled", "implicit relax": 0.9},
            "cycles": 500,
        },
        "output settings": {
            "report": {"frequency": 1},
            "solution": {
                "format": "vtk",
                "surface variables": ["V", "p"],
                "volume variables": ["V", "p", "cell_centre"],
            },
        },
    },
    "model": {
        "cavity": {
            "mesh": "cavity.h5",
            "boundary conditions": {
                "BC_1": {
                    "zones": [14],
                    "type": "wall",
                    "kind": "no slip",
                    "velocity": {"type": "linear", "vector": [1.0, 0.0, 0.0]},
                },
                "BC_2": {
                    "zones": [12],
                    "type": "wall",
                    "kind": "no slip",
                },
                "BC_3": {
                    "zones": [11, 13],
                    "type": "symmetry",
                },
            },
        }
    },
}

config version

Says which layout of control file this is, so zCFD knows how to read the rest.

  • config version: 2, the current layout, with solver and model at the top level.

solver

The settings shared by the whole simulation: which solver runs, what it solves, the fluid, the flow states, and how the run marches and what it writes.

solver settings

Chooses the solver.

  • type: "incompressible": the solver that holds the density constant and solves for velocity and pressure. The lid moves at 1 m/s, far below the speed of sound, so the density does not change.

See solver settings.

equations

Sets the equations being solved.

  • type: "viscous": the Navier–Stokes equations, which describe a fluid with viscosity, solved without a turbulence model. At a Reynolds number of 400 the flow is laminar: it moves in smooth layers, with none of the chaotic eddies of turbulence.

See equations, and Choosing a turbulence model for flows that are turbulent.

fluid properties

Describes the fluid.

  • material: "ideal gas": pressure, density and temperature are related by the ideal gas law.
  • gamma: 1.4, the ratio of specific heats, a property of the gas.
  • gas constant: 287.0 J/kg/K. With the reference pressure and temperature it gives the density, 1.29 kg/m³, which the solver then holds constant.
  • sutherlands const: 110.4 K, for Sutherland's law, which varies the viscosity with temperature. The temperature does not change in this run, and the viscosity is set by the Reynolds number instead.
  • prandtl no and turbulent prandtl no: 0.72 and 0.9, which relate heat conduction to viscosity. This run solves no energy equation, so they play no part.

See fluid properties.

reference conditions

Defines named flow states, each a set of values such as temperature, pressure and velocity. Other parts of the file refer to them by name: to fill the domain at the start, to set conditions at boundaries, and to scale the equations.

IC_1

The reference state, moving at the lid's speed.

  • temperature and pressure: 273.15 K and 101325.0 Pa, which with the gas constant fix the density.
  • v: {"vector": [1.0, 0.0, 0.0]}, 1 m/s along x, the lid speed.
  • reynolds no: 400.0, the Reynolds number of the flow, based on the reference length.
  • reference length: 1.0 m, the side of the cavity. With the speed and density, Re 400 sets the viscosity to 0.00323 Ns/m².
IC_2

The fluid at rest.

  • temperature and pressure: as IC_1.
  • v: {"vector": [0.0, 0.0, 0.0]}, still fluid.
  • reynolds no: 400.0, as IC_1.

See reference conditions and Viscosity.

initialisation

Chooses which states start the run and scale it.

  • reference: "IC_1". The solver scales its equations by this state, so the lid speed sets the scale.
  • initial conditions: "IC_2". The fluid starts at rest, and the moving lid sets it in motion.

See initialisation and Choosing initialisation and reference state.

numerical scheme

Sets how the equations are discretised in space: how the smooth flow is represented by one value in each cell of the mesh.

  • order: "second": the solver estimates how the velocity varies across each cell, not just its average, which is more accurate than treating it as constant (first order).

See numerical scheme and Choosing a spatial scheme.

time settings

Says what kind of run this is.

  • type: "steady": at Re 400 the cavity flow settles to a pattern that does not change with time, so the run looks for that final state directly, rather than following the flow through time.

See time settings.

convergence control

Sets how the solver reaches the steady solution. It starts from a guess and improves it once per cycle, or iteration, until the flow stops changing; the run has then converged. The incompressible solver needs no CFL number, the step-size setting the compressible tutorials use.

scheme

In an incompressible flow the pressure is whatever it must be to keep the flow from piling up or emptying out anywhere, so velocity and pressure have to be found together. The scheme says how.

  • name: "coupled": each cycle solves for velocity and pressure together, as one large set of linear equations. It is the scheme to start from. "simple" instead solves for them one after the other and corrects the pressure in between.
  • implicit relax: 0.9: each cycle applies 90 % of the change it computes, which keeps the iterations stable.

The linear solvers, the routines that solve those sets of equations each cycle, keep their defaults, which are tuned for the incompressible equations; Incompressible solver lists them.

cycles
  • cycles: 500: the run stops after 500 cycles. Step 4 shows the residuals, the measure of how far the solution is from satisfying the equations, have fallen by more than six orders of magnitude, a factor of a million, by then.

See convergence control and Incompressible convergence control.

output settings

Sets what zCFD writes, and how often. See output settings.

report

The report is a table with one row per report cycle, written to cavity_OUTPUT/cavity/cavity_report.csv and plotted by zMon.

  • frequency: 1: a row every cycle. The file asks for no forces, so each row holds only the residuals: one for each component of momentum, rhoV[0], rhoV[1] and rhoV[2], and one for pressure, p.
solution

The flow solution, written for visualisation and post-processing.

  • format: "vtk": VTK HDF files, .vtkhdf, which ParaView opens.
  • surface variables: V and p, written to one file for each type of boundary condition.
  • volume variables: V, p and cell_centre, the coordinates of each cell centre, which Step 5 uses to pick out the cells along the centrelines.

No frequency is given, so the solution is written once, at the end of the run. Output variables lists the others.

model

One entry per mesh, each with its own boundary conditions. See model.

cavity

The name of the only model. It names the output folder, cavity_OUTPUT/cavity/, and the files in it.

mesh
  • mesh: "cavity.h5", the mesh file in zCFD's format, read from the case directory. Its boundary faces are grouped into numbered zones, which the boundary conditions refer to.
boundary conditions

Assigns a condition to each zone of boundary faces. See boundary conditions and Choosing boundary conditions.

BC_1

The lid.

  • zones: [14], the top face of the cavity.
  • type and kind: "wall", "no slip": the fluid at the wall moves with the wall.
  • velocity: {"type": "linear", "vector": [1.0, 0.0, 0.0]}: a translating wall, sliding at 1 m/s along x. A "rotating" velocity would spin it about an axis instead.
BC_2

The other three walls.

  • zones: [12], the bottom and the two sides.
  • type and kind: "wall", "no slip", with no velocity: the walls are still.
BC_3

The front and back faces.

  • zones: [11, 13].
  • type: "symmetry": a mirror plane. No flow passes through it, so the flow stays two-dimensional across the single cell of depth.

The cavity is closed, so this file has no inflow or outflow. For a flow through a domain, the incompressible solver needs an inflow and an outflow boundary rather than the farfield used by the compressible tutorials.

Step 3. Run

  1. Activate your environment:
source ./<ZCFD_INSTALL_PATH>/bin/activate
  1. Check the control file for errors:
cd ~/tutorial_6_lid_driven_cavity/
validate_input cavity.py
  1. Run zCFD in the case directory:
run_zcfd -c cavity.py

These are extracts from the log of a run of this tutorial, with ... marking lines left out:

Parameters validated successfully
...
 Reading file cavity.h5
 Global Mesh Stats: Cells: 16129 Faces: 64770
...
 Dimensional Conditions
 ----------------------
  Pressure:     101325 Pa
  Temperature:  273.15 K
  Density:      1.29251 kg/m^3
  Viscosity:    0.00323127 Ns/m^2 @ 273.15 K
  Speed:        1 m/s
...
  Reynolds No:  400 @ 273.15 K
...
Starting solve loop
...
Cycle     10 | Step    0 t=0s | CFL   1.000 | MG 1 |    16.6 ms | Total     0.212s
+--------------------------------------------------------------------------------------+
| Report: Cycle 10  Wall time: 0.212s                                                  |
...
| Residuals:                                                                           |
|   rhoV[0]  1.3464E-07  rhoV[1]  1.6890E-07  rhoV[2]  0.0000E+00  p        1.8761E-08 |
+--------------------------------------------------------------------------------------+
...
 Solve Complete: 500 cycles in 9.573s (avg 19.1 ms/cycle)
...
zCFD finished: all requested cycles are complete (500 in total).
  • The mesh has 16,129 cells, 127 × 127 across one cell of depth.
  • The dimensional conditions show the viscosity the Reynolds number set, 0.00323 Ns/m², for a lid speed of 1 m/s.
  • The report box lists three momentum residuals and the pressure residual. rhoV[2], across the depth, is exactly zero, because the flow has no velocity across the symmetry planes.
  • The run takes about fifteen seconds on an NVIDIA RTX 3060.

Output files

File/DirectoryDescription
cavity.logA copy of the output to screen during the run.
cavity_status.yamlA record of how the run was started: version, control file, mesh and devices.
cavity_OUTPUT/cavity/cavity_report.csvThe residuals at every cycle.
cavity_OUTPUT/cavity/cavity_results.h5The checkpoint: the flow solution in zCFD format, used to restart the run.
cavity_OUTPUT/cavity/cavity.vtkhdfThe volume output, for visualisation and post-processing.
cavity_OUTPUT/cavity/cavity_wall.vtkhdfThe surface output on the four walls.
cavity_OUTPUT/cavity/cavity_symmetry.vtkhdfThe surface output on the front and back symmetry planes.

Step 4. Monitor

Open the run in zMon to watch the residuals, while it runs or after it has finished:

zmon cavity.py

Monitoring a run describes each panel. The plot below is the residual panel as zMon draws it.

Residuals

A residual measures how far the current solution is from satisfying one of the equations; as the run converges, the residuals fall. The report holds the momentum residuals, named rhoU, rhoV and rhoW in zMon, and p, the pressure residual, which measures how well the flow conserves mass. rhoW is zero throughout, so it does not appear on the logarithmic scale.

Residuals of the cavity run against cycle
Residuals of the cavity run against cycle

The residuals fall steeply in the first few cycles, as the lid sets the fluid moving, then in steps as the vortex develops, and steadily from about cycle 350. All three fall by more than six orders of magnitude over the 500 cycles, and the solution is converged.

Step 5. Post-process

Python

First activate your zCFD environment, which provides Python with zutil, h5py and ParaView:

source <ZCFD_INSTALL_PATH>/bin/activate

Ghia, Ghia and Shin tabulate two profiles: the x velocity u along the vertical centreline, x = 0.5, and the y velocity v along the horizontal centreline, y = 0.5. The mesh has an odd number of cells across, so a column of cell centres lies exactly on each centreline, and the profiles can be read straight from the cells. This script reads the volume output, picks out those cells, and plots them against Ghia's values. Save it as compare_with_ghia.py beside the control file:

import h5py
import matplotlib.pyplot as plt
import numpy as np

with h5py.File("cavity_OUTPUT/cavity/cavity.vtkhdf", "r") as f:
    centre = f["VTKHDF/CellData/cell_centre"][:]
    velocity = f["VTKHDF/CellData/V"][:]

# The cells on the vertical centreline (x = 0.5), bottom to top,
# and on the horizontal centreline (y = 0.5), left to right
column = np.flatnonzero(np.isclose(centre[:, 0], 0.5, atol=1e-5))
column = column[np.argsort(centre[column, 1])]
row = np.flatnonzero(np.isclose(centre[:, 1], 0.5, atol=1e-5))
row = row[np.argsort(centre[row, 0])]

ghia = np.genfromtxt("ghia_re400.csv", delimiter=",", names=True)

fig, (left, right) = plt.subplots(1, 2, figsize=(10, 4.5))
left.plot(velocity[column, 0], centre[column, 1], label="zCFD")
left.plot(ghia["u"], ghia["y"], "o", mfc="none", label="Ghia et al. (1982)")
left.set_xlabel("u (m/s) at x = 0.5")
left.set_ylabel("y (m)")
right.plot(centre[row, 0], velocity[row, 1], label="zCFD")
right.plot(ghia["x"], ghia["v"], "o", mfc="none", label="Ghia et al. (1982)")
right.set_xlabel("x (m)")
right.set_ylabel("v (m/s) at y = 0.5")
right.legend()
fig.savefig("compare_with_ghia.png", dpi=200, bbox_inches="tight")

and run it:

python compare_with_ghia.py

It writes compare_with_ghia.png:

Centreline velocities from zCFD against Ghia et al. (1982)
Centreline velocities from zCFD against Ghia et al. (1982)

zCFD matches Ghia's values to within 0.01 m/s, 1 % of the lid speed, at every tabulated point. The minimum u on the vertical centreline is −0.33 m/s, near y = 0.27, where the main vortex carries the fluid back beneath its centre. Along the horizontal centreline, v runs from 0.30 m/s on the upward side of the vortex to −0.45 m/s on the downward side, close to the right-hand wall.

ParaView

Open cavity_OUTPUT/cavity/cavity_symmetry.vtkhdf in ParaView and click "Apply". The symmetry plane spans the whole cavity, so it shows the flow in a single view. Colour it by V, magnitude, to see the fast-moving layer beneath the lid. To see the vortices, add a "Stream Tracer" filter, which draws the paths the fluid follows, with a "Line" seed type across the cavity; the main vortex fills most of the cavity, with the two smaller corner vortices below it.

:::

Next steps

  • Raise the Reynolds number to 1000 in both reference conditions. Ghia also tabulates Re 1000 and above; the primary vortex moves towards the centre of the cavity. Check that the residuals have converged before you compare.
  • Change the coupling scheme to "simple", following Incompressible convergence control, and compare how many cycles each scheme needs to converge.

Citations

Ghia, U., Ghia, K. N. and Shin, C. T. (1982). High-Re solutions for incompressible flow using the Navier-Stokes equations and a multigrid method. Journal of Computational Physics, 48(3), 387–411.