1708 words
9 minutes
Lid Driven Cavity Simulation

From: Ansys Fluent Rust Computational fluid dynamics

Code: https://github.com/A1i98/rust-cfd-lid-driven-cavity

Bind#

[[meta bind projects]]

Tasks#

Sessions BUTTON[add-session] BUTTON[open-latest-session]#

Simulation in ANSYS#

A two-dimensional square cavity with dimensions of 1 m × 1 m was created in ANSYS DesignModeler to represent the computational domain for the lid-driven cavity simulation.

Lid Driven Cavity Simulation in ANSYS Fluent-1783243704919.webp

After the sketch was converted into a surface body, the resulting geometry was assigned the Fluid body type. This step defines the computational domain as a fluid region, allowing ANSYS Fluent to generate the control volumes required for solving the governing flow equations.

Lid Driven Cavity Simulation in ANSYS Fluent-1783248425439.webp A structured quadrilateral mesh was generated with a global element size of 0.01 m. Local mesh refinement was then applied to improve spatial resolution in regions where higher velocity gradients are expected. Finally, appropriate Named Selections were created for all boundaries, including the moving wall (lid) and stationary walls, to facilitate the assignment of boundary conditions in Fluent.

Lid Driven Cavity Simulation in ANSYS Fluent-1783249314993.webp

For full access, please contact me.

Lid Driven Cavity Simulation in ANSYS Fluent-1783253543118.webp Lid Driven Cavity Simulation in ANSYS Fluent-1783254177082.webp Lid Driven Cavity Simulation in ANSYS Fluent-1783253810384.webp

Simulation in Rust#

Purpose

This note documents a complete 2D lid-driven cavity flow solver written in Rust. The solver uses a finite-difference staggered layout and an artificial-compressibility pseudo-time iteration to obtain the steady incompressible solution. It also writes CSV, Tecplot, and SVG plot files automatically.

Problem Definition#

The classical lid-driven cavity is a square 2D domain of side length L=1L = 1. The top wall moves horizontally with velocity U=1U = 1, while the other three walls are stationary. The flow is assumed incompressible, laminar, Newtonian, and two-dimensional.

The default case solved by the Rust program is:

ParameterSymbolDefault value
Domain lengthLL11
Lid velocityUU11
Reynolds numberReRe100100
Grid sizeN×NN \times N65×6565 \times 65
Pseudo-time stepΔt\Delta t0.0010.001
Artificial-compressibility parameterδ\delta4.54.5
Physical meaning

The final result is the steady-state incompressible cavity flow. The artificial-compressibility time variable is a pseudo-time used for convergence, not necessarily the physical transient time.


Governing Equations#

For a 2D incompressible Newtonian flow, the dimensional governing equations are the continuity equation and the Navier-Stokes momentum equations:

ux+vy=0\frac{\partial u}{\partial x}+\frac{\partial v}{\partial y}=0ut+uux+vuy=1ρpx+ν(2ux2+2uy2)\frac{\partial u}{\partial t} +u\frac{\partial u}{\partial x} +v\frac{\partial u}{\partial y} =-\frac{1}{\rho}\frac{\partial p}{\partial x} +\nu\left(\frac{\partial^2u}{\partial x^2}+\frac{\partial^2u}{\partial y^2}\right)vt+uvx+vvy=1ρpy+ν(2vx2+2vy2)\frac{\partial v}{\partial t} +u\frac{\partial v}{\partial x} +v\frac{\partial v}{\partial y} =-\frac{1}{\rho}\frac{\partial p}{\partial y} +\nu\left(\frac{\partial^2v}{\partial x^2}+\frac{\partial^2v}{\partial y^2}\right)

Non-Dimensional Form#

Using LL, UU, and ρU2\rho U^2 as reference scales, the equations become:

ux+vy=0\frac{\partial u}{\partial x}+\frac{\partial v}{\partial y}=0ut+uux+uvy=px+1Re(2ux2+2uy2)\frac{\partial u}{\partial t} +\frac{\partial uu}{\partial x} +\frac{\partial uv}{\partial y} =-\frac{\partial p}{\partial x} +\frac{1}{Re}\left(\frac{\partial^2u}{\partial x^2}+\frac{\partial^2u}{\partial y^2}\right)vt+uvx+vvy=py+1Re(2vx2+2vy2)\frac{\partial v}{\partial t} +\frac{\partial uv}{\partial x} +\frac{\partial vv}{\partial y} =-\frac{\partial p}{\partial y} +\frac{1}{Re}\left(\frac{\partial^2v}{\partial x^2}+\frac{\partial^2v}{\partial y^2}\right)

