Skip to content

Commit 7e0d3ac

Browse files
committed
AMR regrid hysteresis (amr_snap, default 0 = off): a new box within amr_snap coarse cells per face of a live block of the same level takes the live block's box, applied all-or-none under same-level disjointness and proper nesting. The [amr-keep] probe showed 6 of 10 rebuilds on the S0 deck re-create every block because the tag envelope drifted one cell and every tile shifted with it; snapped boxes are identical, and when every box snaps the existing boxes_unchanged fast path skips the rebuild. Validator bounds amr_snap <= amr_buf - 2 so two cells of tag padding survive every snap; the cadence audit is the runtime coverage check. On the S0 deck at np8, amr_snap = 2 turns 7 of 11 rebuilds into no-ops and the differenced step falls 15-18% with every skew wait roughly halved (ledger 109).
1 parent 62bd10e commit 7e0d3ac

6 files changed

Lines changed: 78 additions & 0 deletions

File tree

docs/documentation/case.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ To restart the simulation from $k$-th time step, see @ref running "Restarting Ca
731731
| `amr_regrid_int` | Integer | Steps between AMR regrid events (0 = static block, i.e. NO adaptivity). The tag sweep is per-cell and flat in block count, so a larger interval is cheap: 8 measured 1.39x faster than 2 in 3D. Raise it unless the refined feature moves quickly |
732732
| `amr_tag_eps` | Real | Relative density-gradient threshold for AMR refinement tagging (default 0.1) |
733733
| `amr_buf` | Integer | Coarse-cell padding around tagged cells when regridding (default 3) |
734+
| `amr_snap` | Integer | Regrid hysteresis: a new box within this many coarse cells per face of a live block of the same level takes the live block's box, so a feature drifting by a cell or two does not re-create every block; the whole regrid then skips when every box snaps. 0 (default) disables it; must be <= `amr_buf` - 2 |
734735
| `amr_subcycle` | Logical | Advance the coarse level at the case dt and the fine level at dt/2 (two substeps; Berger-Colella refluxing). Requires `amr`; incompatible with `cfl_dt`. |
735736
| `amr_device_pack` | Logical | Pack and unpack the per-stage coarse-patch gather (F1/F2) over the plan's flat transfer list instead of one launch per transfer. The sends fuse to one kernel per family per stage; the receives fuse per contiguous (box, peer) run, so measured at np=8 the pack dispatches fall about 89x and the unpack about 4.3x. Wire bytes and floating-point values are unchanged. Requires `amr`; incompatible with `amr_subcycle`; the non-polytropic QBMM pb/mv twin keeps its per-transfer path. Default F in Fortran; the toolchain turns it on with the batching default when `amr_max_grid_size` is pinned at 64 or below (it pays where blocks are many and small: -9 % wall at cap 32, -0.14 s/step at cap 64, +4.5 % at cap 96). |
736737
| `amr_batched_gather` | Logical | Consume the per-stage coarse-patch gather (F1/F2) for all of a rank's blocks at once: the gathered patches live in one pool, and each wave runs one fused own-copy, one fused unpack and one batched ghost fill instead of one set of launches per block. Wire bytes and floating-point values are unchanged. Requires `amr` and `amr_device_pack`; incompatible with `amr_subcycle`. Default F. |
@@ -1014,6 +1015,7 @@ visualization output is future work.
10141015
| `amr_regrid_int` | Integer | Coarse steps between regrid events (0 = static block) |
10151016
| `amr_tag_eps` | Real | Normalized density-gradient threshold for refinement tagging; must be > 0 when `amr_regrid_int > 0` (default 0.1) |
10161017
| `amr_buf` | Integer | Coarse-cell padding around tagged cells; must be >= 1 when `amr_regrid_int > 0` (default 3) |
1018+
| `amr_snap` | Integer | Regrid hysteresis in coarse cells per face (0 = off, default); requires `amr_snap <= amr_buf - 2` |
10171019
| `amr_subcycle` | Logical | Advance fine level at dt/2 (two substeps per coarse step) with Berger–Colella refluxing |
10181020
| `amr_max_blocks` | Integer | Number of fixed refined-block slots preallocated (each max-block sized; ~N x device memory); must be >= 1 (default 4) |
10191021
| `amr_max_grid_size` | Integer | Absolute cap on a refined block's coarse-cell extent per dimension, the AMReX max_grid_size concept; must be >= 2 when set (default 0). With 0 the cap is derived from the decomposition and so shrinks as ranks are added, which tiles a fixed feature into more blocks the further you scale and makes the box set depend on the rank count. Setting it pins the cap, so the box set is identical at every rank count. The value may exceed half a rank subdomain: the solver scratch is then sized to the cap rather than to the subdomain, so per-rank memory grows as the cap raised to the number of dimensions |

