include/pops/numerics/time/amr/levels/amr_patch_range.hpp Source FileΒΆ

adc_cpp: include/pops/numerics/time/amr/levels/amr_patch_range.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_patch_range.hpp
Go to the documentation of this file.
1#pragma once
2
5#include <pops/parallel/comm.hpp> // all_reduce_sum_inplace (distributed multi-patch reflux)
6
7#include <algorithm>
8
32
33namespace pops {
34
35static_assert(kAmrRefRatio == 2, "ratio-2-structural kernels below assume kAmrRefRatio == 2");
36
37// PatchRange (review, point 5: role promoted to a type). COARSE footprint [I0..I1]x[J0..J1]
38// of a fine patch under ratio 2: I0 = lo/2, I1 = (hi-1)/2 (aligned patch, lo even / hi odd).
39// Centralizes the footprint computation repeated inline in average_down, the coverage and the
40// init of the reflux registers. NB: this is NOT Box2D::coarsen (floor of both bounds) but the
41// historical upper bound (hi-1)/2; the exact arithmetic is preserved (bit-identical).
42struct PatchRange {
43 int I0, I1, J0, J1;
44 explicit PatchRange(const Box2D& fine)
45 : I0(fine.lo[0] / 2),
46 I1((fine.hi[0] - 1) / 2),
47 J0(fine.lo[1] / 2),
48 J1((fine.hi[1] - 1) / 2) {}
49 Box2D box() const { return Box2D{{I0, J0}, {I1, J1}}; } // coarse footprint (cells)
50};
51
52// multi-box fine ghosts from the coarse (space+time interp), THEN fill_boundary
53// (fine-fine) will overwrite the ghosts covered by a neighbor box. coarse mono-box.
54inline void mf_fill_fine_ghosts_multi(MultiFab& Uf, const MultiFab& Uc_old, const MultiFab& Uc_new,
55 Real frac) {
57 const int nc = Uf.ncomp();
58 const ConstArray4 co = Uc_old.fab(0).const_array();
59 const ConstArray4 cn = Uc_new.fab(0).const_array();
60 for (int li = 0; li < Uf.local_size(); ++li) {
61 Array4 f = Uf.fab(li).array();
62 const Box2D v = Uf.box(li), g = Uf.fab(li).grown_box();
63 for (int j = g.lo[1]; j <= g.hi[1]; ++j)
64 for (int i = g.lo[0]; i <= g.hi[0]; ++i)
65 if (!v.contains(i, j))
66 fill_cf_ghost_cell(f, co, cn, i, j, nc, frac);
67 }
68}
69
70namespace detail {
71// NAMED device-clean functor (extended lambda -> trips nvcc cross-TU): fine -> coarse average
72// (ratio 2) of a fine box over the PatchRange coarse footprint. Body bit-identical to the old
73// lambda of mf_average_down_multi.
77 int nc;
78 POPS_HD void operator()(int I, int J) const {
79 for (int k = 0; k < nc; ++k)
80 c(I, J, k) = Real(0.25) * (f(2 * I, 2 * J, k) + f(2 * I + 1, 2 * J, k) +
81 f(2 * I, 2 * J + 1, k) + f(2 * I + 1, 2 * J + 1, k));
82 }
83};
84} // namespace detail
85
86// fine -> coarse average over the footprint of EACH fine box (multi-box).
87inline void mf_average_down_multi(const MultiFab& Uf, MultiFab& Uc) {
88 const int nc = Uc.ncomp();
89 Array4 c = Uc.fab(0).array();
90 for (int li = 0; li < Uf.local_size(); ++li) {
91 const ConstArray4 f = Uf.fab(li).const_array();
92 const PatchRange pr(Uf.box(li));
94 }
95}
96
97// PURELY LOCAL periodic fill of the ghosts of a mono-box coarse (self-folding).
98// Equivalent to a periodic fill_boundary for a single box, but WITHOUT the MPI plan: serves
99// the REPLICATED coarse (per-rank copy), whose per-rank DistributionMapping would violate the
100// replicated-metadata assumption of fill_boundary. Reads valid cells (indices
101// folded into [0,N)) and writes only ghosts: no read/write race.
102inline void fill_periodic_local(MultiFab& mf, const Box2D& dom) {
103 device_fence();
104 const int nc = mf.ncomp(), NX = dom.nx(), NY = dom.ny();
105 auto wrap = [](int x, int n) { return (x % n + n) % n; };
106 for (int li = 0; li < mf.local_size(); ++li) {
107 Array4 a = mf.fab(li).array();
108 const Box2D g = mf.fab(li).grown_box();
109 for (int j = g.lo[1]; j <= g.hi[1]; ++j)
110 for (int i = g.lo[0]; i <= g.hi[0]; ++i)
111 if (i < 0 || i >= NX || j < 0 || j >= NY)
112 for (int k = 0; k < nc; ++k)
113 a(i, j, k) = a(wrap(i, NX), wrap(j, NY), k);
114 }
115}
116
117// FluxRegister (review, point 2: role promoted to a type). Coarse register with GLOBAL indexing
118// over a REGION (box, with origin), to lift average_down (overwrite of covered cells, set)
119// and reflux (addition to bordering cells, bounded add) across ranks.
120// Each rank fills its LOCAL contributions (0 elsewhere), gather() sums them via
121// all_reduce_sum_inplace, then each rank reads the total via at(). In serial all_reduce is
122// the identity -> bit-for-bit identical. Region with origin (0,0) = full coarse grid; region
123// = bounding box = multi-box average_down path. Same index formulas as before.
125 int I0, J0, NX, NY, nc;
126 std::vector<Real> buf;
127 FluxRegister(const Box2D& region, int ncomp)
128 : I0(region.lo[0]),
129 J0(region.lo[1]),
130 NX(region.hi[0] - region.lo[0] + 1),
131 NY(region.hi[1] - region.lo[1] + 1),
132 nc(ncomp),
133 buf(static_cast<std::size_t>(NX) * NY * ncomp, Real(0)) {}
134 std::size_t idx(int I, int J, int k) const {
135 return (static_cast<std::size_t>(J - J0) * NX + (I - I0)) * nc + k;
136 }
137 bool in(int I, int J) const { return I >= I0 && I < I0 + NX && J >= J0 && J < J0 + NY; }
138 void set(int I, int J, int k, Real v) { buf[idx(I, J, k)] = v; } // overwrite (average_down)
139 void add(int I, int J, int k, Real v) { // bordering addition (reflux)
140 if (in(I, J))
141 buf[idx(I, J, k)] += v;
142 }
143 Real at(int I, int J, int k) const { return buf[idx(I, J, k)]; }
144 void gather() { all_reduce_sum_inplace(buf.data(), static_cast<int>(buf.size())); }
145};
146
147// CoverageMask (review, point 2: "coverage" part of CoarseFineInterface). Coarse mask
148// over a REGION saying which cells are SHADOWED by a fine patch. Built on the
149// GLOBAL box_array (known to all ranks) -> MPI-safe. mark(box) marks the coarse footprint
150// of a patch (intersected with the region); covered(I,J) is bounded (false outside region).
151// This is what prevents the double-reflux of a fine-fine joint. Same cells as before.
153 int I0, J0, NX, NY;
154 std::vector<char> cov;
155 explicit CoverageMask(const Box2D& region)
156 : I0(region.lo[0]),
157 J0(region.lo[1]),
158 NX(region.hi[0] - region.lo[0] + 1),
159 NY(region.hi[1] - region.lo[1] + 1),
160 cov(static_cast<std::size_t>(NX) * NY, 0) {}
161 void mark(const Box2D& b) { // marks the cells of b intersected with the region
162 const int i0 = std::max(b.lo[0], I0), i1 = std::min(b.hi[0], I0 + NX - 1);
163 const int j0 = std::max(b.lo[1], J0), j1 = std::min(b.hi[1], J0 + NY - 1);
164 for (int J = j0; J <= j1; ++J)
165 for (int I = i0; I <= i1; ++I)
166 cov[(static_cast<std::size_t>(J - J0) * NX) + (I - I0)] = 1;
167 }
168 bool covered(int I, int J) const {
169 if (I < I0 || I >= I0 + NX || J < J0 || J >= J0 + NY)
170 return false;
171 return cov[(static_cast<std::size_t>(J - J0) * NX) + (I - I0)] != 0;
172 }
173};
174
175// SubcyclingSchedule (review, point 5: role promoted to a type). Berger-Oliger cadence of a
176// level: temporal refinement ratio r, substep dt/r, and temporal position frac(s)
177// = s/r of substep s in the parent step. Centralizes the `const int r = kAmrRefRatio`, `dt / r` and
178// `Real(s) / r` scattered across the subcycling loops. Arithmetic strictly preserved:
179// dt_sub(dt) == dt / r and frac(s) == Real(s) / r at the same types, thus bit-identical.
181 int r;
182 explicit SubcyclingSchedule(int ratio = kAmrRefRatio) : r(ratio) {}
183 int count() const { return r; } // number of substeps
184 Real dt_sub(Real dt) const { return dt / r; } // fine step = parent step / r
185 Real frac(int s) const { return Real(s) / r; } // temporal position of substep s
186};
187
188// CoarseFineInterface (review, point 2). The coarse-fine interface of a level: coverage
189// (which coarse cells are shadowed by a fine patch, via CoverageMask) + bordering ROUTING
190// of the reflux (which coarse cell borders which fine-patch face, and the conservative
191// correction poured into it). Centralizes the two inline logics previously duplicated
192// in amr_step_2level_multipatch and subcycle_level_mp. Builds the mask on the GLOBAL
193// box_array() of the fine patches (MPI-safe). route_reflux is a template on the register
194// type (Reg / RegMP, same field layout): named function (no generic lambda), thus
195// safe under nvcc. Arithmetic bit-identical to the previous bodies.
198 // region = coarse footprint of the level (origin (0,0), dims NX x NY); fine_ba = GLOBAL
199 // fine patches (all the boxes, known to all ranks). We mark the coarse PatchRange footprint
200 // of each patch.
201 CoarseFineInterface(const Box2D& coarse_region, const BoxArray& fine_ba) : cmask(coarse_region) {
202 for (int g = 0; g < fine_ba.size(); ++g)
203 cmask.mark(PatchRange(fine_ba[g]).box());
204 }
205 bool covered(int I, int J) const { return cmask.covered(I, J); }
206
207 // Pours the reflux correction of ONE fine patch (register g, parent coords) into the
208 // coarse register ref: on each BORDERING coarse cell not covered by another patch,
209 // (time-integrated fine flux - coarse flux x dt) / dx|dy. Same formulas, same order
210 // (left/right in x, bottom/top in y) as the original inline bodies.
211 template <class Reg>
212 void route_reflux(const Reg& g, Real dx, Real dy, Real dt, FluxRegister& ref, int nc) const {
213 for (int J = g.J0; J <= g.J1; ++J)
214 for (int k = 0; k < nc; ++k) {
215 if (!covered(g.I0 - 1, J))
216 ref.add(g.I0 - 1, J, k,
217 -(g.fL[(J - g.J0) * nc + k] - g.cL[(J - g.J0) * nc + k] * dt) / dx);
218 if (!covered(g.I1 + 1, J))
219 ref.add(g.I1 + 1, J, k,
220 +(g.fR[(J - g.J0) * nc + k] - g.cR[(J - g.J0) * nc + k] * dt) / dx);
221 }
222 for (int I = g.I0; I <= g.I1; ++I)
223 for (int k = 0; k < nc; ++k) {
224 if (!covered(I, g.J0 - 1))
225 ref.add(I, g.J0 - 1, k,
226 -(g.fB[(I - g.I0) * nc + k] - g.cB[(I - g.I0) * nc + k] * dt) / dy);
227 if (!covered(I, g.J1 + 1))
228 ref.add(I, g.J1 + 1, k,
229 +(g.fT[(I - g.I0) * nc + k] - g.cT[(I - g.I0) * nc + k] * dt) / dy);
230 }
231 }
232};
233
234} // namespace pops
Basic MultiFab building blocks of an AMR step: AmrTimeMethod enum, device-clean functors and advance ...
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
const Box2D & grown_box() const
Grown box (valid + ng ghosts) = actual memory footprint.
Definition fab2d.hpp:76
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 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
Parallel seam: minimal MPI abstraction (rank/size + collectives) with serial fallback.
Definition amr_hierarchy.hpp:29
void for_each_cell(const Box2D &b, F f)
Applies f to EACH cell (i, j) of box b (bounds inclusive), via Kokkos::parallel_for (Serial / OpenMP ...
Definition for_each.hpp:138
void mf_average_down_multi(const MultiFab &Uf, MultiFab &Uc)
Definition amr_patch_range.hpp:87
double Real
Definition types.hpp:30
void all_reduce_sum_inplace(double *, int)
Definition comm.hpp:155
void fill_cf_ghost_cell(Array4 f, const ConstArray4 &co, const ConstArray4 &cn, int i, int j, int nc, Real frac, Real pos_floor=Real(0), int pos_comp=0)
Definition amr_flux_helpers.hpp:194
void device_fence()
Device barrier: waits for in-flight kernels to finish before a HOST access to unified memory.
Definition kokkos_env.hpp:43
void fill_periodic_local(MultiFab &mf, const Box2D &dom)
Definition amr_patch_range.hpp:102
constexpr int kAmrRefRatio
The native AMR refinement ratio between two consecutive levels.
Definition refinement_ratio.hpp:30
void mf_fill_fine_ghosts_multi(MultiFab &Uf, const MultiFab &Uc_old, const MultiFab &Uc_new, Real frac)
Definition amr_patch_range.hpp:54
Single source of truth for the native AMR refinement ratio.
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
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
bool contains(int i, int j) const
true if cell (i, j) is inside the box (lo/hi bounds inclusive).
Definition box2d.hpp:61
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
Definition amr_patch_range.hpp:196
void route_reflux(const Reg &g, Real dx, Real dy, Real dt, FluxRegister &ref, int nc) const
Definition amr_patch_range.hpp:212
CoarseFineInterface(const Box2D &coarse_region, const BoxArray &fine_ba)
Definition amr_patch_range.hpp:201
CoverageMask cmask
Definition amr_patch_range.hpp:197
bool covered(int I, int J) const
Definition amr_patch_range.hpp:205
READ-only handle (const counterpart of Array4): same layout and same contract (POD device-copyable,...
Definition fab2d.hpp:44
Definition amr_patch_range.hpp:152
int NX
Definition amr_patch_range.hpp:153
int NY
Definition amr_patch_range.hpp:153
std::vector< char > cov
Definition amr_patch_range.hpp:154
void mark(const Box2D &b)
Definition amr_patch_range.hpp:161
int J0
Definition amr_patch_range.hpp:153
int I0
Definition amr_patch_range.hpp:153
CoverageMask(const Box2D &region)
Definition amr_patch_range.hpp:155
bool covered(int I, int J) const
Definition amr_patch_range.hpp:168
Definition amr_patch_range.hpp:124
int nc
Definition amr_patch_range.hpp:125
std::size_t idx(int I, int J, int k) const
Definition amr_patch_range.hpp:134
int NY
Definition amr_patch_range.hpp:125
Real at(int I, int J, int k) const
Definition amr_patch_range.hpp:143
void gather()
Definition amr_patch_range.hpp:144
FluxRegister(const Box2D &region, int ncomp)
Definition amr_patch_range.hpp:127
int J0
Definition amr_patch_range.hpp:125
int I0
Definition amr_patch_range.hpp:125
std::vector< Real > buf
Definition amr_patch_range.hpp:126
void set(int I, int J, int k, Real v)
Definition amr_patch_range.hpp:138
void add(int I, int J, int k, Real v)
Definition amr_patch_range.hpp:139
int NX
Definition amr_patch_range.hpp:125
bool in(int I, int J) const
Definition amr_patch_range.hpp:137
Definition amr_patch_range.hpp:42
int I0
Definition amr_patch_range.hpp:43
int I1
Definition amr_patch_range.hpp:43
int J1
Definition amr_patch_range.hpp:43
int J0
Definition amr_patch_range.hpp:43
PatchRange(const Box2D &fine)
Definition amr_patch_range.hpp:44
Box2D box() const
Definition amr_patch_range.hpp:49
Definition amr_patch_range.hpp:180
Real frac(int s) const
Definition amr_patch_range.hpp:185
int count() const
Definition amr_patch_range.hpp:183
Real dt_sub(Real dt) const
Definition amr_patch_range.hpp:184
int r
Definition amr_patch_range.hpp:181
SubcyclingSchedule(int ratio=kAmrRefRatio)
Definition amr_patch_range.hpp:182
Definition amr_patch_range.hpp:74
Array4 c
Definition amr_patch_range.hpp:76
int nc
Definition amr_patch_range.hpp:77
ConstArray4 f
Definition amr_patch_range.hpp:75
POPS_HD void operator()(int I, int J) const
Definition amr_patch_range.hpp:78
#define POPS_HD
Definition types.hpp:25