877 words
4 minutes
Simulation of Flow over Cylinder

From: Ansys Fluent Rust Computational fluid dynamics

Code: https://github.com/A1i98/rust-cfd-cylinder-flow

Bind#

[[meta bind projects]]

Simulation in ANSYS#

Problem: 2D Flow Over a Circular Cylinder Flow type: Incompressible, laminar, unsteady Reynolds number: Re = 100 Solver type: Transient Navier-Stokes Domain length: L = 20 m Domain height: H = 10 m Cylinder diameter: D = 1 m Cylinder radius: R = 0.5 m Cylinder center: xc = 1 m yc = 5 m Density: rho = 1 Inlet velocity: U∞ = 1 Cylinder diameter: D = 1 Re = 100 mu = rho * U∞ * D / Re mu = 1 * 1 * 1 / 100 mu = 0.01 rho = 1 mu = 0.01 nu = mu / rho = 0.01 Simulation of Flow over Cylinder-1783619699272.webp

For full access, please contact me.

Simulation of Flow over Cylinder-1783625904719.webp Simulation of Flow over Cylinder-1783625975583.webp Simulation of Flow over Cylinder-1783626053148.webp

Simulation in Rust#

Case definition#

QuantityValue
Domain length, LL2525
Domain height, HH1010
Cylinder diameter, DD11
Cylinder radius, RR0.50.5
Cylinder center(xc,yc)=(5,5)(x_c,y_c)=(5,5)
Density, ρ\rho11
Free-stream velocity, UU_\infty11
Reynolds number, ReRe100100
Dynamic viscosity, μ\mu0.010.01
Kinematic viscosity, ν\nu0.010.01
Mesh500×200500\times200
Grid spacingΔx=Δy=0.05\Delta x=\Delta y=0.05
Physical time stepΔt=0.002\Delta t=0.002
End timetend=100t_{end}=100
Cylinder resolutionD/Δx=20D/\Delta x=20 cells
Blockage ratioD/H=0.1D/H=0.1

The Reynolds number is

Re=UDν=1×10.01=100.Re=\frac{U_\infty D}{\nu} =\frac{1\times1}{0.01}=100.

Expected flow physics#

At Re=100Re=100, flow past a circular cylinder is expected to be laminar but unsteady. The symmetric steady wake is unstable, and the developed solution should exhibit periodic vortex shedding known as the von Kármán vortex street.

Governing equations#

The incompressible Navier–Stokes equations are

u=0,\nabla\cdot\mathbf{u}=0,ut+uu=1ρp+ν2u.\frac{\partial\mathbf{u}}{\partial t} +\mathbf{u}\cdot\nabla\mathbf{u} =-\frac{1}{\rho}\nabla p +\nu\nabla^2\mathbf{u}.

The LBM solver recovers these equations in the low-Mach hydrodynamic limit.

D2Q9 lattice Boltzmann formulation#

The D2Q9 discrete velocities are

ci{(0,0),(1,0),(0,1),(1,0),(0,1),(1,1),(1,1),(1,1),(1,1)}.\mathbf{c}_i\in \left\{ (0,0),(1,0),(0,1),(-1,0),(0,-1), (1,1),(-1,1),(-1,-1),(1,-1) \right\}.

The weights are

w0=49,w14=19,w58=136.w_0=\frac{4}{9}, \qquad w_{1\ldots4}=\frac{1}{9}, \qquad w_{5\ldots8}=\frac{1}{36}.

The equilibrium distribution is

fieq=wiρ[1+3ciu+92(ciu)232uu].f_i^{eq}=w_i\rho \left[ 1+3\mathbf{c}_i\cdot\mathbf{u} +\frac{9}{2}(\mathbf{c}_i\cdot\mathbf{u})^2 -\frac{3}{2}\mathbf{u}\cdot\mathbf{u} \right].

Macroscopic density and velocity are reconstructed from

ρ=ifi,\rho=\sum_i f_i,ρu=ifici.\rho\mathbf{u}=\sum_i f_i\mathbf{c}_i.

The physical-to-lattice conversion gives