src/simulation/m_amr_regrid.fpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,7 @@ contains
13521352
call s_phase_tic(PH_RGSHAPE); call s_amr_regrid_shape_boxes(boxes, nboxes); call s_phase_toc(PH_RGSHAPE)
13531353
if (nboxes == 0) return ! every box was confined to the domain margin
13541354
call s_amr_regrid_nest_children(boxes, nboxes, box_level)
1355+
if (amr_snap > 0) call s_amr_regrid_snap_boxes(boxes, nboxes, box_level)
13551356
call s_amr_check_box_caps(boxes, nboxes, box_level) ! invariant: no box may exceed its level's slot cap
13561357
call s_amr_check_box_disjoint(boxes, nboxes, box_level) ! invariant: same-level boxes are pairwise disjoint
13571358
call s_amr_regrid_boxes_unchanged(boxes, nboxes, box_level, same)
@@ -1574,6 +1575,71 @@ contains
15741575
15751576
end subroutine s_amr_regrid_cluster_tags
15761577
1578+
!> Regrid hysteresis (amr_snap > 0): every new box within amr_snap coarse cells per face of a LIVE block of the same level takes
1579+
!! that block's box. A feature drifting a cell between regrids otherwise shifts every tile of its envelope by that cell and
1580+
!! re-creates every block (the [amr-keep] probe: 6 of 10 rebuilds on the S0 deck had no box identical to a live one); snapped
1581+
!! boxes are identical, and when every box snaps s_amr_regrid_boxes_unchanged skips the rebuild outright. Coverage: a new box is
1582+
!! the tags padded by amr_buf, so a snap of <= amr_snap <= amr_buf - 2 cells (the validator's bound) keeps >= 2 cells of padding
1583+
!! on every face; the cadence audit ([amr-cad] escaped) is the runtime check. All-or-none: the snapped set must stay pairwise
1584+
!! disjoint per level and every level >= 2 box must stay inside a single parent box by amr_cpat_mar (the nester's window), else
1585+
!! the whole snap is dropped and the fresh boxes stand. Replicated inputs, so every rank decides alike.
1586+
impure subroutine s_amr_regrid_snap_boxes(boxes, nboxes, box_level)
1587+
1588+
type(t_box), intent(inout) :: boxes(:)
1589+
integer, intent(in) :: nboxes, box_level(:)
1590+
type(t_box), allocatable :: snapped(:)
1591+
integer :: k, kk, ks, npar, nsnap, mlo(3), mhi(3)
1592+
logical :: ok
1593+
1594+
allocate (snapped(nboxes)); snapped(1:nboxes) = boxes(1:nboxes)
1595+
nsnap = 0
1596+
do k = 1, nboxes
1597+
do ks = l0_slot_off + 1, amr_num_blocks
1598+
if (amr_block_level(ks) /= box_level(k)) cycle
1599+
if (all(abs(amr_region_lo_all(:,ks) - boxes(k)%lo) <= amr_snap) .and. all(abs(amr_region_hi_all(:, &
1600+
& ks) - boxes(k)%hi) <= amr_snap)) then
1601+
if (any(amr_region_lo_all(:,ks) /= boxes(k)%lo) .or. any(amr_region_hi_all(:, &
1602+
& ks) /= boxes(k)%hi)) nsnap = nsnap + 1
1603+
snapped(k)%lo = amr_region_lo_all(:,ks); snapped(k)%hi = amr_region_hi_all(:,ks)
1604+
exit
1605+
end if
1606+
end do
1607+
end do
1608+
ok = nsnap > 0
1609+
! same-level disjointness of the snapped set
1610+
do k = 1, nboxes
1611+
if (.not. ok) exit
1612+
do kk = k + 1, nboxes
1613+
if (box_level(kk) /= box_level(k)) cycle
1614+
if (all(snapped(k)%lo <= snapped(kk)%hi .and. snapped(kk)%lo <= snapped(k)%hi)) then
1615+
ok = .false.; exit
1616+
end if
1617+
end do
1618+
end do
1619+
! proper nesting: a level >= 2 box lies inside exactly one parent-level box, inset by the nesting margin
1620+
do k = 1, nboxes
1621+
if (.not. ok) exit
1622+
if (box_level(k) < 2) cycle
1623+
npar = 0
1624+
do kk = 1, nboxes
1625+
if (box_level(kk) /= box_level(k) - 1) cycle
1626+
if (.not. all(snapped(k)%lo <= snapped(kk)%hi .and. snapped(kk)%lo <= snapped(k)%hi)) cycle
1627+
npar = npar + 1
1628+
mlo = snapped(kk)%lo; mhi = snapped(kk)%hi
1629+
mlo(1) = mlo(1) + amr_cpat_mar; mhi(1) = mhi(1) - amr_cpat_mar
1630+
if (n_glb > 0) then; mlo(2) = mlo(2) + amr_cpat_mar; mhi(2) = mhi(2) - amr_cpat_mar; end if
1631+
if (p_glb > 0) then; mlo(3) = mlo(3) + amr_cpat_mar; mhi(3) = mhi(3) - amr_cpat_mar; end if
1632+
if (any(snapped(k)%lo < mlo) .or. any(snapped(k)%hi > mhi)) ok = .false.
1633+
end do
1634+
if (npar /= 1) ok = .false.
1635+
end do
1636+
if (ok) boxes(1:nboxes) = snapped(1:nboxes)
1637+
if (rank_time_wrt .and. proc_rank == 0) write (0, '(A,I0,A,I0,A,L1)') '[amr-snap] boxes ', nboxes, ' snapped ', nsnap, &
1638+
& ' applied ', ok
1639+
deallocate (snapped)
1640+
1641+
end subroutine s_amr_regrid_snap_boxes
1642+
15771643
!> Regrid phase 3: pad + clamp + size-cap each box, clip it clear of the acoustic/Lagrangian supports and the active window,
15781644
!! expand it over immersed bodies, then tile oversized boxes (non-IB) or merge overlapping ones (IB).
15791645
impure subroutine s_amr_regrid_shape_boxes(boxes, nboxes)

src/simulation/m_global_parameters.fpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,7 @@ contains
628628
amr_regrid_int = 0
629629
amr_tag_eps = 0.1_wp
630630
amr_buf = 3
631+
amr_snap = 0
631632
amr_subcycle = .false.
632633
amr_device_pack = .false.
633634
amr_batched_gather = .false.

toolchain/mfc/case_validator.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,6 +1927,12 @@ def check_amr(self):
19271927
(amr_regrid_int or 0) > 0 and amr_buf is not None and amr_buf < 1,
19281928
"amr_buf must be >= 1 when amr_regrid_int > 0",
19291929
)
1930+
amr_snap = self.get("amr_snap")
1931+
self.prohibit(amr_snap is not None and amr_snap < 0, "amr_snap must be >= 0")
1932+
self.prohibit(
1933+
(amr_snap or 0) > 0 and amr_buf is not None and amr_snap > amr_buf - 2,
1934+
"amr_snap must leave two cells of amr_buf (amr_snap <= amr_buf - 2): a snapped box keeps at least that much " "tag padding on every face",
1935+
)
19301936
# advisory, not a prohibit: at CFL <= 1 a feature front can cross up to one cell per step, so
19311937
# amr_buf < amr_regrid_int risks features outrunning the tag buffer between regrids; low-CFL
19321938
# cases are legitimately below this worst-case bound (several suite goldens run int=5, buf=2-3).

toolchain/mfc/params/definitions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ def _load():
703703
_r("amr_regrid_int", INT)
704704
_r("amr_tag_eps", REAL)
705705
_r("amr_buf", INT)
706+
_r("amr_snap", INT)
706707
_r("amr_subcycle", LOG)
707708
_r("amr_device_pack", LOG)
708709
_r("amr_batched_gather", LOG)
@@ -1459,6 +1460,7 @@ def _decl(targets: set, *names: str) -> None:
14591460
"amr_regrid_int",
14601461
"amr_tag_eps",
14611462
"amr_buf",
1463+
"amr_snap",
14621464
"amr_subcycle",
14631465
"amr_device_pack",
14641466
"amr_batched_gather",

toolchain/mfc/params/descriptions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
"amr_regrid_int": "Steps between AMR regrid events (0 = static block)",
127127
"amr_tag_eps": "Density-gradient tagging threshold: a cell is tagged when max over axes of |rho(i+1)-rho(i-1)|/(2 rho_i) exceeds it (dimensionless; halves per grid-doubling for a smooth field)",
128128
"amr_buf": "Coarse-cell padding around tagged cells when regridding",
129+
"amr_snap": "Regrid hysteresis in coarse cells per face: a new box this close to a live same-level block takes its box (0 = off)",
129130
"amr_subcycle": "Advance the coarse level at the case dt and the fine level at dt/2 (two substeps; Berger-Colella refluxing)",
130131
"amr_batched_gather": "Consume the per-stage coarse-patch gather (F1/F2) for all of a rank's blocks from one pooled patch: one own-copy, one unpack, one ghost fill per wave, not per block",
131132
"amr_device_pack": "Pack/unpack the per-stage coarse-patch gather (F1/F2) as ONE fused device kernel per family per stage over the plan's flat transfer list, not one launch per transfer",

0 commit comments

Comments
 (0)