Skip to content

Block-structured adaptive mesh refinement (AMR) - #2117

Open
sbryngelson wants to merge 12 commits into
MFlowCode:masterfrom
sbryngelson:amr
Open

sbryngelson wants to merge 12 commits into
MFlowCode:masterfrom
sbryngelson:amr

Conversation

@sbryngelson

@sbryngelson sbryngelson commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Developed with AI assistance (Claude Code). Results were run on OLCF Frontier (MI250X) and an NVIDIA A100 node; commands
are listed so each number can be reproduced.

Stacked on #2116 (solver performance) and #2118 (small fixes). Until they merge this diff includes their commits;
the AMR changes are the last 8 commits.

Contribution Policy

All contributions are expected to demonstrate:

  • A clear understanding of the codebase
  • Alignment with product direction
  • Thoughtful reasoning behind changes
  • Evidence of real-world usage or hands-on experience with the problem

Summary

Adds block-structured adaptive mesh refinement to simulation, off by default (amr = F). With AMR off, results are
those of #2116 (every existing golden passes unchanged).

  • Refinement: density-gradient tagging, Berger-Rigoutsos clustering into blocks of at most amr_max_grid_size
    cells, refinement ratio 2 (or 4 for one level), up to amr_max_level properly nested levels, regrid every
    amr_regrid_int steps.
  • Time stepping: lock-step SSP-RK3, or subcycling (amr_subcycle, two fine steps per coarse step, up to two refined
    levels) with time-interpolated ghosts. Flux registers reflux every coarse/fine face, so the scheme is conservative.
  • Transfer: conservative limited-linear prolongation, or quartic (amr_prolong = 2) with a fallback to linear
    wherever the quartic state would have non-positive density or internal energy; conservative restriction.
  • Parallel: each block is owned whole by one rank (cost-weighted space-filling-curve cut, or owner-aware placement
    with amr_place_owner). Exchanges are nonblocking waves with keyed tags, so results do not depend on message timing.
  • GPU: block data is device-resident; blocks of a level advance in batches through one solver call.
  • Physics under AMR: 5- and 6-equation models, multi-fluid, viscous, hypoelastic, body forces, chemistry (lock-step),
    Lagrangian bubbles (lock-step, cloud on the base grid), acoustic sources (base grid), 1D MHD, IGR (single rank), and
    immersed boundaries (fixed, prescribed or force-driven motion).
  • I/O: restart (serial and MPI-IO) and post-process block overlays.
  • Docs: docs/documentation/amr.md (user guide), amr_implementation.md (internals), one AMR section in case.md.

The solver runs on a block by swapping its grid globals (m, n, p, index ranges, coordinates) in and out;
amr_implementation.md states the contract for module state that depends on the grid. AMR validation fails closed: with
amr = T the validator rejects any logical switch not in AMR_VETTED_FLAGS, so new physics is refused until it meets that
contract. Explicitly rejected with a message: model_eqns = 1, Euler-Euler bubbles, QBMM, phase change, surface tension,
synthetic turbulence, cylindrical or stretched grids, bc = -4 with null_weights; subcycling with IGR, chemistry,
Lagrangian bubbles or moving bodies.

Verification

Regression

Lane Result
Frontier, amdflang OpenMP (-c frontier_amd, 4 shards) 786/786
Build without MPI (--no-mpi) builds
NVHPC 25.11 OpenACC, 4x A100 786/786
NVHPC 25.11 OpenMP offload, build every test variant builds
Frontier CCE 19 OpenACC (-c frontier, 4 shards) 779/786
Frontier CCE 19 OpenMP (-c frontier, 4 shards) 779/786