Ulu=UΔtΔx=1×0.0020.05=0.04,U_{lu}=U_\infty\frac{\Delta t}{\Delta x} =1\times\frac{0.002}{0.05}=0.04,νlu=νΔtΔx2=0.010.0020.052=0.008,\nu_{lu}=\nu\frac{\Delta t}{\Delta x^2} =0.01\frac{0.002}{0.05^2}=0.008,τ=12+3νlu=0.524.\tau=\frac{1}{2}+3\nu_{lu}=0.524.

TRT collision model#

The distribution and equilibrium functions are split into even and odd parts with respect to the opposite lattice direction iˉ\bar{i}:

fi+=fi+fiˉ2,fi=fifiˉ2.f_i^+=\frac{f_i+f_{\bar{i}}}{2}, \qquad f_i^-=\frac{f_i-f_{\bar{i}}}{2}.

The TRT collision step is

fi=fiω+(fi+fieq,+)ω(fifieq,).f_i^*=f_i -\omega_+\left(f_i^+-f_i^{eq,+}\right) -\omega_-\left(f_i^--f_i^{eq,-}\right).

The even relaxation rate controls viscosity:

ω+=1τ.\omega_+=\frac{1}{\tau}.

The odd rate is determined from the TRT magic parameter Λ\Lambda:

Λ=(1ω+12)(1ω12).\Lambda= \left(\frac{1}{\omega_+}-\frac{1}{2}\right) \left(\frac{1}{\omega_-}-\frac{1}{2}\right).

Using

Λ=316,\Lambda=\frac{3}{16},

produces approximately

ω+=1.908397,ω0.120301.\omega_+=1.908397, \qquad \omega_-\approx0.120301.
Why TRT is used here

TRT separates the viscous even modes from the odd kinetic modes. This significantly improves robustness for halfway bounce-back when τ\tau is close to 0.50.5.

TRT code section#

src/main.rs
let omega_plus = self.cfg.omega_plus();
let omega_minus = self.cfg.omega_minus();
for k in 0..Q {
let ko = OPP[k];
let f_plus = 0.5 * (self.f[base + k] + self.f[base + ko]);
let f_minus = 0.5 * (self.f[base + k] - self.f[base + ko]);
let eq_plus = 0.5 * (eq[k] + eq[ko]);
let eq_minus = 0.5 * (eq[k] - eq[ko]);
f_post[k] = self.f[base + k]
- omega_plus * (f_plus - eq_plus)
- omega_minus * (f_minus - eq_minus);
}

Boundary conditions#

BoundaryPhysical conditionNumerical implementation
Inletu=Uu=U_\infty, v=0v=0Zou/He velocity reconstruction
Outletp=0p=0, u/x=0\partial u/\partial x=0, v/x=0\partial v/\partial x=0inner populations copied and normalized to ρ=1\rho=1
Cylinderu=0u=0, v=0v=0halfway bounce-back
Top/bottomv=0v=0, u/y=0\partial u/\partial y=0specular reflection

Outlet implementation#

At the outlet, the distribution shape from the adjacent internal cell is copied and normalized:

fi(xout)=fi(xoutΔx)ρ(xoutΔx).f_i(x_{out})= \frac{f_i(x_{out}-\Delta x)} {\rho(x_{out}-\Delta x)}.

Therefore,

ρ(xout)=1,\rho(x_{out})=1,

while the velocity is inherited from the adjacent cell, approximating a zero streamwise gradient.

src/main.rs
let bi = self.base(nx - 2, j);
let rho_inner: f64 = (0..Q).map(|k| self.f[bi + k]).sum();
let scale = 1.0 / rho_inner;
for k in 0..Q {
self.f[b + k] = self.f[bi + k] * scale;
}

Symmetry-breaking perturbation#

A perfectly symmetric grid, centered cylinder, symmetric boundaries, and v=0v=0 initial field can preserve the unstable symmetric numerical branch. A small perturbation is therefore introduced behind the cylinder.

Version 3 uses a localized streamfunction perturbation:

ψ=Aσexp[(xx0)2+(yy0)22σ2].\psi'=A\sigma \exp\left[ -\frac{(x-x_0)^2+(y-y_0)^2}{2\sigma^2} \right].

Velocity perturbations are obtained from

u=ψy,v=ψx,u'=\frac{\partial\psi'}{\partial y}, \qquad v'=-\frac{\partial\psi'}{\partial x},

with the code sign convention chosen consistently. Because the perturbation comes from a streamfunction,

