Skip to content

refactor(params): auto-generate Fortran namelists from Python, eliminate toolchain duplication#1456

Merged
sbryngelson merged 33 commits into
MFlowCode:masterfrom
sbryngelson:refac-params
May 29, 2026
Merged

refactor(params): auto-generate Fortran namelists from Python, eliminate toolchain duplication#1456
sbryngelson merged 33 commits into
MFlowCode:masterfrom
sbryngelson:refac-params

Conversation

@sbryngelson
Copy link
Copy Markdown
Member

@sbryngelson sbryngelson commented May 28, 2026

Summary

  • Auto-generate Fortran namelist and declaration .fpp files from the Python parameter registry (definitions.py) at CMake configure time, replacing ~390 lines of hand-written boilerplate across the 6 m_global_parameters.fpp / m_start_up.fpp files
  • Consolidate the parameter system: adding a parameter now requires editing definitions.py only (previously 3–4 files)
  • Eliminate accumulated toolchain duplication across namelist_parser.py, descriptions.py, case_validator.py, test/cases.py, and several other files

What changed

Fortran codegen (original branch):

  • fortran_gen.py + cmake_gen.py: generate generated_namelist.fpp and generated_decls.fpp per target at CMake configure time
  • CMake stamp-based caching: codegen only reruns when definitions.py or namelist_targets.py actually change
  • m_start_up.fpp and m_global_parameters.fpp for all 3 targets now #:include the generated files

Toolchain cleanup (on top):

  • NAMELIST_VARS moved from namelist_targets.py into definitions.py (deleted namelist_targets.py); CASE_OPT_EXCLUDE was a duplicate of the existing CASE_OPT_PARAMS
  • _FALLBACK_PARAMS (380 hardcoded lines in namelist_parser.py) replaced by a 4-line function that derives the same data from NAMELIST_VARS — the old dict was also missing 5 simulation params that were in the actual Fortran
  • _SIMPLE_DESCS + _auto_describe (200 lines in definitions.py) deleted; ParamDef.description is now populated from descriptions.get_description() at registration time, giving callers a single place to read descriptions
  • _PREFIX_DESCS + _ATTR_DESCS (113 lines) deleted — dead code after _auto_describe removal
  • Duplicate "resolve generated include → generate in-memory" logic extracted to resolve_namelist_content() in fortran_gen.py; both namelist_parser.py and lint_param_docs.py now use it
  • lint_source.py: removed dead check_pylint_directives (no pylint directives exist in the codebase)
  • run/run.py: 4 duplicate profiler if-blocks → _PROFILERS data table
  • case_validator.py: extracted _check_order_fits_grid() and _get_recon_type() — each was copy-pasted across 3–4 check methods
  • test/cases.py: extracted make_3d_box_patches() — 18-line IC setup block was copy-pasted into 3 test functions
  • Switched toolchain test runner from hardcoded unittest module list to pytest discover; moved 3 orphaned test files that were never running

Net diff vs master

27 files changed, 824 insertions(+), 1454 deletions(-)

−630 net lines. The original branch was +330 net before cleanup.

Test plan

  • ./mfc.sh precheck -j 8 passes (all 6 checks: format, spell, lint, source lint, doc refs, param docs)
  • ./mfc.sh build -j 8 compiles with gfortran (CI will verify nvfortran, Cray ftn, ifx)
  • ./mfc.sh test -j 8 regression suite passes
  • ./mfc.sh build --gpu acc -j 8 on a GPU node (OpenACC path)
  • ./mfc.sh build --gpu mp -j 8 on a GPU node (OpenMP path)
  • Verify generated .fpp files match expected namelist content for each target

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 28, 2026

Claude Code Review

Head SHA: b5eeea3

Files changed:

  • 32
  • CMakeLists.txt
  • src/post_process/m_global_parameters.fpp
  • src/post_process/m_start_up.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/pre_process/m_start_up.fpp
  • src/simulation/m_global_parameters.fpp
  • src/simulation/m_start_up.fpp
  • toolchain/mfc/params/definitions.py
  • toolchain/mfc/params/generators/fortran_gen.py
  • toolchain/mfc/params/namelist_parser.py

