Mason Shopperly

MS-07 · numerics · Reviewed Jul 14, 2026

CFDLab — a two-book CFD codebase

An independent, ongoing Python companion to the Lomax–Pulliam–Zingg two-book CFD curriculum — analytical probes, three structurally different quasi-1-D Euler solvers, verification-gated 2-D lanes, and CI-pinned numerical baselines.

Role
Independent project · Python implementation of Lomax–Pulliam–Zingg
Era
2024–ongoing
Status
ongoing
Tier
flagship
Tools
Python · NumPy · Matplotlib · MD5 baselines · CI
Modified wavenumber κ*(κh) for the standard FD operator family — phase / amplitude error per scheme on one plot
Modified wavenumber κ*(κh) — phase / amplitude error per FD scheme. The analytical layer's opening figure.

Why this codebase

The Lomax–Pulliam–Zingg two-book CFD curriculum sets the theory and the algorithms in one consistent voice: Book 1 is the analytical spine — operator analysis, modified wavenumbers, stability regions, modified-PDE character — and Book 2 is the algorithmic spine, three structurally different solver families on the same governing equations. Reading the books is one thing; building the codebase that runs every result they describe is the thing that makes the theory operational.

CFDLab is that codebase — mine, written from scratch in Python, still developing.

What it is

An analytical-probe layer, three structurally different quasi-1-D Euler solvers, two 2-D lanes in verification-gated development, and a shared library of the numerical fundamentals everything else stands on — mesh generation and quality, spatial discretization, implicit solves, viscous operators, Riemann and dissipation methods, exact solutions, multigrid transfer — each guarded by tolerance-gated checks. Over a hundred numerical baselines are pinned by per-project MD5 hashes and enforced by the repository’s checks on every push (100+ as of July 2026; the repo is private, so the counts live here rather than behind a link). A 74-entry worked Exercise Guide spans both books.

The reading order on this page mirrors the codebase. Analytical foundations first — the modified wavenumber, the TVD region, modal decay rates that everything else has to obey. Then the three 1-D solver projects, each on the canonical problem it was built to handle, and the cross-project JST-vs-Roe shock-tube comparison that closes the 1-D arc. The two-dimensional lanes come last, because that is where the work now lives.

Analytical foundations

Three figures, all from the analytical-probe layer. The modified wavenumber sets the reading frame: every scheme below the line is a choice in phase and amplitude error per resolved wave. The Sweby diagram is the bridge from pure FD analysis to shock-capturing — limiters live inside that region or they are not TVD. The diffusion modal decay closes the lead trio with the time-domain side of the same machinery: discrete decay rates against the analytical reference, mode by mode.

These are not anchored to any one solver. They are the language the solvers below speak.

Three solver projects

The three Book 2 solvers are structurally different. ARC1D is the Ch.4 lane — implicit finite difference with JST scalar artificial dissipation in delta-form, solved by block-Thomas. FLOMG is Ch.5 — explicit finite volume with five-stage Van der Houwen Runge–Kutta time-marching wrapped in a FAS multigrid driver. upwind1d is Ch.6 — Roe approximate Riemann + MUSCL piecewise-linear reconstruction + TVD slope limiting + SSP-RK2.

Each runs on the canonical problem the project was built to handle. ARC1D does Sod and the transonic nozzle; FLOMG does steady transonic with multigrid; upwind1d does Sod with shock-capturing. The figures here are per-solver — each solver, on its own, doing its own work.

A note on FLOMG: the explicit-FV multigrid lane is purpose-built for steady flows with a single shock, not for the Sod shock-tube transient. Showing FLOMG on transonic rather than on Sod is the honest figure for that lane.

Cross-project shock-tube comparison

With the analytical frame and the three solver lanes on the screen, the JST-vs-Roe Sod overlay reads correctly: the rarefaction → contact → shock structure across the whole domain (full overlay), then the contact zoom that makes Book 2’s central algorithmic argument visible in one image. Measured from each solver’s own output at the sample time, on grids of equal 0.025-wide cells, the scalar-pressure-sensor JST artificial dissipation spreads the contact discontinuity over roughly 1.5 to 1.8 times as many cells as characteristic-decomposition Roe + MUSCL upwinding does — 16 cells against 9 when the contact is counted as the cells lying more than 10 % of the density jump from either plateau, and 20 against 12 at a 5 % tolerance. Neither scheme holds the contact in a single cell, and the ratio moves with the tolerance chosen. That is one 1-D model problem at one grid spacing under one width metric rather than a general ranking of the two schemes, and it is the result the 1-D arc was built to reach.

Into two dimensions