where:

Re=ULνRe=\frac{UL}{\nu}

The Rust code solves the steady incompressible problem through pseudo-time marching. For this reason, the continuity equation is replaced during the iterative process by the artificial-compressibility equation:

1δpt+ux+vy=0\frac{1}{\delta}\frac{\partial p}{\partial t} +\frac{\partial u}{\partial x} +\frac{\partial v}{\partial y}=0

At convergence, p/t0\partial p/\partial t \rightarrow 0, so the incompressibility constraint is recovered:

ux+vy0\frac{\partial u}{\partial x}+\frac{\partial v}{\partial y}\rightarrow 0

Boundary Conditions#

The cavity walls use no-penetration and no-slip boundary conditions.

BoundaryVelocity condition
Top wall / moving lidu=Uu=U, v=0v=0
Bottom wallu=0u=0, v=0v=0
Left wallu=0u=0, v=0v=0
Right wallu=0u=0, v=0v=0

The pressure uses a zero-normal-gradient condition at the walls:

pn=0\frac{\partial p}{\partial n}=0

Because incompressible pressure is defined up to an arbitrary constant, the code subtracts the mean pressure after every pressure update.


Numerical Method#

The code uses a staggered finite-difference arrangement similar to a MAC grid:

QuantityStorage ideaPurpose
uuhorizontal velocity stored on vertical control-volume facesreduces pressure-velocity decoupling
vvvertical velocity stored on horizontal control-volume facesimproves discrete continuity coupling
pppressure-like field at pressure nodesenforces incompressibility through pseudo-time correction
ucu_c, vcv_c, pcp_ccell-centered post-processed valuesexported for plotting and comparison
Why staggered storage is useful

On a collocated grid, pressure and velocity can decouple if the interpolation is not handled carefully. The staggered layout naturally couples pressure gradients and mass fluxes.


Staggered Grid Arrangement#

For a grid size n, the Rust solver stores the fields as follows:

FieldRust vector sizeLogical index range
un * (n + 1)i=0..n1i=0..n-1, j=0..nj=0..n
v(n + 1) * ni=0..ni=0..n, j=0..n1j=0..n-1
p(n + 1) * (n + 1)i=0..ni=0..n, j=0..nj=0..n

The grid spacing is:

Δx=Δy=1n1\Delta x=\Delta y=\frac{1}{n-1}

The exported cell-centered values are computed as:

uc(i,j)=12[u(i,j)+u(i,j+1)]u_c(i,j)=\frac{1}{2}\left[u(i,j)+u(i,j+1)\right]vc(i,j)=12[v(i,j)+v(i+1,j)]v_c(i,j)=\frac{1}{2}\left[v(i,j)+v(i+1,j)\right]pc(i,j)=14[p(i,j)+p(i+1,j)+p(i,j+1)+p(i+1,j+1)]p_c(i,j)=\frac{1}{4}\left[p(i,j)+p(i+1,j)+p(i,j+1)+p(i+1,j+1)\right]

Discrete Equations Used in the Code#

X-Momentum Equation#

The continuous non-dimensional x-momentum equation is:

ut+uux+uvy=px+1Re2u\frac{\partial u}{\partial t} +\frac{\partial uu}{\partial x} +\frac{\partial uv}{\partial y} =-\frac{\partial p}{\partial x} +\frac{1}{Re}\nabla^2u

The pseudo-time finite-difference update used in the code is:

ui,jnew=ui,jΔt[ui+1,j2ui1,j22Δx+(uv)n(uv)sΔy+pi+1,jpi,jΔx]+ΔtRe[ui+1,j2ui,j+ui1,jΔx2+ui,j+12ui,j+ui,j1Δy2]u^{new}_{i,j}=u_{i,j} -\Delta t\left[ \frac{u^2_{i+1,j}-u^2_{i-1,j}}{2\Delta x} +\frac{(uv)_n-(uv)_s}{\Delta y} +\frac{p_{i+1,j}-p_{i,j}}{\Delta x} \right] +\frac{\Delta t}{Re}\left[ \frac{u_{i+1,j}-2u_{i,j}+u_{i-1,j}}{\Delta x^2} +\frac{u_{i,j+1}-2u_{i,j}+u_{i,j-1}}{\Delta y^2} \right]