ux+vy=0.\frac{\partial u'}{\partial x} +\frac{\partial v'}{\partial y}=0.

This breaks wake symmetry without creating a strong compressibility pulse.

src/main.rs
let g = (-((x - x0).powi(2) + (y - y0).powi(2))
/ (2.0 * sigma2))
.exp();
let psi = amp * sigma * g;
let du = -(y - y0) * psi / sigma2;
let dv = (x - x0) * psi / sigma2;
let ux = u_lu + du;
let uy = dv;

Force coefficients#

The momentum-exchange method calculates the force on the cylinder during bounce-back.

Cd=Fx12ρU2D,Cl=Fy12ρU2D.C_d=\frac{F_x}{\frac{1}{2}\rho U_\infty^2D}, \qquad C_l=\frac{F_y}{\frac{1}{2}\rho U_\infty^2D}.

In lattice units, the denominator is

12ρluUlu2Dlu.\frac{1}{2}\rho_{lu}U_{lu}^2D_{lu}.

Version 3 averages force over each output_every interval:

Fx=1Nn=1NFx(n),Fy=1Nn=1NFy(n).\overline{F}_x=\frac{1}{N}\sum_{n=1}^{N}F_x^{(n)}, \qquad \overline{F}_y=\frac{1}{N}\sum_{n=1}^{N}F_y^{(n)}.

This reduces high-frequency lattice noise without suppressing the physical shedding frequency.

Running the solver#

Clean short test#

Terminal
rm -rf results_test
cargo run --release -- \
--out results_test \
--t-end 4 \
--output-every 100 \
--print-every 500 \
--frame-every 500 \
--diagnostic-every 100 \
--perturb 1e-3

Full simulation#

Terminal
rm -rf results
cargo run --release -- \
--out results \
--output-every 100 \
--print-every 1000 \
--frame-every 1000 \
--diagnostic-every 100 \
--perturb 1e-3

Important runtime parameters#

ArgumentDefaultPurpose
--output-every100CSV force/probe interval
--print-every1000terminal log interval
--frame-every1000BMP animation interval
--diagnostic-every100finite-value and stability check interval
--perturb1e-3divergence-free wake seed amplitude
--trt-lambda0.1875TRT magic parameter 3/163/16

Output files#

case_summary.txt
field.csv
field_tecplot.dat
force_coefficients.csv
probe_wake.csv
wake_centerline_y5.csv
vertical_profile_x10.csv
frames/speed_00000.bmp
frames/speed_00001.bmp
frames/vorticity_00000.bmp
frames/vorticity_00001.bmp
images/speed.svg
images/u_velocity.svg
images/v_velocity.svg
images/pressure.svg
images/vorticity.svg
images/streamfunction.svg
images/force_coefficients.svg
images/wake_v_probe.svg

Animation workflow#

The signed vorticity field is the clearest visualization of alternating shedding.

Terminal
ffmpeg -framerate 10 -i results/frames/vorticity_%05d.bmp \
-c:v libx264 -pix_fmt yuv420p vorticity.mp4

Speed animation:

Terminal
ffmpeg -framerate 10 -i results/frames/speed_%05d.bmp \
-c:v libx264 -pix_fmt yuv420p speed.mp4
Frame interpretation

speed_00000.bmp and vorticity_00000.bmp represent the initial field. Subsequent files represent intervals of frame_every solver steps.

Plots#

Speed magnitude#

Simulation of Flow over Cylinder-1783670209609.webp

Streamwise velocity#

Simulation of Flow over Cylinder-1783670238994.webp

Transverse velocity#

Simulation of Flow over Cylinder-1783670245088.webp

Relative pressure#

Simulation of Flow over Cylinder-1783670254605.webp

Vorticity field#

Simulation of Flow over Cylinder-1783670262719.webp

Streamfunction diagnostic#

Simulation of Flow over Cylinder-1783670230363.webp

Drag and lift coefficients#

Simulation of Flow over Cylinder-1783670277232.webp

Wake transverse-velocity probes#

Simulation of Flow over Cylinder-1783670290900.webp

Simulation of Flow over Cylinder
https://jamshidzadeh.ir/posts/projects/simulation-of-flow-over-cylinder/
Author
Ali Jamshidzadeh
Published at
2026-07-09