Moving past quasi-1-D changes the problem more than it multiplies it. Geometry arrives: the grid becomes curvilinear, and every derivative picks up metric terms that are themselves computed quantities with their own error. The implicit operator that was a single block-tridiagonal solve in 1-D is too large to invert directly, so it is approximately factored into direction-wise sweeps — a decision with stability consequences of its own. Boundaries become curves with their own discrete treatment, viscous terms bring their own operators and Jacobians, and the mesh itself becomes a numerical artefact that has to be generated, smoothed, and graded for quality before the flow solver ever sees it.

CFDLab’s two 2-D lanes take that step from opposite directions. ARC2D reconstructs the implicit curvilinear method of the NASA Ames lineage — Beam–Warming approximate factorization with the Pulliam–Chaussee diagonal form on a C-grid. These are the general-purpose implicit codes Ames introduced in 1977–78, and the diagonal form earned its keep by cutting the work and storage per step on the vector supercomputers of that era. CFDLab does not reproduce the historical code; it rebuilds and studies the numerical method itself, in a modern Python setting where every milestone is checked before the next is attempted. The lane is built and milestone-gated: the transonic airfoil case below captures the supersonic pocket and shock with the residual driven to a few parts in a thousand of its starting value. It is a qualitative reconstruction — the figure says so on its face — and it is not presented as validated CFD. The reference-data comparison that would earn that word is still ahead.

upwind2d extends the Ch.6 upwind family instead: HLLE and Roe fluxes through cell faces, direction-by-direction MUSCL reconstruction, SSP-RK2 in time — a finite-volume method that is genuinely two-dimensional rather than a rotated 1-D scheme. Its discipline is verification against cases with known answers: reduced to one dimension it reproduces upwind1d’s results, and on the regular oblique-shock reflection below the computed region states land on the derived analytic solution — the dashed lines over the field are the analytic shocks. Where no exact answer exists, results are labelled as comparisons against published figures, never called validated.

What it earns

Building three structurally different solvers against the same governing equations, with the analytical layer right there to consult, is what lets me read a method-property probe before running a case and know what the case will say. It is the same discipline that makes a workflow elsewhere — an OpenFOAM run on the cluster, a high-order SBP scheme on a model PDE — give up its pathologies before a dashboard hides them. The 2-D lanes are that discipline meeting more machinery: curvilinear metrics, factored implicit operators, real meshes — and the same rule holding throughout, that nothing gets called more than what its checks have earned.

The tagged plateau is v0.6-two-book-exercise-guide (April 2026). The work is now well past that tag — the 2-D lanes and the shared fundamentals library are where it is moving — with no newer formal release yet.

Analytical foundations

Sweby TVD region with common limiters overlaid
Sweby TVD region — common limiters overlaid. The bridge from FD analysis into shock-capturing.
Diffusion modal decay rates vs analytical reference
Diffusion modal decay — discrete decay rates per mode against the analytical reference.

Three solver projects

ARC1D — implicit FD + JST artificial dissipation on the Sod shock tube
ARC1D (Book 2 Ch.4) — implicit FD + JST artificial dissipation on Sod, the Ch.4 lane on its definitional problem.
ARC1D κ4 sweep — JST 4th-order coefficient effect on the contact discontinuity
ARC1D κ4 sweep — the JST 4th-order coefficient effect on the contact discontinuity, zoomed in on the Sod contact.
FLOMG — explicit FV with FAS multigrid, transonic nozzle inlet-closure sensitivity
FLOMG (Book 2 Ch.5) — explicit FV + FAS multigrid on transonic. The multigrid lane is purpose-built for steady transonic flows; transonic is the honest figure for FLOMG, not Sod.
upwind1d — Roe + MUSCL + TVD + SSP-RK2 on the Sod shock tube
upwind1d (Book 2 Ch.6) — Roe approximate Riemann + MUSCL + TVD + SSP-RK2 on Sod, the Ch.6 lane on its definitional problem.

Into two dimensions

ARC2D transonic airfoil at Mach 0.75 — near-field Mach contours with supersonic pocket and sonic line, plus surface pressure-coefficient distribution
ARC2D — transonic airfoil at M∞ = 0.75: supersonic pocket (max local Mach 1.22, sonic line in white) and surface Cp, residual at 2.3×10⁻³ of its initial value. Qualitative reconstruction of the implicit curvilinear method — not presented as validated CFD.
upwind2d regular oblique-shock reflection at Mach 2.9 — density field and numerical schlieren with the derived analytic shock solution overlaid as dashed lines
upwind2d — regular oblique-shock reflection off a slip wall (M = 2.9, β₁ = 29°, HLLE + MUSCL): computed region densities match the derived analytic solution; dashed lines are the analytic shocks. Verification against an exact solution, not a validation claim.
FIG. 01

Contact-discontinuity width under JST artificial dissipation against Roe + MUSCL upwinding, on the Sod shock tube

