# Verification status The package was first overhauled on a machine with no Linux, conda or OpenFOAM, and tested against fake solvers. It has since been run against a **real DAFoam v5.1.1 / OpenFOAM v2506 built from source on ARM64 Linux** (a Lima VM on an Apple-silicon Mac, `scripts/vm/`). This page states exactly what each claim rests on. ## Verified on the real solver (ARM64 Linux, DAFoam 5.1.1, 4032-cell NACA 0012, Euler-mode case, 1 and 4 MPI ranks) | Claim | Evidence | | --- | --- | | DAFoam, OpenFOAM v2506 (with DAFoam's MPI patch) and the reverse-mode-AD OpenFOAM build and import on ARM64 | `scripts/vm/install_dafoam_ubuntu.sh`; `import dafoam`, `PYDAFOAM` | | The interface runs a real flow solve: alpha = 3 deg, 238 m/s gives **CL = 0.4706, CD = 0.01174 (117 counts)**; the SIMPLE solve converges to the 1e-8 tolerance in 164 iterations from a cold start | `tests/test_dafoam_integration.py`, `scripts/vm/fd_study.py` | | **Adjoint derivatives are correct.** Finite differences converge onto the adjoint as the step grows past the noise floor: best-step disagreement 0.2 to 0.7% (alpha, camber), 1 to 2% (thickness), 0.1 to 0.5% for CL. At a step of 1e-3 or below the finite differences are noise (8% to 34% off), which is why a naive single-step check fails | `scripts/vm/fd_study.py` | | **A real optimization works**: `examples/airfoil_optimization.py` (IDWarp-JAX, OpenSQP, 4 ranks). **100 iterations (36.7 min): CD 117.4 to 75.4 counts (-35.8%), CL 0.50000** (target 0.5), alpha 0.00 deg (its lower bound), thickness changes of +19.6%, +35.7%, +33.9% at the three interior control points and camber changes of +4.2%, +8.2%, +11.2%. CD stops changing at about iteration 65; optimality ends near 6e-5 (tolerance 1e-5), so modOpt reports the iteration cap, not convergence. Earlier, shorter runs ended at different, less optimized local designs: 15 iterations CD 85.6 (alpha 2.81 deg), 40 iterations CD 76.6 (alpha 0.84 deg, still improving). The recorded CD of every iteration matches modOpt's objective to 0.0014 counts | run logs, `modopt_summary.out`, `history.npz`; the movie and figures in the docs | | The initial design's drag includes a **real shock**: the surface C_p drops sharply at about 30% chord (from -1.45 to -0.55, upper surface), and the optimized design has none. So the drag reduction is wave-drag reduction, not an artifact of the optimizer | `viz.plot_airfoil_comparison` of the real initial and final flow solutions | | `csdl_dafoam.viz` reads real DAFoam output: an MPI-decomposed (4 processor) solution is reassembled on the global mesh, deformed meshes included, and plotted (pressure-coefficient contours, surface C_p, shapes) | `tests/test_viz.py` on a real 4-rank snapshot (coarse mesh); the figures in the README and docs | | The 336-cell test mesh (`naca0012_coarse`) runs on the real solver: model build 2.6 s, warm solve 0.2 s, adjoint 0.74 s, adjoint agrees with finite differences to 0 to 2.3%, 1 and 4 ranks identical; OpenFOAM v2506's `checkMesh` accepts it (non-orthogonality 22.5 deg, skewness 0.7) | `scripts/vm/fd_study.py`, `timing.py`, `tests/test_coarse_mesh.py` | | The install is reproducible: every download is checksum-verified (the 8 hashes were computed independently in the VM), the two branch-only sources are pinned to commits, the Python and lab-package versions are recorded | `scripts/vm/install_dafoam_ubuntu.sh`, `pip-freeze-ubuntu2404-aarch64.txt`, `requirements-verified.txt`, `tests/test_vm_scripts.py` | | Cost on this machine, 4032 cells: forward solve about 3.9 s (1 rank) and 1.4 s (4 ranks); adjoint (two outputs) about 7 s at the base design on 4 ranks, up to about 31 s (4 ranks) or 101 s (1 rank) at a design with CL 0.58, so it depends on the flow state; first `compute_totals` about 17 s on 4 ranks before the recorder fix was re-timed (JAX compilation is about 5 s of it; not re-timed since) | `scripts/vm/timing.py`, `profile_totals.py` | | The CSDL graph itself is not the cost: a 2,400-node graph compiles in about 5 to 9 s. The slow first call was our bug (inline recorder re-executing the adjoint 14 times instead of 2), now fixed | `scripts/vm/profile_totals.py` before and after | | No GPU: DAFoam and OpenFOAM are CPU-only MPI codes | DAFoam docs and source; the VM has no GPU passthrough | ## Verified without DAFoam (fake solvers, native checks) | Claim | How | | --- | --- | | The package builds a wheel, installs into a clean environment pulling only NumPy and SciPy, ships its data (geometry, mesh, cases), and its console script works | built and installed the wheel in a fresh venv | | `import csdl_dafoam` needs no DAFoam, PETSc, MPI, CSDL or JAX | `test_import.py` (subprocess check of `sys.modules`) | | The `lsdo_geo` / `lsdo_function_spaces` code paths work with the current releases (1.0.0 / 1.0.0) and CSDL main | real STEP import, caching, projection of the real wall mesh (max error 2.7e-6), FFD thickness and camber response | | `DAFoamSolver`, `DAFoamFunctions`, `DAFoamMeshWarper` produce correct states, functions and **total derivatives** given a solver obeying DAFoam's API | exact comparison against closed-form linear fakes, both adjoint methods; deliberate sign/hand-back bugs are caught | | `build_airfoil_model` and both example scripts run end to end, with IDWarp-JAX or the Fortran-IDWarp interface, and with OpenSQP or PySLSQP; reverse-mode derivatives through geometry, warp and flow agree with finite differences | `test_airfoil_model.py`, `test_examples.py` with a fake flow solver | | **IDWarp-JAX on the real bundled airfoil mesh**: wall points land on the prescribed surface (2e-5), the span coordinate is untouched, no cell inverts and none falls below 69% of its volume even for a 50%-thickness plus 5%-camber change (86% with the default `LdefFact=100`), and its VJP matches finite differences (about 1e-7 relative) | `test_jax_warp.py` (real `idwarp-jax` 0.1.0, CPU) | | **Warped meshes pass OpenFOAM v2506's own `checkMesh`** under DAFoam's criteria, natively on an Apple-silicon Mac (OpenFOAM.app v2506 arm64): max non-orthogonality 22.8 to 23.7 deg (limit 70), skewness 1.4 (limit 4), aspect ratio about 98 (limit 1000), no negative volumes, for the undeformed mesh and three IDWarp-JAX perturbations up to +50% thickness and 5% camber; OpenFOAM's minimum cell volume matches `foam_io.cell_volumes` to 1e-6; a deliberately tangled mesh is rejected | `test_openfoam_mesh.py` (`pytest -m openfoam`), `csdl_dafoam.foam_check` | | The bundled mesh and case files are accepted by OpenFOAM v2506 | `checkMesh`, `potentialFoam` and stock `rhoSimpleFoam` all read the case (turbulence, thermophysics, boundary conditions) | | **OpenSQP** (modOpt 0.3.1) solves the CSDL problem, hands its final design back to the simulator, and needs `qpsolvers`, `quadprog` and `highspy` | `test_airfoil_model.py`; found by running it | | The interface calls only DAFoam methods that exist in v4.0.4 and v5.1.1 | compared against both releases' `pyDAFoam.py` / `mphys_dafoam.py` (names present; signatures read, not executed) | | Python 3.9 and NumPy 2.0 / SciPy 1.13 / JAX 0.4.30 | the test run | ## Also verified on the real solver: parallel runs | Claim | Evidence | | --- | --- | | 1 and 4 MPI ranks give the same answer (CL 0.57947, CD 0.012846 to the printed digits at the same design), so IDWarp-JAX's mapping of each rank's points into the global mesh works with a real `decomposePar` decomposition (shared processor-boundary points included), and the CSDL MPI region's reverse-pass summation is right | `scripts/vm/timing.py` at 1 and 4 ranks | | The optimization example runs on 4 ranks | `examples/airfoil_optimization.py` run above | ## Not verified | Item | Why it matters | What to do | | --- | --- | --- | | **modOpt does not report convergence on this problem.** The 100-iteration run ends at the iteration cap: drag has been flat since about iteration 65 (75.42 counts) and the lift constraint is met to 1e-7, but optimality reaches only about 6e-5 against a tolerance of 1e-5 | the design is a good local result for this mesh, but the optimizer's own criterion was not met | look at scaling of the design variables, and try `--tolerance 1e-4` or a larger `--maxiter` | | **Whether the optimum is physically meaningful.** The mesh has only 4,032 cells and the Euler-mode case has no resolved boundary layer, so the 117-count baseline drag is likely dominated by numerical dissipation and a smeared shock, not wave drag alone, and an optimizer can exploit mesh-resolution artifacts | do not read these drag numbers as physical; the design changes (thinner, cambered, alpha 2.8) are plausible but unvalidated | refine the mesh and re-evaluate the optimized shape; compare with RANS | | **The RANS case** (`--case naca0012`) on the real solver | only the Euler-mode case has been run | `airfoil_analysis.py --case naca0012` | | OpenSQP against PySLSQP on the real problem | only OpenSQP was run | `--optimizer PySLSQP` | | **The conda recipes** (`recipes/`, `recipe/recipe.yaml`, `environment.yml`) | still never built. The source build revealed that they are **incomplete**: DAFoam's C++ also needs **Hisa4DAFoam** built first (its headers are on the include path), OpenFOAM's FFTW-dependent utilities need a second `Allwmake` pass after the environment is re-sourced, and DAFoam's own test suite needs pyOFM, mphys and pygeo. The verified route is `scripts/vm/install_dafoam_ubuntu.sh`, which has all of this | port those three findings into `recipes/openfoam-dafoam` and `recipes/dafoam`, then build on Linux | | x86-64 Linux, and the build script on anything but Ubuntu 24.04 ARM64 | verified only on one platform | run `scripts/vm/install_dafoam_ubuntu.sh` there | | DAFoam's own regression suite (`tests/Allrun`) | not run: the test for our solver (`runRegTests_DARhoSimpleCFoam.py`) needs mphys and pygeo, which this build omits | install the MACH components or accept our own `pytest -m dafoam` as the check | | IDWarp-JAX: the second symmetry plane (y = 0.1) it does not model, far-field motion, and scale | the solve and the adjoint were fine with them on this mesh; their effect on accuracy is unquantified, and every rank warps the whole mesh, untested for large 3D meshes | refine, compare with the Fortran IDWarp | | The GitHub workflows (`tests.yml`, `pages.yml`, conda) | written, never run | they run on the pull request; Pages also needs Settings, Pages, Source: "GitHub Actions" | | 3D visualization (`write_patch_vtk` on a real 3D wing, `plot_surface_3d`) | tested only on the one-cell-thick 2D mesh | try on a 3D case | | `--adjoint-tol 1e-3` in a full optimization | measured on one gradient only (29% faster, derivative error 7e-4) | run an optimization with it and compare the optimum | ## Open questions for the maintainers - Is publishing a *source-built OpenFOAM* package to the `lsdolab` channel acceptable (size, GPL licensing of DAFoam/OpenFOAM, rattler-build runner time)? The alternative is to keep the DAFoam stack in a single user-built environment and publish only `csdl_dafoam`. - Should `lsdo-geo` and `lsdo-function-spaces` (PyPI) also be published on the channel so the environment needs no pip section?