where:

(uv)n=14(ui,j+ui,j+1)(vi,j+vi+1,j)(uv)_n=\frac{1}{4}(u_{i,j}+u_{i,j+1})(v_{i,j}+v_{i+1,j})(uv)s=14(ui,j+ui,j1)(vi,j1+vi+1,j1)(uv)_s=\frac{1}{4}(u_{i,j}+u_{i,j-1})(v_{i,j-1}+v_{i+1,j-1})

Y-Momentum Equation#

The y-momentum equation is:

vt+uvx+vvy=py+1Re2v\frac{\partial v}{\partial t} +\frac{\partial uv}{\partial x} +\frac{\partial vv}{\partial y} =-\frac{\partial p}{\partial y} +\frac{1}{Re}\nabla^2v

The pseudo-time update used in the code is:

vi,jnew=vi,jΔt[(uv)e(uv)wΔx+vi,j+12vi,j122Δy+pi,j+1pi,jΔy]+ΔtRe[vi+1,j2vi,j+vi1,jΔx2+vi,j+12vi,j+vi,j1Δy2]v^{new}_{i,j}=v_{i,j} -\Delta t\left[ \frac{(uv)_e-(uv)_w}{\Delta x} +\frac{v^2_{i,j+1}-v^2_{i,j-1}}{2\Delta y} +\frac{p_{i,j+1}-p_{i,j}}{\Delta y} \right] +\frac{\Delta t}{Re}\left[ \frac{v_{i+1,j}-2v_{i,j}+v_{i-1,j}}{\Delta x^2} +\frac{v_{i,j+1}-2v_{i,j}+v_{i,j-1}}{\Delta y^2} \right]

where:

(uv)e=14(ui,j+ui,j+1)(vi,j+vi+1,j)(uv)_e=\frac{1}{4}(u_{i,j}+u_{i,j+1})(v_{i,j}+v_{i+1,j})(uv)w=14(ui1,j+ui1,j+1)(vi,j+vi1,j)(uv)_w=\frac{1}{4}(u_{i-1,j}+u_{i-1,j+1})(v_{i,j}+v_{i-1,j})

Artificial-Compressibility Pressure Update#

The discrete pressure update is:

pi,jnew=pi,jΔtδ[ui,jnewui1,jnewΔx+vi,jnewvi,j1newΔy]p^{new}_{i,j}=p_{i,j} -\Delta t\,\delta\left[ \frac{u^{new}_{i,j}-u^{new}_{i-1,j}}{\Delta x} +\frac{v^{new}_{i,j}-v^{new}_{i,j-1}}{\Delta y} \right]

The convergence criterion is based mainly on the mean absolute divergence:

ϵdiv=1Ninti,jui,jui1,jΔx+vi,jvi,j1Δy\epsilon_{div}=\frac{1}{N_{int}}\sum_{i,j} \left| \frac{u_{i,j}-u_{i-1,j}}{\Delta x} +\frac{v_{i,j}-v_{i,j-1}}{\Delta y} \right|

The default stopping criterion is:

ϵdiv<108\epsilon_{div}<10^{-8}

Rust Code#

Configuration and CLI Arguments#

The solver can be controlled from the terminal without editing the source file.

src/main.rs
#[derive(Debug, Clone)]
struct Config {
n: usize,
re: f64,
lid_velocity: f64,
dt: f64,
delta: f64,
max_steps: usize,
min_steps: usize,
tolerance: f64,
report_every: usize,
out_dir: PathBuf,
stream_sor_omega: f64,
stream_max_iter: usize,
stream_tol: f64,
}

The most important parameters are n, re, dt, delta, and tolerance.

Staggered Indexing#

The arrays are one-dimensional Vec<f64> buffers, but the code maps them to 2D staggered indices.

src/main.rs
fn iu(&self, i: usize, j: usize) -> usize {
// u: i = 0..n-1, j = 0..n
j * self.cfg.n + i
}
fn iv(&self, i: usize, j: usize) -> usize {
// v: i = 0..n, j = 0..n-1
j * (self.cfg.n + 1) + i
}
fn ip(&self, i: usize, j: usize) -> usize {
// p: i = 0..n, j = 0..n
j * (self.cfg.n + 1) + i
}