The full lanes ran before the last restructure, which splits m_amr_ghost and m_amr_seam out of m_amr_exchange,
folds seven copies of the limited-linear stencil into f_amr_lin and two admissibility checks into f_amr_cons_ok, and
fuses the ghost fill, quartic guard and volume-fraction closure into one per-cell kernel (same operations per cell). On
the head: the AMR cases pass on Frontier amdflang OpenMP (52/52), and simulation builds with NVHPC 25.5 and 25.11 OpenMP
in the CI container. CI runs the full matrix on the head. The 7 CCE misses are multi-rank AMR cases (np = 2 to 8) off by 1.5e-12 to 1.6e-9
against tolerances of 1e-12 to 1e-10: the goldens come from amdflang, and CCE's multi-rank sums differ in the last digits.
The same 7 cases miss by the same amounts before this PR's cleanup commits. Tolerances are unchanged.

New tests: 56 AMR cases (multi-rank, multi-level, subcycling, restart, IB, quartic prolongation, and AMR variants of the
chemistry, IGR and Lagrangian-bubble cases) and one rank_time_wrt diagnostics case. No existing golden changes.

Compiler issues found and handled here:

  • CCE 19, IPA: inlined the kernel routines of m_amr_regrid into host loops it flattened and dropped stores in them
    (regrid read garbage block origins and faulted). m_amr_regrid joins the existing per-file -Oipa0 list in
    CMakeLists.txt.
  • CCE 19, OpenMP runtime: looked up two local allocatable arrays named in kernel data clauses in the present table
    anyway; both are now automatic arrays.
  • nvfortran 25.5-26.3, -mp=gpu: fort2 segfaults in its hlscrub store-forwarding pass. The pass reads the
    address constant of the CPU fallback's argument pack as an ILI index (is_alloc), which faults when that symbol index
    exceeds the routine's ILI table. MFC's large kernels keep the table far above the symbol count; the AMR ghost fill was
    three one-kernel routines (about 3.5k symbols against a 2-4k table), and source edits only moved the crash. Fusing them
    into one kernel removes it with no compiler flag: CI-container builds of 25.5 and 25.11 pass, and every AMR kernel file
    compiles under 48 perturbations each (2 versions, 3 paths, 8 line offsets). gpuParallelization.md records the rule:
    keep a routine's kernel work in one kernel rather than splitting it into small routines.
  • Intel -O3: the 1D acoustic-source case under dynamic regrid drifts 3e-12 from the golden, like its static-block
    parent, and gets the same 1e-8 tolerance.

Accuracy and conservation

1D periodic Euler, WENO5, SSP-RK3, refinement ratio 2:

N AMR off static block (middle half) regrid every 4 steps
64 3.14e-7 5.43e-5 5.71e-5
128 2.80e-8 6.94e-6 6.94e-6
256 3.15e-9 8.81e-7 8.81e-7
512 3.82e-10 1.11e-7 1.11e-7
order 3.5, 3.2, 3.0 2.97, 2.98, 2.99 3.04, 2.98, 2.99

L1 density error after one period. Third order is kept; the error constant is larger because the coarse/fine ghost fill is
second order. Total mass, momentum and energy change by 1.4e-16, 4.2e-16 and 1.9e-16 (relative) over 200 steps with regrid
every 4 steps. The same run on 1 and 4 ranks is bitwise identical.

Performance (weak scaling, blast lattice, 8M base cells per GPU, one refined level, subcycled)

s/step (efficiency vs 8 GPUs):

GPUs MFC AMReX CNS PeleC
8 0.674 (1.00) 0.467 (1.00) 0.600 (1.00)
64 0.699 (0.96) 0.600 (0.78) 0.802 (0.75)
128 0.716 (0.94) 0.613 (0.76) 0.832 (0.72)
512 0.775 (0.87) 0.678 (0.69) 0.952 (0.63)
1024 0.944 (0.71) 0.712 (0.66) 0.994 (0.60)

MFC refines about 1.3x more volume than AMReX and PeleC here (40.5% vs 30.3%). The table is from an earlier commit of this
work; at 64 GPUs this PR's tree gives 0.686 and 0.687 s/step in two runs (table: 0.699), timed as
(t240 - t40)/200 of the step-loop wall so startup and the first regrids are excluded. Regrid cost
grows faster than the step beyond 512 GPUs; that is a follow-up.