Case Sod shock tube in ideal air: p_L = 1.0×10⁵ Pa, p_R = 1.0×10⁴ Pa, ρ_L = 1.0 and ρ_R = 0.125 kg/m³, diaphragm at x₀ = 5 m in a 10 m domain, sampled at t = 6.1 ms. Both lanes run uniform cells of equal width Δx = 0.025 m — 399 cells for ARC1D, 400 for upwind1d — so the two contact widths are counted on the same spacing.

Two stacked panels — density and Mach number against x — over the contact and shock region of the Sod tube. A black step line marks the exact Riemann solution. Two dotted solver traces follow it: the JST trace leaves the upper plateau earlier and rejoins the lower one later, spreading the contact over a visibly wider band than the Roe + MUSCL trace, which turns more sharply at both ends. Both traces overshoot slightly at the shock on the right.

Legend drawn inside the figure: the solid black step is the exact Riemann solution; the two dotted series are ARC1D (JST + implicit Euler) and upwind1d (Roe + MUSCL-minmod). Density above, Mach number below. The two solver series are separated by colour alone in this export, so they are named in words here and in the alt text. At narrow widths the inline render is an overview: its axis labels fall to roughly 6 px and are not claimed to be readable there — open the full-size file below to read them.

Source CFDLab — ARC1D (implicit finite difference, JST scalar artificial dissipation, implicit Euler) and upwind1d (Roe approximate Riemann solver, MUSCL reconstruction, minmod limiter, SSP-RK2), run independently and overlaid from each project's committed final-state file.

Open the full-resolution figure 1125 × 1200

What
Contact-discontinuity width under JST artificial dissipation against Roe + MUSCL upwinding, on the Sod shock tube
Check
Analytical verification The exact Riemann solution for this initial condition, drawn as the black step in both panels. It is an analytical answer rather than a second computation, so it carries no discretisation error of its own and each solver's departure from it is attributable to the scheme.
Result
Measured from each solver's own output at the sample time, JST spreads the contact over approximately 1.5–1.8 times as many cells as Roe + MUSCL does on these equal-spacing grids: 16 cells against 9 at a ±10 % plateau tolerance, 20 against 12 at ±5 %, and 25 against 16 at ±2 %. The ratio moves with the tolerance, which is why a range is quoted rather than a single number. Neither scheme holds the contact within one cell.
Limit
One 1-D model problem, one grid spacing, one sample time, one contact-width metric. This is not a ranking of the two schemes, and no order of accuracy is claimed — that would need an error norm against the exact solution on a refined sequence of grids, and this is a single grid. Nothing here transfers to two or three dimensions, to viscous or turbulent flow, or to the shock, whose width was not what was measured.

Artifacts

  • Figure Published
  • Method Published

    Each lane's spatial discretisation, dissipation or limiter, and time integrator are named on this page.

  • Reference standard Published

    The exact Riemann solution is plotted on the figure as the black step in both panels.

  • Parameters Published

    Initial condition, domain, sample time and cell width are stated in the Case field above.

  • Refinement protocol Not produced

    This comparison is one grid. No refinement sequence was run — which is exactly why no order of accuracy is claimed.

  • Error norm Not produced

    The reported quantity is a contact width in cells at a stated plateau tolerance, not a norm of the error against the exact solution.

  • Tabulated data Not published
  • Regeneration record Not published
  • Source repository Not published
  • Tabulated data, Regeneration record, Source repository — In a private repository.

Access The figure, the case parameters and the counting method are published here; the solver source and its output files are in a private repository.

What this establishes

  • That a scalar pressure-sensor dissipation smears a contact discontinuity measurably more than characteristic-decomposition upwinding does, on a problem with an exact answer.
  • That the two lanes compared here — ARC1D and upwind1d — reproduce the analytical structure of the Sod problem, rarefaction through contact to shock, against the exact Riemann solution.
  • That the numerical claim on this page is counted from the solvers' own output by a method stated in full, rather than asserted.

What it does not establish

  • That Roe + MUSCL is generally superior to JST. One model problem at one spacing is not a ranking of two schemes.
  • An order of accuracy for either scheme, which would require an error norm on a refined sequence of grids.
  • Anything about two- or three-dimensional behaviour, where curvilinear metrics and factored implicit operators change the problem.
  • Anything about turbulent or viscous flow, neither of which this comparison models.
  • That either solver is qualified for production use.

Context — not the subject of the claim

Density and Mach number across the full 10 m Sod domain at 6.1 ms. A black line marks the exact solution as a plateau, a linear rarefaction ramp, then two steps down. The two dotted solver traces sit on it through the rarefaction and separate from it only at the contact and the shock.

The same two solvers over the whole domain at the same instant: rarefaction, contact and shock in one frame, against the exact Riemann solution.

Relation Included so the zoomed contact above can be located in the full wave structure. No measurement is taken from this frame.

Working notes — what's open on this lane

← Previous · Multi-node CFD server

Next · Human Control of Flight Systems →