Explicit incompressible Navier-Stokes solver written in C.
Solves the 2D lid-driven cavity problem using the vorticity-streamfunction formulation, which eliminates pressure from the equations and enforces incompressibility exactly.
Vorticity field for the lid-driven cavity at Re=1000.
The governing equations are:
∂ω/∂t + u·∂ω/∂x + v·∂ω/∂y = (1/Re) ∇²ω (vorticity transport)
∇²ψ = −ω (Poisson equation)
u = ∂ψ/∂y, v = −∂ψ/∂x (velocity recovery)
ω = ∂v/∂x − ∂u/∂y (vorticity definition)
At each timestep:
- Compute vorticity boundary conditions from current velocity field
- Evaluate spatial derivatives of ω using finite differences
- Advance ω in time (Euler or RK4)
- Solve the Poisson equation for ψ
- Recover u and v from ψ
- Spatial discretisation: finite differences of selectable order (2nd, 4th, or 6th)
- Time integration: explicit Euler or classical RK4 (4th-order accurate)
- Poisson solver: three options — Gauss-Seidel, SOR, or FFTW3-based direct DST-I solver (default)
- Sparse operators: 2D derivative operators built as CSR sparse matrices via Kronecker products, replacing dense O(n³) matrix-vector multiplies with O(7n) SpMV
- Output: VTK files for visualisation in ParaView
gcclibfftw3— required for the FFT Poisson solver- OpenMP — optional; used only if you build with
make OPENMP=1(see below)
Ubuntu/Debian
sudo apt install libfftw3-devFor parallel builds, install a full GCC toolchain (OpenMP comes with GCC as libgomp; nothing extra beyond the compiler is usually required):
sudo apt install build-essentialArch Linux
sudo pacman -S fftwGCC on Arch already includes OpenMP support for make OPENMP=1.
macOS
brew install fftwApple’s default compiler is often Clang without OpenMP enabled for -fopenmp. To use OpenMP, install GCC from Homebrew and point CC at it (the exact version suffix may vary):
brew install gcc
CC=gcc-14 make OPENMP=1Alternatively, install libomp and use Clang with the appropriate -fopenmp / -lomp flags if you maintain a custom toolchain.
You typically do not install a package named “openmp” alone. GCC implements OpenMP and links the runtime (libgomp on Linux). If gcc -fopenmp works on your system, make OPENMP=1 should work.
makeOptional OpenMP (shared-memory parallelism in the hot loops — SpMV, Euler/RK4 RHS, Poisson):
make OPENMP=1Thread count follows OMP_NUM_THREADS (and the OpenMP runtime defaults). The default make build is unchanged (no OpenMP).
This produces the cnavier binary. Output VTK files are written to output/ — create it first:
mkdir -p output
./cnavierTo clean build artifacts:
make cleanAll parameters are set at the top of src/main.c:
| Parameter | Default | Description |
|---|---|---|
Re |
1000 |
Reynolds number |
Lx, Ly |
1 |
Domain size |
| Parameter | Default | Description |
|---|---|---|
nx, ny |
64 |
Grid points in x and y |
dt |
0.005 |
Time step |
tf |
20 |
Final time |
order |
6 |
Finite difference order (2, 4, or 6) |
time_scheme |
2 |
1 = Euler, 2 = RK4 |
poisson_type |
3 |
1 = Gauss-Seidel, 2 = SOR, 3 = FFT |
output_interval |
10 |
Write VTK every N iterations |
The default case is the lid-driven cavity: the top wall moves at u=1, all other walls are stationary no-slip. Boundary conditions are set via u1–u4 and v1–v4 in main.c.
VTK files are written to output/ and can be opened in ParaView. The vorticity field is exported by default; stream function, velocity components, and pressure can be enabled by uncommenting the relevant printvtk calls in main.c.
cnavier/
├── src/
│ ├── main.c # Simulation loop and configuration
│ ├── linearalg.c # Dense and sparse (CSR) linear algebra
│ ├── finitediff.c # Finite difference operators (dense + sparse)
│ ├── fluiddyn.c # Euler/RK4 time integration, vorticity, continuity
│ ├── poisson.c # Gauss-Seidel, SOR, and FFT Poisson solvers
│ └── utils.c # VTK output, random utilities
├── include/
│ ├── linearalg.h
│ ├── finitediff.h
│ ├── fluiddyn.h
│ ├── poisson.h
│ └── utils.h
├── output/ # VTK output files
├── Re1000_cavity_flow_example.png
├── Re1000_cavity_flow_example.mp4
├── LICENSE
├── Makefile
└── README.md
The default poisson_type = 3 uses FFTW3's RODFT00 plan (DST-I) to solve the Poisson equation exactly in O(n² log n). This is orders of magnitude faster than the iterative solvers at high Reynolds numbers where many iterations are required for convergence. FFTW plans are computed once at startup via fft_setup() and reused every timestep.
With RK4 (time_scheme = 2), the Poisson equation is solved once per RK4 stage (5 solves per timestep total). Since each solve is O(n² log n), this remains fast and gives 4th-order temporal accuracy.
