MFC is an exascale multi-physics CFD solver written in modern Fortran 2008+ with Fypp preprocessing. It has three executables (pre_process, simulation, post_process), a Python toolchain for building/running/testing, and supports GPU acceleration via OpenACC and OpenMP target offload. It must compile with gfortran, nvfortran, Cray ftn, and Intel ifx (CI-gated). AMD flang is additionally supported for OpenMP target offload GPU builds.
You are editing this repository as a conservative maintainer. Three facts shape everything:
- Four compilers, one truth. Every line must compile and behave identically under four CI-gated compilers and three GPU configurations. Code that is merely clever on one is broken on another. Prefer the established idiom over the elegant one.
- Failures here are silent. The worst bugs in a CFD code are not crashes — they are answers that are subtly wrong: a wrong index, a serial loop that should be parallel, a precision mix. Golden-file regression tests are the safety net, and incidental edits are how regressions slip past them.
- Trust the toolchain.
./mfc.shalready solves environments, module loading, dependencies, and build configuration; the lint (./mfc.sh precheck) encodes the project's forbidden patterns. When unsure of a flag or rule, ask the tooling (--help, precheck) rather than guessing.
Primary rule: make the smallest correct change. Prefer deleting code to adding code.
Diff discipline:
- Every changed line should trace to the request. Keep changes localized.
- Do not rewrite, reformat, or rename unrelated code.
- Do not add new files, dependencies, or net-positive LOC without clear justification.
Do not add bloat:
- abstractions for one call site, or defensive code for hypothetical future callers
- validation layers around trusted internal data — constraint checks belong in
case_validator.pyand them_checker*.fppfiles, not scattered through the solver - optional parameters or config knobs for behavior with one correct value
- broad try/except blocks in toolchain Python
- comments explaining obvious code; compatibility shims unless explicitly requested
Tests: add one only when it protects real behavior — it would fail before your change and covers behavior a real case depends on. Prefer one targeted case over many broad ones.
Before editing, state: the smallest viable fix, what changes, what deliberately does not change, and whether a test is needed. After editing, report: files changed, net LOC, and anything that could still be deleted or simplified. If a patch exceeds roughly 100 net new lines, stop and justify before continuing.
For general behavioral guidance (simplicity, surfacing assumptions, verifiable success
criteria), invoke the karpathy-guidelines skill.
All commands run from the repo root via ./mfc.sh; avoid invoking CMake, Python toolchain
scripts, or Fortran compilers directly. Run ./mfc.sh <command> --help for flags
(-j N = parallel jobs).
./mfc.sh build -j 8 # all 3 targets; -t <target>, --gpu acc|mp, --debug
./mfc.sh run case.py -n 4 # run with 4 MPI ranks; -e batch for clusters
./mfc.sh test -j 8 # full suite; --only <filter>, -l to list,
# --generate to refresh golden files after an intended output change
./mfc.sh format -j 8 # auto-format Fortran + Python
./mfc.sh precheck -j 8 # all CI lint checks — run before every commit
./mfc.sh validate case.py # validate a case without running
./mfc.sh params <query> # search case parametersOn HPC clusters, load modules before building: source ./mfc.sh load -c <slug> -m <g|c>
(the source is required). Slugs and per-system GPU backends: toolchain/modules; batch
templates: toolchain/templates/. Identify the system via $LMOD_SYSHOST, then a
non-empty $CRAY_LD_LIBRARY_PATH (→ Cray), then hostname.
For ALL code changes: read the relevant code first, plan multi-file changes before implementing, then format → precheck → build → test → commit, in that order, with one logical change per commit.
- YOU MUST run
./mfc.sh precheckbefore any commit (pre-commit hooks enforce this). - YOU MUST run the tests relevant to your change before claiming work is done.
Changes to
src/common/are shared by all three executables — test all three. - NEVER commit code that does not compile or fails tests.
- NEVER use heredocs for git commit messages. Use simple
git commit -m "message".
src/common/ # shared by ALL three executables — wide blast radius
src/pre_process/ # grid generation and initial conditions
src/simulation/ # CFD solver (the only GPU-accelerated target)
src/post_process/ # data output and visualization
toolchain/ # Python CLI; params/definitions.py is the parameter source of truth
examples/ # example cases (case.py); tests/ holds regression golden files
Source files are .fpp (Fortran + Fypp macros), preprocessed to .f90 by CMake.
The exhaustive forbidden-pattern list is toolchain/mfc/lint_source.py (enforced by
precheck and CI). The rules to internalize — they exist because MFC must behave
identically across compilers, precisions, and GPU backends:
- GPU directives only via
GPU_*Fypp macros — NEVER raw!$acc/!$omppragmas. (Raw#ifdef/#ifndefguards for feature/compiler/library gating ARE normal.) - Precision only via
wp/stpkinds and generic intrinsics — NEVERdsqrt/dble/real(8)ordexponent literals. Writesqrt,1.0_wp,real(..., wp). - Abort only via
call s_mpi_abort()or@:PROHIBIT()/@:ASSERT()— NEVERstop/error stop. - NEVER
goto,COMMONblocks, or globalsavevariables. - Every
@:ALLOCATE(...)MUST have a matching@:DEALLOCATE(...). - New parameters are defined in
toolchain/mfc/params/definitions.py(pluscase_validator.pyif physics-constrained); Fortran declarations and namelist bindings are auto-generated. Exceptions:.claude/rules/common-pitfalls.md.
Modules m_<feature> with s_initialize_/s_finalize_<feature>_module pairs; public
subroutines s_<verb>_<noun>, functions f_<verb>_<noun>; 2-space indent, lowercase
keywords, explicit intent on all arguments; constants get descriptive names, not
ALL_CAPS. Full hard/soft rule tables: docs/documentation/contributing.md.
wp = working precision (computation); stp = storage precision (field arrays and I/O).
Both double by default; --single → both single; --mixed → wp=double, stp=half — so
wp/stp mixing is a silent precision bug, not a style issue. scalar_field%sf and all new
field arrays use stp. MPI types must match: mpi_p ↔ wp, mpi_io_p ↔ stp.
docs/documentation/ is freshness-checked by precheck (lint_docs.py) — prefer pointing
there over restating it. Most relevant: contributing.md (standards, architecture,
general pitfalls), gpuParallelization.md (GPU macro API), testing.md (test system),
case.md (case parameters, analytic ICs). MFC-specific traps with silent failure modes
live in .claude/rules/common-pitfalls.md — read it before touching indexing, GPU loops,
parameters, or tests.
When reviewing PRs, prioritize in this order:
- Correctness (logic bugs, numerical issues, array bounds)
- Precision discipline (stp vs wp mixing)
- Memory management (@:ALLOCATE/@:DEALLOCATE pairing, GPU pointer setup)
- MPI correctness (halo exchange, buffer sizing, GPU_UPDATE calls)
- GPU code (GPU_* Fypp macros only, no raw pragmas)
- Physics consistency (pressure formula matches model_eqns)
- Compiler portability (4 CI-gated compilers + AMD flang for GPU)