X-Momentum Update#

This section applies convection, pressure gradient, and viscous diffusion for the horizontal velocity.

src/main.rs
let u_ij = self.u[self.iu(i, j)];
let duu_dx = (self.u[self.iu(i + 1, j)].powi(2)
- self.u[self.iu(i - 1, j)].powi(2))
/ (2.0 * dx);
let uv_n = 0.25
* (self.u[self.iu(i, j)] + self.u[self.iu(i, j + 1)])
* (self.v[self.iv(i, j)] + self.v[self.iv(i + 1, j)]);
let uv_s = 0.25
* (self.u[self.iu(i, j)] + self.u[self.iu(i, j - 1)])
* (self.v[self.iv(i, j - 1)] + self.v[self.iv(i + 1, j - 1)]);
let duv_dy = (uv_n - uv_s) / dy;
let dp_dx = (self.p[self.ip(i + 1, j)] - self.p[self.ip(i, j)]) / dx;
let lap_u = (self.u[self.iu(i + 1, j)] - 2.0 * u_ij + self.u[self.iu(i - 1, j)]) / dx2
+ (self.u[self.iu(i, j + 1)] - 2.0 * u_ij + self.u[self.iu(i, j - 1)]) / dy2;
let new_u = u_ij - dt * (duu_dx + duv_dy + dp_dx) + dt * inv_re * lap_u;

Y-Momentum Update#

This section is the analogous update for vertical velocity.

src/main.rs
let uv_e = 0.25
* (self.u[self.iu(i, j)] + self.u[self.iu(i, j + 1)])
* (self.v[self.iv(i, j)] + self.v[self.iv(i + 1, j)]);
let uv_w = 0.25
* (self.u[self.iu(i - 1, j)] + self.u[self.iu(i - 1, j + 1)])
* (self.v[self.iv(i, j)] + self.v[self.iv(i - 1, j)]);
let duv_dx = (uv_e - uv_w) / dx;
let dvv_dy = (self.v[self.iv(i, j + 1)].powi(2)
- self.v[self.iv(i, j - 1)].powi(2))
/ (2.0 * dy);
let dp_dy = (self.p[self.ip(i, j + 1)] - self.p[self.ip(i, j)]) / dy;
let lap_v = (self.v[self.iv(i + 1, j)] - 2.0 * v_ij + self.v[self.iv(i - 1, j)]) / dx2
+ (self.v[self.iv(i, j + 1)] - 2.0 * v_ij + self.v[self.iv(i, j - 1)]) / dy2;
let new_v = v_ij - dt * (duv_dx + dvv_dy + dp_dy) + dt * inv_re * lap_v;

Pressure and Continuity Update#

The pressure equation drives the divergence toward zero.

src/main.rs
for i in 1..=(n - 1) {
for j in 1..=(n - 1) {
let divergence = (self.un[self.iu(i, j)] - self.un[self.iu(i - 1, j)]) / dx
+ (self.vn[self.iv(i, j)] - self.vn[self.iv(i, j - 1)]) / dy;
let idx = self.ip(i, j);
self.pn[idx] = self.p[idx] - dt * delta * divergence;
}
}

Boundary Conditions#

The top lid is imposed through a ghost-cell relation. For the moving lid:

utopghost+utopinside2=U\frac{u_{top\,ghost}+u_{top\,inside}}{2}=U

therefore:

utopghost=2Uutopinsideu_{top\,ghost}=2U-u_{top\,inside}
src/main.rs
for i in 0..n {
let bottom = self.iu(i, 0);
let bottom_inside = self.iu(i, 1);
let top = self.iu(i, n);
let top_inside = self.iu(i, n - 1);
self.un[bottom] = -self.un[bottom_inside];
self.un[top] = 2.0 * self.cfg.lid_velocity - self.un[top_inside];
}

Post-Processing Fields#

The solver computes vorticity as:

ω=vxuy\omega=\frac{\partial v}{\partial x}-\frac{\partial u}{\partial y}

It also computes a streamfunction from:

2ψ=ω\nabla^2\psi=-\omega

The post-processing step writes the final fields, centerline profiles, and plots.

How to Run#

1. Build in Release Mode#

Terminal
cargo build --release

2. Run the Default Case#

Terminal
cargo run --release -- --n 65 --re 100 --dt 0.001 --delta 4.5 --out results

