Channel flow past a cylinder
Optimize the inflow of a 2D channel to minimize drag on a cylinder, which requires solvers to handle walls, an obstacle, an inflow and an outflow.
Incompressible fluid flow in 2D. We solve and differentiate through the 2D incompressible Navier–Stokes equations \(\partial_t \mathbf{u} + (\mathbf{u}\cdot\nabla)\mathbf{u} = -\nabla p + \nu\,\nabla^2\mathbf{u}\), \(\nabla\cdot\mathbf{u}=0\), with the kinematic viscosity \(\nu\) as the primary control parameter. The problem has three setups: a doubly periodic box, a channel flow past a cylinder, and a forced Kolmogorov flow.
The channel follows the Schäfer–Turek DFG 2D-1 benchmark at \(Re = 20\), where the flow is steady and laminar.
These are example results, produced automatically on GitHub-hosted runners and refreshed on every release. Each solver runs on its intended device: GPU-capable solvers on a Tesla T4 GPU node, CPU-only solvers (OpenFOAM, deal.II, FEniCS, Firedrake, TopOpt.jl) on a separate CPU node. Each benchmark job has its virtual machine to itself, but the hardware and the load on the host can change from run to run, so wall-clock times vary. Read them for relative scaling between solvers rather than as absolute timings. Accuracy and gradient metrics change little across hardware, although float32 arithmetic is not identical across devices and chaotic rollouts amplify small discrepancies. For numbers that reflect your setup, run the benchmark yourself on your target hardware.
The Schäfer–Turek DFG 2D-1 channel \([0, 2.2] \times [0, 0.41]\) with a cylinder of diameter 0.1 at \((0.2, 0.2)\), a parabolic inflow of mean speed 0.2 on the left, a zero-gradient outflow at \(p=0\) on the right, and no-slip walls. The viscosity \(\nu = 10^{-3}\) gives \(Re = 20\).
Checks
The results overview explains the columns and states.
| Solver | Forward | Optimization | Effort |
|---|---|---|---|
| GPU solvers | |||
| INS.jl | Pass | Pass | L2 |
| PhiFlow | Pass | Pass | L2 |
| PICTmodified setup | Pass | Pass | L2 |
| Warp-NS | Pass | Pass | L2 |
| XLBmodified setup | Pass | Pass | L0 |
| Exponax | Not supported | Not supported | L0 |
| jax-cfd | Not supported | Not supported | L0 |
| CPU solvers | |||
| OpenFOAM | Pass | Not supported | L0 |
Solvers differ in how general they are. Exponax, INS.jl, OpenFOAM, PhiFlow, Warp-NS, XLB and jax-cfd do not run Forced Kolmogorov flow.
Modified setups (2)
These solvers run a substitute for part of the setup, so their checks hold for the modified setup.
- PICT: Replaces the pressure outlet with advective outflow. Effect: Partly measured. In OpenFOAM, an advective velocity outlet with p = 0 leaves Cd, Cl and pressure drop unchanged to six digits, as expected for a steady flow; the missing pressure outlet could not be reproduced there. Its share of PICT's 3% Cd excess (#239) is unmeasured.
- XLB: Sub-steps each step to bound the Mach number. Effect: LBM ties the time step to the grid through the lattice Mach number, so XLB runs k times the prescribed number of steps (k = 9 at N = 256 on the 2-D box), and its cost includes them. Without sub-stepping the Taylor-Green case ran at Ma of about 0.88, with errors 84 to 114x the peer median.
Not supported (3 solvers)
- OpenFOAM, 1 experiment. OpenFOAM is a non-differentiable, forward-only solver
- Exponax, 2 experiments. a Fourier pseudo-spectral method represents only periodic fields, so it cannot express a channel with walls, an inflow and an outflow
- jax-cfd, 2 experiments. jax-cfd 0.2.1 cannot express the channel. Its advection raises NotImplementedError for any inflow (only no-penetration and periodic boundaries are supported), it pads a zero-gradient boundary only for cell-centered fields, so the outlet's face-centered u_x cannot have one, its boundary values are scalars, so the inlet cannot carry a profile, and it has no obstacles
Forward
Is the prediction right? Forward experiments compare each solver’s output with a trusted reference, an analytic solution where one exists, and the other solvers.
Flow past a cylinder against the DFG reference
Steady flow past a cylinder in a channel (Schäfer–Turek DFG 2D-1, Re = 20) at 10 and 20 cells per diameter. Each solver’s drag coefficient at 20 is scored against the reference Cd = 5.5795. The pressure difference across the cylinder (reference 0.1175) and the lift coefficient are also reported. The vorticity fields are for visual comparison.
Sweeps resolution ∈ {10, 20}
{
"ic": {
"name": "dfg_inflow",
"seed": 0
},
"physics": {
"resolution": 10
},
"sweep": {
"key": "resolution",
"values": [
10,
20
]
}
}
Results by solver
| Solver | Mean rel. error |
|---|---|
| OpenFOAM | 1.65e-03 |
| PhiFlow | 1.11e-02 |
| Warp-NS | 2.29e-02 |
| PICT | 2.96e-02 |
| INS.jl | 3.74e-02 |
| XLB | 4.49e-02 |
Optimization
Can you optimize through it? Optimization experiments run a gradient-based optimizer on each solver’s own gradients. A gradient can pass the finite-difference check and still fail to drive an optimization loop.
Inflow optimization for drag
Drag minimization over the inlet profile of the DFG 2D-1 channel at a fixed flow rate: drag coefficient per iteration, optimized vs initial inflow profiles, and the flow fields before and after.
{
"ic": {
"name": "dfg_inflow",
"seed": 0
},
"physics": {
"resolution": 10,
"t_end": 4.0
},
"optim": {
"lr": 0.01,
"max_iters": 100,
"patience": 10,
"min_rel_improvement": 0.001,
"snap_interval": 10
}
}


Results by solver
| Solver | Final drag | Iterations to plateau |
|---|---|---|
| INS.jl | 2.77e+00 | — |
| PhiFlow | 2.90e+00 | — |
| Warp-NS | 2.92e+00 | — |
| PICT | 3.06e+00 | — |
| XLB | 3.15e+00 | — |