Findings:

CMakeLists.txt: stamp dependency references non-existent namelist_targets.py

The newly added CMake stamp block lists toolchain/mfc/params/namelist_targets.py as a tracked input:

set(_mfc_gen_inputs
    "${CMAKE_CURRENT_SOURCE_DIR}/toolchain/mfc/params/generators/cmake_gen.py"
    "${CMAKE_CURRENT_SOURCE_DIR}/toolchain/mfc/params/generators/fortran_gen.py"
    "${CMAKE_CURRENT_SOURCE_DIR}/toolchain/mfc/params/namelist_targets.py"   ← does not exist
    "${CMAKE_CURRENT_SOURCE_DIR}/toolchain/mfc/params/definitions.py"
)

This file does not exist anywhere in the repository — grep -rn "namelist_targets" toolchain/ returns no matches, and the NAMELIST_VARS dict now lives at the bottom of definitions.py (which is already tracked). Per CMake semantics, "${_input}" IS_NEWER_THAN "${_mfc_gen_stamp}" returns TRUE when the left-hand file does not exist, so _mfc_needs_regen is always set to TRUE. As a result, cmake_gen.py re-runs and overwrites all six generated .fpp files on every cmake .. invocation, defeating the stamp optimisation. Fix: remove the namelist_targets.py entry (or correct it to point to definitions.py).


fortran_gen.py: unchecked index into possibly-empty list in generate_namelist_fpp

# generate_namelist_fpp — sim path
nl_lines = _pack_namelist(normal, _FIRST_PREFIX, _CONT_PREFIX, _MAX_LINE)
nl_lines[-1] += ", &"   # IndexError if nl_lines == []

_pack_namelist returns [] when given an empty vars_list. If normal (the non-case-optimisation sim variables) is ever empty, nl_lines[-1] raises IndexError at CMake configure time, breaking the build. The current parameter set makes this unlikely, but the code is unguarded. A simple check before the append (if nl_lines: nl_lines[-1] += ", &") would prevent a confusing configure-time crash.

@sbryngelson sbryngelson marked this pull request as ready for review May 28, 2026 17:22
@qodo-code-review
Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.31%. Comparing base (4b633d5) to head (7923676).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1456   +/-   ##
=======================================
  Coverage   61.31%   61.31%           
=======================================
  Files          72       72           
  Lines       19771    19771           
  Branches     2852     2852           
=======================================
  Hits        12123    12123           
  Misses       5699     5699           
  Partials     1949     1949           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…n/namelist systems

- Delete namelist_targets.py: NAMELIST_VARS moved to definitions.py (one file to
  edit when adding a param); CASE_OPT_EXCLUDE was a duplicate of CASE_OPT_PARAMS

- Delete _FALLBACK_PARAMS (380 hardcoded lines) in namelist_parser.py: replaced
  by _fallback_params() that derives the same data from NAMELIST_VARS at runtime

- Delete _SIMPLE_DESCS (145 lines) and _auto_describe in definitions.py: _r() now
  populates ParamDef.description from descriptions.get_description() so callers
  can read param.description directly; three callers (docs_gen, json_schema_gen,
  params_cmd) migrated off the external get_description() import

- Delete _PREFIX_DESCS and _ATTR_DESCS (113 lines) in definitions.py: dead code
  after _auto_describe was removed

- Add resolve_namelist_content() and TARGET_FROM_DIR to fortran_gen.py: single
  canonical helper for the detect-generated-include pattern duplicated across
  namelist_parser.py and lint_param_docs.py

- Consolidate NAMELIST_VARS to use _nv() helper: 200-line explicit dict replaced
  by grouped calls (~75 lines); cmake_gen.py refactored to use get_generated_files()

- Switch test runner from hardcoded unittest module list to pytest discover; add
  pytest to pyproject.toml; move three orphaned test files into params_tests/

- CMake: stamp-based caching so Fortran codegen only reruns when inputs change

- Extract _format_constraints_cell() in docs_gen.py: 5-line pattern duplicated
  across pattern-view and full-table rendering paths

- Inline 4 single-use wrapper functions in case_dicts.py