Lid Driven Cavity Simulation-1783534125409.webp

3. Quick Test Case#

For a fast check before the final run:

Terminal
cargo run --release -- --n 33 --re 100 --max-steps 30000 --out quick_results

4. Useful Parameter Changes#

GoalSuggested command option
Finer grid--n 129
Lower Reynolds number--re 10
Higher Reynolds number--re 400
More strict convergence--tol 1e-9
Faster rough run--tol 1e-6 --n 33
Change output folder--out my_results
Stability warning

For larger Reynolds numbers or finer grids, reduce --dt. A safe first adjustment is to halve dt when the residual oscillates or grows.


Output Files#

After running, the solver creates an output folder such as results/.

FileDescription
run_config.txtSimulation parameters and final step
convergence.csvIteration history: divergence and velocity change
field.csvFull field data: x,y,u,v,p,speed,vorticity,streamfunction
field_tecplot.datTecplot-compatible field file
centerline_u_vertical.csvuu profile along x=0.5x=0.5
centerline_v_horizontal.csvvv profile along y=0.5y=0.5
images/u_velocity.svgSVG heatmap for uu
images/v_velocity.svgSVG heatmap for vv
images/speed.svgSVG heatmap for velocity magnitude
images/pressure.svgSVG heatmap for pressure
images/vorticity.svgSVG heatmap for vorticity
images/streamfunction.svgSVG heatmap for streamfunction
images/centerline_u_vertical.svgCenterline uu profile plot
images/centerline_v_horizontal.svgCenterline vv profile plot
images/convergence.svgConvergence history plot

Plot#

Velocity Magnitude#

Lid Driven Cavity Simulation-1783534250339.webp

Figure 1. Velocity magnitude field.

Horizontal Velocity Contour#

Lid Driven Cavity Simulation-1783534310294.webp

Figure 2. Horizontal velocity field.

Vertical Velocity Contour#

Lid Driven Cavity Simulation-1783534316065.webp

Figure 3. Vertical velocity field.

Pressure Field#

Lid Driven Cavity Simulation-1783534330264.webp

Figure 4. Pressure field from artificial-compressibility iteration.

Vorticity Field#

Lid Driven Cavity Simulation-1783534341066.webp

Figure 5. Vorticity field.

Streamfunction Field#

Lid Driven Cavity Simulation-1783534349156.webp

Figure 6. Streamfunction field.

Vertical Centerline U Profile#

Lid Driven Cavity Simulation-1783534376199.webp

Figure 7. uu velocity profile along x=0.5x=0.5.

Horizontal Centerline V Profile#

Lid Driven Cavity Simulation-1783534383976.webp

Figure 8. vv velocity profile along y=0.5y=0.5.

Convergence History#

Lid Driven Cavity Simulation-1783534392017.webp

Figure 9. Convergence history based on mean absolute divergence.


Verification and Comparison with ANSYS Fluent#

For full access, please contact me.

Numerical Stability Notes#

The explicit pseudo-time update is simple and transparent, but stability depends on grid size, Reynolds number, and pseudo-time step.

A conservative starting point is:

Δtmin(0.5ΔxU,0.25ReΔx2)\Delta t \lesssim \min\left(0.5\frac{\Delta x}{U},\,0.25Re\Delta x^2\right)

For the default case:

Δx=164=0.015625\Delta x=\frac{1}{64}=0.0156250.25ReΔx20.00610.25Re\Delta x^2 \approx 0.0061

The default Δt=0.001\Delta t=0.001 is therefore conservative for Re=100Re=100.

Higher-Reynolds-number warning

At higher Reynolds numbers, the cavity develops thinner boundary layers and stronger gradients. Use a finer grid and reduce dt. For example, try --n 129 --dt 0.0005 as a starting point for Re=400Re=400.

References#

  • Ghia, U., Ghia, K. N., & 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.
  • Peyret, R., & Taylor, T. D. (1983). Computational Methods for Fluid Flow. Springer.
  • Ferziger, J. H., Perić, M., & Street, R. L. (2020). Computational Methods for Fluid Dynamics. Springer.
  • Patankar, S. V. (1980). Numerical Heat Transfer and Fluid Flow. Hemisphere Publishing.
Lid Driven Cavity Simulation
https://jamshidzadeh.ir/posts/projects/lid-driven-cavity-simulation/
Author
Ali Jamshidzadeh
Published at
2026-07-05