Use

Mach 2.5 shock through 297 fixed random spheres: 384 x 256 x 256 base grid plus one level, 128 GPUs, 1350 steps, up to
about 5300 blocks.

Known limitations and follow-ups

  • Regrid keeps the current blocks when no cell is tagged, so refinement does not retract until something is tagged again.
  • Level-2 and deeper blocks are re-prolonged from their parent at every regrid (only level-1 data is carried over or
    migrated), which costs accuracy in multi-level runs that regrid often.
  • Regrid cost above 512 GPUs.
  • The solver reads grid state from module globals; AMR swaps them per block. Passing the grid explicitly would remove
    the swap but touches the whole solver.
  • m_amr_state holds many module variables; a block-table derived type would be cleaner.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

Claude Code Review

Head SHA: f08f835

Files changed:

  • 80+ (AMR feature PR; diff is about 26k lines)
  • .github/scripts/check_caseopt_shared_slugs.py
  • .github/scripts/prebuild-case-optimization.sh
  • .github/scripts/submit-slurm-job.sh
  • .github/workflows/common/build.sh
  • src/common/m_eos.fpp
  • src/common/m_global_parameters_common.fpp
  • src/common/include/omp_macros.fpp
  • src/common/include/macros.fpp
  • toolchain/mfc/case.py

Findings:

  • medium .github/scripts/submit-slurm-job.sh: The Phoenix GPU job now requests --ntasks-per-node=8 while -G${gpu_count} is unchanged. If gpu_count is below 8, 8 ranks share fewer GPUs on every GPU test shard, not just the AMR tests the comment cites. I could not tell from the diff whether this is intended. Check that the oversubscription is acceptable, or raise the task count only for the jobs that need 8 slots. Confidence: low-medium.
  • medium src/common/m_eos.fpp: any_state_dependent_eos is now an Fypp constant in every build, and the @:PROHIBIT runs in all builds. A plain ./mfc.sh build or ./mfc.sh run flow that reuses a binary built from an empty case (eos_state_dependent=False) aborts at startup when the case uses Mie-Gruneisen, JWL or Vinet. This is by design, since cmake/Fypp.cmake hard-codes False and case.py bakes the real value into the slug. It does mean any path that bypasses the per-case slug, for example prebuilt or shared staging dirs, now fails at runtime. check_caseopt_shared_slugs.py covers only the case-optimization benchmarks. The new eos variant in build.sh covers only the test suite. Confidence: medium-low.
  • medium src/common/include/omp_macros.fpp: For amdflang, OMP_PARALLEL_LOOP now always appends OMP_MAP_STR('alloc', privateArrays) to default_val. With privateArrays=None, this depends on OMP_MAP_STR returning an empty string for None. I did not verify that in the diff. Also, default_val.strip('\n') can join two clauses without a separating space. The AMD default is '' or 'defaultmap(present:allocatable) ', so the trailing space survives only if strip is on newlines. Confirm the generated directive text on the AMD path. Confidence: low.

Not reviewed in depth: the bulk of the src/simulation/m_amr*.fpp modules (about 20k lines) and the Riemann solver hunks. No line-level findings come from those.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

Claude Code Review

Head SHA: ac5becc

Files changed:

  • 94
  • .github/scripts/check_caseopt_shared_slugs.py
  • .github/scripts/prebuild-case-optimization.sh
  • .github/workflows/common/build.sh
  • src/common/include/3dHardcodedIC.fpp
  • src/common/include/omp_macros.fpp
  • src/common/include/2dHardcodedIC.fpp
  • src/common/m_boundary_common.fpp
  • src/simulation/m_body_forces.fpp
  • src/simulation/m_bubbles_EL.fpp
  • (the remaining ~84 AMR files in src/simulation, src/common and src/post_process were only sampled; the diff is ~26k lines)

