Skip to content

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

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

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 lanes ran on commits just before the head. Since then: an unused dummy argument removed, the no-MPI reduction
fallback from #2118 (not compiled in MPI builds), comments, two compiler-specific build flags and one test tolerance
(below). CI runs 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 (not 25.7), -mp=gpu: fort2 segfaults while writing the -Minfo report for m_amr_exchange
    (reproduced in the CI container; the generated code is not involved). That file is built with -Mnoinfo under NVHPC
    OpenMP.
  • 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.

@sbryngelson
sbryngelson force-pushed the amr branch 5 times, most recently from 88c7622 to fb72d12 Compare November 3, 2026 18:00
…L/HLLC/LF/HLLD, copy primitives only for readers; case-optimized builds compile only the selected Riemann solver
…scalar reductions return their input; fail format on ffmt structure warnings; Cray debug bounds checking; Open MPI oversubscribe; stage-guard lint; validator fixes
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

Lines of Code

File Lines Diff
src/simulation/m_amr_exchange.fpp 1273 +1273
src/simulation/m_amr_regrid.fpp 917 +917
src/simulation/m_amr_transfer.fpp 827 +827
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 381 +381
src/simulation/m_ibm.fpp 2195 +350
src/simulation/m_amr_restart.fpp 303 +303
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 660 +221
src/simulation/m_amr.fpp 209 +209
src/simulation/m_amr_stage.fpp 195 +195
src/post_process/m_data_output.fpp 1412 +155
src/common/m_amr_restart_io.fpp 114 +114
src/simulation/m_phase_timing.fpp 91 +91
src/simulation/m_rhs.fpp 2060 +89
src/simulation/m_global_parameters.fpp 859 +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 1523 +29
src/simulation/m_riemann_solvers.fpp 141 +29
src/simulation/m_start_up.fpp 1300 +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 1656 +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 903 -7
src/simulation/m_hypoelastic.fpp 640 +6
src/simulation/m_riemann_state.fpp 1182 -6
src/common/m_boundary_common.fpp 453 +5
src/common/m_eos.fpp 520 -5
src/common/m_helper_basic.fpp 123 +5
src/common/m_constants.fpp 93 +4
src/common/m_derived_types.fpp 488 +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 266 +3
src/common/m_global_parameters_common.fpp 248 -2
src/common/m_variables_conversion.fpp 968 +2
src/post_process/m_start_up.fpp 794 +2
src/simulation/m_cbc.fpp 1112 -2
src/simulation/m_data_output.fpp 1581 +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 400 +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 10786 +242
pre_process 5023 +1
simulation 36818 +8238
post_process 3870 +386
total 56497 +8867

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