| name | fluid-dynamics |
| description | Computational fluid dynamics — Navier-Stokes solvers, lid-driven cavity, channel flow, vortex methods, turbulence statistics, drag/lift computation. Spectral and finite-difference methods for incompressible and compressible flows. |
| category | physics |
| version | 1.0.0 |
| author | Synthetic Sciences |
| license | MIT |
| tags | ["CFD","Navier-Stokes","Fluid Dynamics","Turbulence","Incompressible Flow"] |
| dependencies | ["scipy>=1.11.0","numpy>=1.24.0","matplotlib>=3.7.0"] |
Fluid Dynamics (CFD)
Overview
Solve the Navier-Stokes equations for incompressible and compressible flows. Covers lid-driven cavity, channel flow, flow past obstacles, and turbulence analysis. For pseudospectral methods on periodic domains, also consider the fluidsim skill.
When to Use
- Incompressible flow simulations (lid-driven cavity, channel flow, jets)
- Computing drag and lift on bodies
- Turbulence statistics (energy spectrum, Reynolds stresses)
- Vortex dynamics and wake analysis
- Flow visualization (streamlines, vorticity fields)
Do NOT Use When
- Periodic-domain turbulence at high resolution (use
fluidsim — optimized pseudospectral)
- Compressible flow with shocks (need specialized Riemann solvers)
- Complex 3D geometries with unstructured meshes (use FEniCS or OpenFOAM)
Core Workflows
1. 2D Lid-Driven Cavity (Incompressible, Vorticity-Streamfunction)
import numpy as np
import matplotlib.pyplot as plt
def lid_driven_cavity(N=64, Re=100, dt=0.001, n_steps=50000):
"""
2D lid-driven cavity using vorticity-streamfunction formulation.
∂ω/∂t + u·∇ω = (1/Re)∇²ω
∇²ψ = -ω
u = ∂ψ/∂y, v = -∂ψ/∂x
"""
dx = 1.0 / (N - 1)
x = np.linspace(0, 1, N)
y = np.linspace(0, 1, N)
omega = np.zeros((N, N))
psi = np.zeros((N, N))
U_lid = 1.0
CFL = U_lid * dt / dx
diff_num = dt / (Re * dx**2)
print(f"Grid: {N}x{N}, Re={Re}, dt={dt}")
print(f"CFL = {CFL:.4f}, Diffusion number = {diff_num:.4f}")
if CFL > 0.5 or diff_num > 0.25:
print("WARNING: stability may be marginal")
for step in range(n_steps):
for _ in range(50):
psi[:-, :-] = * (
psi[:, :-] + psi[:-, :-] +
psi[:-, :] + psi[:-, :-] +
dx** * omega[:-, :-]
)
psi[, :] = ; psi[-, :] =
psi[:, ] = ; psi[:, -] =
u = np.zeros((N, N))
v = np.zeros((N, N))
u[:-, :-] = (psi[:-, :] - psi[:-, :-]) / (*dx)
v[:-, :-] = -(psi[:, :-] - psi[:-, :-]) / (*dx)
u[-, :] = U_lid
omega[, :-] = -*psi[, :-] / dx**
omega[-, :-] = -*psi[-, :-] / dx** - *U_lid/dx
omega[:-, ] = -*psi[:-, ] / dx**
omega[:-, -] = -*psi[:-, -] / dx**
domega_dx = (omega[:, :-] - omega[:-, :-]) / (*dx)
domega_dy = (omega[:-, :] - omega[:-, :-]) / (*dx)
laplacian = (omega[:, :-] + omega[:-, :-] +
omega[:-, :] + omega[:-, :-] -
*omega[:-, :-]) / dx**
omega[:-, :-] += dt * (
-u[:-, :-] * domega_dx
-v[:-, :-] * domega_dy
+ laplacian / Re
)
(step+) % == :
max_div = np.(np.(
(u[:-, :] - u[:-, :-])/(*dx) +
(v[:, :-] - v[:-, :-])/(*dx)
))
()
x, y, u, v, omega, psi
x, y, u, v, omega, psi = lid_driven_cavity(N=, Re=, n_steps=)
X, Y = np.meshgrid(x, y)
fig, axes = plt.subplots(, , figsize=(, ))
ax = axes[]
cs = ax.contour(X, Y, psi.T, levels=, cmap=)
ax.set_title()
ax.set_xlabel(); ax.set_ylabel()
ax.set_aspect()
plt.colorbar(cs, ax=ax)
ax = axes[]
cf = ax.contourf(X, Y, omega.T, levels=, cmap=)
ax.set_title()
ax.set_xlabel(); ax.set_ylabel()
ax.set_aspect()
plt.colorbar(cf, ax=ax)
ax = axes[]
skip =
ax.quiver(X[::skip, ::skip], Y[::skip, ::skip],
u.T[::skip, ::skip], v.T[::skip, ::skip], scale=)
ax.set_title()
ax.set_xlabel(); ax.set_ylabel()
ax.set_aspect()
plt.suptitle(, fontsize=)
plt.tight_layout()
plt.savefig(, dpi=, bbox_inches=)
2. Centerline Velocity Profiles (Ghia Benchmark)
j_center = len(x) // 2
u_centerline = u[j_center, :]
fig, ax = plt.subplots(figsize=(6, 8))
ax.plot(u_centerline, y, 'b-', linewidth=2, label='Computed')
ax.set_xlabel('u')
ax.set_ylabel('y')
ax.set_title(f'Vertical centerline velocity (Re={100})')
ax.legend()
ax.grid(True, alpha=0.3)
plt.savefig('centerline.png', dpi=150, bbox_inches='tight')
3. Flow Past a Cylinder (Immersed Boundary)
def cylinder_flow_simple(Nx=200, Ny=100, Re=100, n_steps=10000):
"""
Simplified 2D flow past a cylinder using penalty method.
For production use, consider lattice Boltzmann or FEniCS.
"""
dx = 1.0 / Ny
dt = 0.1 * dx / 1.0
u = np.ones((Nx, Ny))
v = np.zeros((Nx, Ny))
cx, cy = Nx//4, Ny//2
R = Ny // 10
Y_grid, X_grid = np.meshgrid(np.arange(Ny), np.arange(Nx))
mask = ((X_grid - cx)**2 + (Y_grid - cy)**2) < R**2
for step in range(n_steps):
u[mask] = 0
v[mask] = 0
return u, v, mask
Flow Regime Reference
| Re | Flow Type | Characteristics |
|---|
| Re < 1 | Stokes (creeping) | Reversible, no inertia |
| 1 < Re < 40 | Steady laminar | Attached flow, twin vortices |
| 40 < Re < 200 | Periodic (Von Karman) | Vortex shedding |
| 200 < Re < 10⁵ | Turbulent wake | Broad spectrum |
| Re > 10⁵ | Fully turbulent | Boundary layer transition |
Validation Checklist
Troubleshooting
| Symptom | Fix |
|---|
| Solution blows up | CFL too large, reduce dt |
| Checkerboard pattern | Pressure-velocity decoupling — use staggered grid |
| Poisson solver slow | Use SOR (ω ≈ 1.5-1.9) or FFT-based solver |
| Wrong drag coefficient | Insufficient resolution near body surface |