Findings:

  • medium src/common/include/3dHardcodedIC.fpp: case 306 uses exp((-(...))/0.15) and 1 + 4*exp(...) with a bare 0.15 literal. That literal is default (single) real, not 0.15_wp. In double-precision builds the divisor is 0.15000000596..., not 0.15. The comment says the output is "bit-identical to the analytic-IC path", but that holds only if codegen emits the same unsuffixed literal. If codegen emits _wp literals, the two paths differ at about 1e-8 relative. Suggest 0.15_wp (and check the codegen output), or drop the bit-identical claim. Confidence is moderate because I could not see the codegen output.
  • medium src/common/include/2dHardcodedIC.fpp: case 299 sets only cont%beg and E inside the blob. That is correct only if the patch's other primitives are filled from the background. I did not confirm that for multi-fluid or model_eqns /= 2 cases. The per-cell loop over blob_n with mod is O(cells x blob_n), which is acceptable for a benchmark IC. Low confidence.
  • medium .github/scripts/prebuild-case-optimization.sh: the new slug check is a subshell whose . ./mfc.sh venv exit status is not checked. It runs before the shard/marker logic in every shard. If venv setup fails, the subshell failure only aborts the script when set -e is active. Confirm set -e is on in this script. If it is not, the guard silently passes.
  • medium src/common/m_boundary_common.fpp: s_populate_variables_buffers now returns early when amr_in_fine_advance is true. This skips physical-BC fills as well as MPI fills for every caller, not only the AMR fine path. skip_mpi is a separate dummy that only skips the sendrecv. Verify that the physical BCs at domain faces are actually pre-filled for fine blocks that touch the domain boundary. If they are not, fine blocks at domain edges get stale ghosts. Evidence is only the diff comment "ghosts pre-filled from the coarse level"; I could not check the fill path.

Residual: only partial review of the m_amr_*.fpp, riemann solver and rhs hunks; no defect was confirmed there.

@codecov

codecov Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.95483% with 1873 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.61%. Comparing base (7b16d32) to head (2bd18db).

Files with missing lines Patch % Lines
src/simulation/m_amr_regrid.fpp 63.88% 100 Missing and 99 partials ⚠️
src/simulation/m_amr_transfer.fpp 74.49% 53 Missing and 73 partials ⚠️
src/simulation/m_amr_registers.fpp 62.62% 47 Missing and 61 partials ⚠️
src/simulation/m_amr_cluster.fpp 71.42% 44 Missing and 62 partials ⚠️
src/simulation/m_amr_distribution.fpp 69.36% 56 Missing and 50 partials ⚠️
src/simulation/m_riemann_solver_hlld.fpp 2.75% 105 Missing and 1 partial ⚠️
src/simulation/m_rhs.fpp 50.27% 73 Missing and 18 partials ⚠️
src/simulation/m_amr_exchange.fpp 79.26% 43 Missing and 42 partials ⚠️
src/simulation/m_amr_store.fpp 65.98% 50 Missing and 33 partials ⚠️
src/simulation/m_amr_nest.fpp 67.33% 51 Missing and 30 partials ⚠️
... and 41 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2117      +/-   ##
==========================================
+ Coverage   64.27%   64.61%   +0.34%     
==========================================
  Files          89      109      +20     
  Lines       23733    28860    +5127     
  Branches     3402     4326     +924     
==========================================
+ Hits        15255    18649    +3394     
- Misses       5863     6784     +921     
- Partials     2615     3427     +812     

☔ View full report in Codecov by Harness.
📢 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.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

Lines of Code