Net vs master: -564 lines
- run/run.py: replace 4 duplicate profiler if-blocks with data-driven table;
  adding a new profiler now requires one entry in _PROFILERS

- lint_source.py: remove check_pylint_directives — MFC uses ruff, no pylint
  directives exist anywhere in the codebase, check was provably dead code

- bench.py: delete self-deprecating TODO comment

- run/input.py: log Cantera candidate failures instead of swallowing silently;
  previously a Cantera yaml parse error looked identical to file-not-found
case_validator.py:
- Add _check_order_fits_grid(): the 3-line m/n/p dimension check was
  duplicated identically in check_igr, check_weno, check_muscl; now one call
- Add _get_recon_type(): the recon_type read + prohibit was duplicated in
  check_weno, check_muscl, check_weno_simulation, check_muscl_simulation;
  now one call each
- Collapse two named param lists in check_muscl into inline literals

test/cases.py:
- Add make_3d_box_patches(): the 18-line 3-patch 3D box IC setup was
  copy-pasted identically in mpi_consistency_tests, restart_roundtrip_tests,
  and kernel_golden_tests; now one line each

lint_docs.py:
- Add private helpers to skip set (they call prohibit but are not
  check_* methods and should not require PHYSICS_DOCS entries)
The refac-params branch dropped the bare alpha_wrt scalar param from
definitions.py — it was in master's _wrt loop but not carried over.

This caused ./mfc.sh validate to reject example cases using 'alpha_wrt': 'T'
(the schema had patternProperties for alpha_wrt(N) but not the scalar form),
matching what master registers: both the bare scalar and the indexed forms.
generate_decls_fpp was emitting unconditional declarations for all
sim-target variables including CASE_OPT_PARAMS (recon_type, weno_order,
nb, mapped_weno, viscous, etc.).

m_global_parameters.fpp for simulation contains a manual
#:if MFC_CASE_OPTIMIZATION / #:else block that declares these same
variables — as compile-time constants in case-opt builds and as regular
variables otherwise. The generated include + the manual block created
duplicate declarations, which is a Fortran compile error for every
./mfc.sh build --case-optimization invocation.

Fix: skip CASE_OPT_PARAMS when generating declarations for the sim target.
The manual block handles both the parameter and variable cases.
…ompletion flags

run/input.py:
- cantera_file was only assigned in the chemistry=T branch; the error
  message on line 69 would raise NameError when chemistry=F and all
  candidates (h2o2.yaml) fail to load — set cantera_file='h2o2.yaml'
  before the branch so the error message is always valid

run/archive.py:
- Extract _relpath_safe(path, dirpath) to replace three inline try/except
  blocks scattered across __build_manifest, __copy_dir, __write_tar
- Fix manifest fallback: was falling back to the full absolute path on
  cross-device paths; now uses basename like copy/tar already did —
  manifest and archive contents now agree

cli/completion_gen.py:
- Bash and zsh completion lists for MFC config flags were hardcoded,
  already missing --reldebug/--no-reldebug; derive dynamically from
  MFCConfig dataclass fields (same source argparse_gen.py already uses)
- New MFCConfig fields are now automatically reflected in completions
- completion_gen: fix zsh --gpu completion ('acc:mp' -> 'acc mp')
- case_validator: move recon_type prohibit to check_parameter_types (was duplicated 4x)
- CMakeLists: remove nonexistent namelist_targets.py stamp watch; add registry.py + schema.py
- fortran_gen: guard nl_with_cont[-1] against empty normal list
- docs_gen: use .get() for REGISTRY lookups to avoid KeyError on unknown params
Fortran decls and namelist bindings are now auto-generated. Simple scalar
params only need definitions.py (_r + _nv) + cmake reconfigure.
Adds fortran_dim support for indexed-family array parameters.
fluid_rho (pre) and 9 output arrays (post) no longer need manual
Fortran declarations — all are generated from definitions.py.

Remaining manual declarations are exclusively Fortran derived-type
variables (bc_x, fluid_pp, patch_icpp, etc.) which require
type information that lives in m_derived_types.fpp.
@sbryngelson sbryngelson merged commit a21bfbf into MFlowCode:master May 29, 2026
87 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant