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.

Lid-driven cavity

Lid-driven cavity

This example shows that the pymrm toolbox is flexible enough to simulate incompressible Navier-Stokes flow. The governing equations are the momentum balance and the continuity (incompressibility) constraint:

ρ(∂v∂t+v⋅∇v)=−∇p+μ∇2v,∇⋅v=0\rho \left( \frac{\partial \boldsymbol{v}}{\partial t} + \boldsymbol{v} \cdot \nabla \boldsymbol{v} \right) = -\nabla p + \mu \nabla^2 \boldsymbol{v}, \qquad \nabla \cdot \boldsymbol{v} = 0

The setup is the classic lid-driven cavity: a closed square (or cubic) box with no-slip walls. The top wall (the lid) moves tangentially at a constant velocity v_top; no fluid passes through any wall. Because the box is closed, the pressure has no boundary value of its own: the pressure correction gets a zero normal gradient on every wall, and the pressure is fixed up to a constant by pinning it in one cell. The flow is solved with a fractional-step (projection) method: a momentum predictor gives a velocity field that is not yet divergence-free, then a pressure-correction Poisson equation is solved and used to project the velocity back onto the divergence-free subspace.

The setup is quite involved because of the staggered mesh: pressure lives at cell centers, and each velocity component lives on its own staggered grid, shifted by half a cell in its own direction. A surprising finding is that in almost all cases the direct sparse solver is faster (or as fast) as ilu + bicgstab.

Imports

Physical and numerical parameters

Grid setup and boundary conditions

Pressure lives on a cell-centered grid; each velocity component v[i] lives on a grid staggered by half a cell along its own axis i (a face-centered grid in that direction, cell-centered in the others). Boundary conditions use the OUTWARD normal, so the same {'a', 'b', 'd'} dictionary means opposite gradients at the two ends of an axis. The lid velocity is imposed by setting bc_v[0][-1][1]['d'] = v_top: the x-velocity (component 0), along the last axis (dim - 1, pointing up), at its upper end (index 1). All other velocity conditions are no-slip, and the normal velocity on every wall is zero by construction: each velocity component is stored on the interior faces of its own axis only, and the wall values are padded with zeros. The pressure gets a zero normal gradient on all walls.

Operators: gradient, divergence, Laplacian, Jacobians, preconditioners

For pressure and for each velocity component, the gradient, divergence and Laplacian operators are assembled once per axis and kept as constant sparse matrices. The momentum Jacobian jac_mat_v[i] = (rho/dt) * eye - mu * lapl_mat_v[i] is the implicit-Euler operator for the momentum predictor; an ILU factorization of it is built once and reused as a preconditioner inside the time loop; the pressure Laplacian gets a direct LU factorization. With zero-gradient conditions on all walls the pressure Laplacian is singular (a constant can be added to the pressure); replacing the equation of one reference cell by dp = 0 removes the constant. The other equations still enforce continuity in that cell, because the divergences of a closed box sum to zero.

Time marching: projection method

The time loop marches to steady state: it stops when the largest velocity change in one step drops below steady_tol * v_top, and plots the pressure field and streamlines every plot_every steps. Each time step performs a fractional-step (projection) update:

  1. Momentum predictor: assemble the TVD-limited (minmod) convective flux and the pressure gradient from the previous step, then solve the implicit momentum equation for each velocity component with bicgstab, preconditioned by the ILU factorization built above.

  2. Pressure correction: solve a Poisson equation for the pressure correction dp from the divergence of the predicted velocity, using the direct sparse LU solve lapl_lu_p.solve(...).

  3. Velocity correction: project the velocity back onto the divergence-free subspace using the pressure-correction gradient.

<Figure size 640x480 with 2 Axes>
steady state after 2200 steps, t = 0.220

Checking the result

The projection step exists to make the velocity field satisfy the discrete continuity equation. Recompute the divergence of the final velocity in every pressure cell, with the zero wall values padded exactly as in the time loop, and compare it with the velocity scale v_top / box[0]. A relative value near round-off confirms the projection; a large value in a few cells points at an inconsistent boundary condition for the pressure correction (for example a fixed pressure on a wall through which no fluid can flow).

max |div v| / (v_top / L) = 1.66e-14
pressure in the reference cell: 2.23e-12 (p_ref = 0)

Comparison with Ghia, Ghia and Shin (1982)

The standard benchmark for this flow is the table of Ghia, Ghia and Shin (J. Comput. Phys. 48, 387-411, 1982): the horizontal velocity along the vertical centreline and the vertical velocity along the horizontal centreline, at steady state, computed on a fine multigrid solution. At Re = 100 the values below are from their Tables I and II. (Two neighbouring entries of the vertical-velocity table, at x = 0.9531 and x = 0.9453, appear in swapped order in some reproductions; they are given here in the order that makes the profile monotone.)

Both components are read on the staggered faces that lie exactly on the centrelines, padded with the wall values, and interpolated to the tabulated positions. The differences are scaled by the lid velocity; on this 50 x 50 grid they stay below 0.01.

Refining the grid shows that this remaining difference comes from the table, not from the model. On 100 x 100 and 200 x 200 grids the profiles change by at most 0.0028 and then 0.0006, converging at second order (observed orders 1.9 to 2.3 at the velocity extrema) to values that differ from the table by up to 0.009. For example, the minimum of the horizontal velocity at y = 0.4531 goes from -0.2112 (50 cells) to -0.2133 (100) and -0.2138 (200), with an extrapolated value of -0.2140, against -0.2109 in the table. Ghia’s values come from a 129 x 129 grid and underestimate the velocity extrema by 1 to 3 %. A sign or index error in the operators, or a flow that is not yet steady, shows up as far larger differences.

centreline faces at x = 0.500 and y = 0.500
max |u - u_Ghia| / v_top = 0.0033
max |v - v_Ghia| / v_top = 0.0069
<Figure size 1000x400 with 2 Axes>