File Lines Diff
src/simulation/m_amr_regrid.fpp 917 +917
src/simulation/m_amr_transfer.fpp 827 +827
src/simulation/m_amr_exchange.fpp 749 +749
src/simulation/m_amr_cluster.fpp 585 +585
src/simulation/m_amr_distribution.fpp 552 +552
src/simulation/m_amr_registers.fpp 536 +536
src/simulation/m_amr_store.fpp 475 +475
src/simulation/m_amr_nest.fpp 430 +430
src/simulation/m_amr_state.fpp 403 +403
src/simulation/m_ibm.fpp 2195 +350
src/simulation/m_amr_restart.fpp 303 +303
src/simulation/m_amr_seam.fpp 270 +270
src/simulation/m_amr_frame.fpp 232 +232
src/simulation/m_amr_advance.fpp 227 +227
src/simulation/m_amr_wave.fpp 227 +227
src/post_process/m_data_input.f90 661 +221
src/simulation/m_amr.fpp 210 +210
src/simulation/m_amr_stage.fpp 195 +195
src/simulation/m_amr_ghost.fpp 191 +191
src/post_process/m_data_output.fpp 1366 +156
src/common/m_amr_restart_io.fpp 114 +114
src/simulation/m_phase_timing.fpp 91 +91
src/simulation/m_rhs.fpp 2072 +89
src/simulation/m_global_parameters.fpp 934 +80
src/common/m_box.fpp 39 +39
src/simulation/m_acoustic_src.fpp 551 +35
src/simulation/m_riemann_solver_lf.fpp 553 +32
src/common/m_mpi_common.fpp 1541 +29
src/simulation/m_riemann_solvers.fpp 141 +29
src/simulation/m_start_up.fpp 1465 +29
src/simulation/m_riemann_solver_hllc.fpp 1336 +28
src/simulation/m_igr.fpp 2316 +27
src/simulation/m_weno.fpp 1348 +22
src/common/include/2dHardcodedIC.fpp 453 +21
src/simulation/m_body_forces.fpp 415 +21
src/simulation/m_bubbles_EL.fpp 929 +17
src/common/include/macros.fpp 141 +13
src/common/include/omp_macros.fpp 391 +11
src/post_process/p_main.fpp 61 +7
src/simulation/m_checker.fpp 76 +7
src/simulation/m_riemann_solver_hlld.fpp 199 +7
src/simulation/m_time_steppers.fpp 908 -7
src/simulation/m_hypoelastic.fpp 640 +6
src/simulation/m_riemann_state.fpp 1182 -6
src/common/m_boundary_common.fpp 459 +5
src/common/m_eos.fpp 520 -5
src/common/m_helper_basic.fpp 133 +5
src/common/m_constants.fpp 102 +4
src/common/m_derived_types.fpp 534 +4
src/simulation/m_data_output.fpp 1923 +4
src/common/include/3dHardcodedIC.fpp 250 +3
src/common/m_boundary_io.fpp 319 -3
src/simulation/m_riemann_solver_hll.fpp 624 +3
src/simulation/m_surface_tension.fpp 261 +3
src/common/m_global_parameters_common.fpp 255 -2
src/common/m_variables_conversion.fpp 968 +2
src/post_process/m_start_up.fpp 795 +2
src/simulation/m_cbc.fpp 1112 -2
src/simulation/m_muscl.fpp 189 +2
src/simulation/m_riemann_solver_hypo_hlld.fpp 785 +2
src/common/include/acc_macros.fpp 283 +1
src/common/include/parallel_macros.fpp 252 +1
src/post_process/m_global_parameters.fpp 401 +1
src/pre_process/m_data_output.fpp 677 +1
src/simulation/m_bubbles_EE.fpp 305 +1
src/simulation/m_thinc.fpp 313 +1
Directory Lines Diff
common 10893 +242
pre_process 5073 +1
simulation 38206 +8200
post_process 3827 +387
total 57999 +8830

…L/HLLC/LF/HLLD, copy primitives only for readers; case-optimized builds compile only the selected Riemann solver
…ir input; fail format on ffmt structure warnings; Cray debug bounds checking; Open MPI oversubscribe; stage-guard lint; validator fixes

This branch has not been deployed

No deployments
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