include/pops/coupling/amr/amr_regrid_coupler.hpp Source FileΒΆ

adc_cpp: include/pops/coupling/amr/amr_regrid_coupler.hpp Source File
adc_cpp 0.3.0
Model-free C++23 core for coupled hyperbolic-elliptic systems on adaptive (AMR) meshes, with MPI and GPU (Kokkos) backends
amr_regrid_coupler.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <pops/amr/tagging/cluster.hpp> // berger_rigoutsos, ClusterParams
5#include <pops/amr/regridding/regrid.hpp> // tag_cells, grow_tags
6#include <pops/amr/tagging/tag_box.hpp> // TagBox
8#include <pops/numerics/time/amr/reflux/amr_reflux_mf.hpp> // AmrLevelMP, mf_find_box
12#include <pops/mesh/execution/for_each.hpp> // device_fence (barrier after async parallel_copy under Cuda)
14#include <pops/mesh/layout/refinement.hpp> // coarsen_index
15#include <pops/parallel/comm.hpp> // n_ranks (explicit include, no longer an indirect path)
16
17#include <algorithm>
18#include <utility>
19#include <vector>
20
30
31namespace pops {
32
33// ------------------------------------------------------------------------------------------------
34// REFACTOR (docs/AMR_REGRID_UNION_TAGS_DESIGN.md section 6): amr_regrid_finest split into TWO
35// responsibilities so the multi-block UNION regrid (AmrRuntime::regrid) can re-grid
36// SEVERAL fields on a SINGLE layout imposed from outside (the same one for all blocks):
37// (1) regrid_compute_fine_layout: tags of a parent -> grow -> all_reduce_or (if distributed) ->
38// berger_rigoutsos -> nesting clamp -> (fine BoxArray, DistributionMapping). This is the
39// COMPUTATION of the layout, done ONCE on the union of the tags by the multi-block path.
40// (2) regrid_field_on_layout: takes an IMPOSED (fb, dmap) and rebuilds ONE fine MultiFab (parent
41// interp + fine carry-over), exactly the BODY of the old amr_regrid_finest, but without the
42// layout computation. Called PER BLOCK on the same union layout.
43// amr_regrid_finest stays the CHAINING of (1) then (2) on a SINGLE block -> the single-block path
44// (AmrCouplerMP::regrid) stays BIT-IDENTICAL (same operations, same order).
45// ------------------------------------------------------------------------------------------------
46
53inline std::pair<BoxArray, DistributionMapping> regrid_compute_fine_layout(
54 TagBox grown, const Box2D& pdom, int pk, int margin, bool coarse_replicated = true) {
55 const int PNX = pdom.nx(), PNY = pdom.ny();
56 // DISTRIBUTED COARSE (pk == 0 && !coarse_replicated): each rank only tagged its LOCAL boxes.
57 // Global OR before the clustering -> all ranks start from the SAME tag grid (otherwise the
58 // fine BoxArray would differ per rank -> incompatible dmaps, MPI desynchronized). Replicated: the
59 // grid is already complete on each rank (no-op, all_reduce_or would be the identity).
60 if (pk == 0 && !coarse_replicated)
61 all_reduce_or_inplace(grown.t.data(), static_cast<int>(grown.t.size()));
62 std::vector<Box2D> cl = berger_rigoutsos(grown, ClusterParams{});
63 std::vector<Box2D> fb; // fine patches (fine level coords = parent x2)
64 for (Box2D b : cl) {
65 b.lo[0] = std::max(b.lo[0], margin);
66 b.lo[1] = std::max(b.lo[1], margin);
67 b.hi[0] = std::min(b.hi[0], PNX - 1 - margin);
68 b.hi[1] = std::min(b.hi[1], PNY - 1 - margin);
69 if (b.hi[0] < b.lo[0] || b.hi[1] < b.lo[1])
70 continue;
71 fb.push_back(Box2D{{2 * b.lo[0], 2 * b.lo[1]}, {2 * b.hi[0] + 1, 2 * b.hi[1] + 1}});
72 }
73 if (fb.empty())
74 return {BoxArray{}, DistributionMapping{}}; // nothing to refine
75 BoxArray ba(fb);
76 return {ba, DistributionMapping(static_cast<int>(ba.size()), n_ranks())};
77}
78
87 const MultiFab& par, const MultiFab& old, int pk, int ngf,
88 bool coarse_replicated = true) {
89 MultiFab nU(fb, dmap, old.ncomp(), ngf);
90 const int ncf = nU.ncomp();
91 // DISTRIBUTED parent (de-replicated coarse): par.fab only holds the LOCAL boxes, so
92 // mf_find_box would return -1 for a coarse cell owned by a REMOTE rank and the patch
93 // would stay uninitialized there. We bring the needed parent regions onto a LOCAL
94 // child-coarsen grid (coarsen of the fine BoxArray) via parallel_copy, then interpolate from
95 // it. Replicated parent: par is entirely local, direct read via mf_find_box.
96 const bool par_replicated = (pk != 0) || coarse_replicated;
97 MultiFab parloc;
98 if (!par_replicated) {
99 parloc = MultiFab(coarsen(nU.box_array(), kAmrRefRatio), nU.dmap(), par.ncomp(), 0);
100 parallel_copy(parloc, par);
101 // parallel_copy launches async kernels under Cuda and, at np=1, returns WITHOUT a fence: without this
102 // fence the read of parloc below would read device memory not yet written -> NaN.
103 device_fence();
104 }
105 for (int li = 0; li < nU.local_size(); ++li) {
106 Array4 a = nU.fab(li).array();
107 const Box2D nb = nU.box(li);
108 if (par_replicated) {
109 for (int j = nb.lo[1]; j <= nb.hi[1]; ++j) // 1) interp from the parent (local)
110 for (int i = nb.lo[0]; i <= nb.hi[0]; ++i) {
111 const int pb =
113 if (pb < 0)
114 continue;
115 const ConstArray4 pp = par.fab(pb).const_array();
116 for (int k = 0; k < ncf; ++k)
117 a(i, j, k) = pp(coarsen_index(i, kAmrRefRatio), coarsen_index(j, kAmrRefRatio), k);
118 }
119 } else {
120 const ConstArray4 pp = parloc.fab(li).const_array(); // local child-coarsen grid
121 for (int j = nb.lo[1]; j <= nb.hi[1]; ++j)
122 for (int i = nb.lo[0]; i <= nb.hi[0]; ++i)
123 for (int k = 0; k < ncf; ++k)
124 a(i, j, k) = pp(coarsen_index(i, kAmrRefRatio), coarsen_index(j, kAmrRefRatio), k);
125 }
126 for (int ol = 0; ol < old.local_size(); ++ol) { // 2) carry-over of the fine data
127 const ConstArray4 o = old.fab(ol).const_array();
128 const Box2D inter = nb.intersect(old.box(ol));
129 if (inter.empty())
130 continue;
131 for (int j = inter.lo[1]; j <= inter.hi[1]; ++j)
132 for (int i = inter.lo[0]; i <= inter.hi[0]; ++i)
133 for (int k = 0; k < ncf; ++k)
134 a(i, j, k) = o(i, j, k);
135 }
136 }
137 return nU;
138}
139
150template <class Crit>
151void amr_regrid_finest(std::vector<AmrLevelMP>& L, std::vector<MultiFab>& aux, const Box2D& dom,
152 Crit crit, int grow, int margin, int aux_ncomp = kAuxBaseComps,
153 bool coarse_replicated = true) {
154 const int nlev = static_cast<int>(L.size());
155 if (nlev < 2)
156 return;
157 const int fk = nlev - 1, pk = fk - 1; // fine and its parent
158 const int PNX = dom.nx() << pk, PNY = dom.ny() << pk;
159 const Box2D pdom = Box2D::from_extents(PNX, PNY);
160 TagBox tags = tag_cells(L[pk].U, pdom, crit);
161 TagBox grown = grow_tags(tags, grow, pdom);
162 // (1) Compute the fine layout (tags -> grow [already done] -> all_reduce_or -> clustering -> clamp).
163 auto [fb, dmap] =
164 regrid_compute_fine_layout(std::move(grown), pdom, pk, margin, coarse_replicated);
165 if (fb.size() == 0)
166 return; // nothing to refine: keep the current grid
167 // The new patches INHERIT the ghost width of the level being replaced (not a frozen 1): a
168 // level rebuilt in 2nd-order MUSCL (Minmod / VanLeer) carries 2 ghosts, which the regrid must
169 // preserve, otherwise the reconstruction would read out of bounds after re-refinement.
170 const int ngf = L[fk].U.n_grow();
171 // (2) Re-grid the field U on this layout (parent interp + fine carry-over): SAME body as before,
172 // so the single-block path stays BIT-IDENTICAL (chaining of (1) then (2) on a single block).
173 L[fk].U = regrid_field_on_layout(fb, dmap, L[pk].U, L[fk].U, pk, ngf, coarse_replicated);
174 aux[fk] = MultiFab(L[fk].U.box_array(), L[fk].U.dmap(), aux_ncomp, 1); // stable address
175 L[fk].aux = &aux[fk];
176}
177
178} // namespace pops
Umbrella for the AMR MultiFab stack: includes the numerics/time sub-headers in dependency order (flux...
Box2D: the integer index space of a 2D cell-centered Cartesian grid.
BoxArray: the set of boxes tiling a level (disjoint, covering).
Ordered list of boxes tiling a level.
Definition box_array.hpp:22
int size() const
Number of boxes in the tiling.
Definition box_array.hpp:42
Owning MPI rank of each box, indexed by GLOBAL box index (parallel to a BoxArray).
Definition distribution_mapping.hpp:19
ConstArray4 const_array() const
READ handle (POD device-copyable) over this Fab. Valid as long as the Fab lives.
Definition fab2d.hpp:96
Array4 array()
WRITE handle (POD device-copyable) over this Fab. Valid as long as the Fab lives.
Definition fab2d.hpp:91
Field distributed over a level: decomposition (BoxArray) + distribution (DistributionMapping) + ncomp...
Definition multifab.hpp:33
Fab2D & fab(int li)
Local fab at index li (0 <= li < local_size()), for writing.
Definition multifab.hpp:67
int ncomp() const
Number of components.
Definition multifab.hpp:60
const DistributionMapping & dmap() const
GLOBAL distribution (owner rank per box).
Definition multifab.hpp:58
const BoxArray & box_array() const
GLOBAL decomposition of the level (all boxes, all ranks).
Definition multifab.hpp:56
const Box2D & box(int li) const
VALID box of local fab li.
Definition multifab.hpp:71
int local_size() const
Number of fabs OWNED by this rank (bound on local indices).
Definition multifab.hpp:65
Berger-Rigoutsos clustering: tag grid -> small set of boxes covering the tagged cells.
Parallel seam: minimal MPI abstraction (rank/size + collectives) with serial fallback.
DistributionMapping: maps each box (by global index) to its owning MPI rank.
for_each_cell and reductions: the parallelism SEAM over the cells of a Box2D; sync_host / sync_device...
MultiFab: a field DISTRIBUTED over a level (equivalent of AMReX's MultiFab).
Definition amr_hierarchy.hpp:29
int mf_find_box(const MultiFab &mf, int I, int J)
Definition amr_subcycling.hpp:231
constexpr int kAuxBaseComps
Definition state.hpp:147
int n_ranks()
Definition comm.hpp:139
void all_reduce_or_inplace(char *, int)
Definition comm.hpp:156
BoxArray coarsen(const BoxArray &ba, int r)
Coarsens each box of the BoxArray by a ratio r (coarsen box by box, order preserved).
Definition refinement.hpp:39
std::pair< BoxArray, DistributionMapping > regrid_compute_fine_layout(TagBox grown, const Box2D &pdom, int pk, int margin, bool coarse_replicated=true)
Compute the fine layout (BoxArray + DistributionMapping) of a Berger-Rigoutsos regrid from the grown ...
Definition amr_regrid_coupler.hpp:53
void device_fence()
Device barrier: waits for in-flight kernels to finish before a HOST access to unified memory.
Definition kokkos_env.hpp:43
constexpr int kAmrRefRatio
The native AMR refinement ratio between two consecutive levels.
Definition refinement_ratio.hpp:30
void amr_regrid_finest(std::vector< AmrLevelMP > &L, std::vector< MultiFab > &aux, const Box2D &dom, Crit crit, int grow, int margin, int aux_ncomp=kAuxBaseComps, bool coarse_replicated=true)
Regrid the finest level (L.back()) by Berger-Rigoutsos on the criterion crit applied to the parent: r...
Definition amr_regrid_coupler.hpp:151
void parallel_copy(MultiFab &dst, const MultiFab &src)
Copies the valid regions that OVERLAP from src to dst (same indices, no shift).
Definition refinement.hpp:50
std::vector< Box2D > berger_rigoutsos(const TagBox &tags, const ClusterParams &p={})
Cluster a TagBox into boxes covering the tagged cells (Berger-Rigoutsos), then final chop.
Definition cluster.hpp:185
TagBox grow_tags(const TagBox &in, int n, const Box2D &domain)
Grows the tags by n cells (square neighborhood), staying within the domain.
Definition regrid.hpp:58
POPS_HD int coarsen_index(int a, int r)
Index of the coarse cell containing the fine cell a (FLOOR division by r, handles a < 0).
Definition refinement.hpp:34
MultiFab regrid_field_on_layout(const BoxArray &fb, const DistributionMapping &dmap, const MultiFab &par, const MultiFab &old, int pk, int ngf, bool coarse_replicated=true)
Rebuild ONE fine MultiFab on the IMPOSED layout fb / dmap (the same one for all blocks in multi-block...
Definition amr_regrid_coupler.hpp:86
TagBox tag_cells(const MultiFab &mf, const Box2D &domain, Crit crit)
Marks the valid cells where the predicate is true, on a TagBox covering the domain.
Definition regrid.hpp:40
AMR inter-level transfer operators (integer ratio r) + parallel_copy.
Single source of truth for the native AMR refinement ratio.
Dynamic regrid: (re)builds a fine level from the tagging of a coarse level.
WRITE POD handle (raw pointer + strides) over a Fab2D buffer, indexed by (i, j, c) IN GLOBAL INDICES ...
Definition fab2d.hpp:29
2D integer index space, cell-centered.
Definition box2d.hpp:37
bool empty() const
true if the box contains no cell (hi < lo in one direction).
Definition box2d.hpp:58
static Box2D from_extents(int nx, int ny)
Box [0, nx-1] x [0, ny-1] covering nx*ny cells from the index origin.
Definition box2d.hpp:42
POPS_HD int nx() const
Width (direction 0). POPS_HD (called from Geometry::dx() in a device kernel).
Definition box2d.hpp:50
int hi[2]
Definition box2d.hpp:39
POPS_HD int ny() const
Height (direction 1). POPS_HD (called from Geometry::dy() in a device kernel).
Definition box2d.hpp:52
int lo[2]
Definition box2d.hpp:38
Box2D intersect(const Box2D &o) const
Intersection of the two boxes (possibly empty: hi < lo if they do not overlap).
Definition box2d.hpp:95
Berger-Rigoutsos clustering parameters (configuration object).
Definition cluster.hpp:31
READ-only handle (const counterpart of Array4): same layout and same contract (POD device-copyable,...
Definition fab2d.hpp:44
Dense grid of 0/1 markers over a box, input to Berger-Rigoutsos clustering.
Definition tag_box.hpp:35
std::vector< char > t
Definition tag_box.hpp:37
TagBox: dense grid of markers (0/1) over a region, input to Berger-Rigoutsos clustering.
Base scalar types and the POPS_HD macro (host+device portability).