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

adc_cpp: include/pops/numerics/time/amr/levels/amr_subcycling.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_subcycling.hpp
Go to the documentation of this file.
1#pragma once
2#include <pops/mesh/storage/mf_arith.hpp> // saxpy, lincomb (SSPRK3 stages, named device-clean functors)
4#include <pops/mesh/layout/refinement.hpp> // coarsen, parallel_copy
7
8#include <cassert> // assert (replicated-parent invariant: mf_find_box always finds it)
9
31
32namespace pops {
33
34static_assert(kAmrRefRatio == 2, "ratio-2-structural kernels below assume kAmrRefRatio == 2");
35
36// --- MULTI-PATCH (several fine boxes per level) ---
37// The fine level is a MultiFab with N boxes. Reflux is COVERAGE-AWARE: it corrects a coarse
38// cell adjacent to a fine box only if it is NOT covered by another fine box (real fine-coarse
39// interface; fine-fine interfaces are handled by fill_boundary). This is AMReX FluxRegister
40// logic.
41
42// Conservative 2-level step, MULTI-BOX fine level. Uc: single-box coarse (periodic).
43// Uf: MultiFab with N fine boxes (ratio 2, strictly interior, coarse-aligned).
44//
45// Distributed (MPI) with COARSE REPLICATION. The single-box coarse level is replicated: each
46// rank holds an identical copy (per-rank DistributionMapping, or deterministic init). The coarse
47// advance (self-periodic fill_boundary, flux, advance) runs identically on each copy; the fine
48// patches are distributed. average_down (overwrite of covered cells) and reflux (addition to
49// bordering cells) gather up through two global-indexed coarse buffers + all_reduce_sum_inplace,
50// then each rank applies to its copy -> all stay identical. In serial this is bit-for-bit
51// identical to the direct path (see the final block). Validation: test_mpi_amr_multipatch
52// (np=1/2/4 bit-identical). The coarse level is small (base level), so the replication is
53// accepted; the recursive N-level path (subcycle_level_mp) still has to be generalized the same
54// way (ROADMAP).
55template <class Limiter = NoSlope, class NumericalFlux = RusanovFlux, class Model>
56void amr_step_2level_multipatch(const Model& m, MultiFab& Uc, const Box2D& dom, Real dxc, Real dyc,
57 MultiFab& Uf, const MultiFab& auxc, const MultiFab& auxf, Real dt) {
58 const SubcyclingSchedule sched(2);
59 const int nc = Uc.ncomp();
60 const Real dxf = dxc / kAmrRefRatio, dyf = dyc / kAmrRefRatio, dtf = sched.dt_sub(dt);
61 const int NX = dom.nx(), NY = dom.ny();
62
63 // coarse-fine interface: coverage (coarse cells shadowed by a fine patch) + bordering reflux
64 // routing. Coverage built on the GLOBAL BoxArray (all boxes, known to all ranks) -> correct
65 // under MPI.
66 const CoarseFineInterface cfi(Box2D{{0, 0}, {NX - 1, NY - 1}}, Uf.box_array());
67 auto covered = [&](int I, int J) { return cfi.covered(I, J); };
68
69 MultiFab Uc_old = Uc;
70 fill_periodic_local(Uc, dom); // replicated coarse -> local periodic fill (no MPI plan)
71 MultiFab fxc(BoxArray(std::vector<Box2D>{xface_box(Uc.box(0))}), Uc.dmap(), nc, 0);
72 MultiFab fyc(BoxArray(std::vector<Box2D>{yface_box(Uc.box(0))}), Uc.dmap(), nc, 0);
73 compute_face_fluxes<Limiter, NumericalFlux>(m, Uc, auxc, fxc, fyc, dxc, dyc);
74
75 // per fine-box register: coarse flux (without dt) saved at the 4 faces.
76 struct Reg {
77 int I0, I1, J0, J1;
78 std::vector<Real> cL, cR, cB, cT, fL, fR, fB, fT;
79 };
80 std::vector<Reg> regs(Uf.local_size());
81 {
83 const ConstArray4 FX = fxc.fab(0).const_array(), FY = fyc.fab(0).const_array();
84 for (int li = 0; li < Uf.local_size(); ++li) {
85 const PatchRange pr(Uf.box(li));
86 Reg& g = regs[li];
87 g.I0 = pr.I0;
88 g.I1 = pr.I1;
89 g.J0 = pr.J0;
90 g.J1 = pr.J1;
91 const int nJ = g.J1 - g.J0 + 1, nI = g.I1 - g.I0 + 1;
92 g.cL.assign(nJ * nc, 0);
93 g.cR.assign(nJ * nc, 0);
94 g.cB.assign(nI * nc, 0);
95 g.cT.assign(nI * nc, 0);
96 g.fL.assign(nJ * nc, 0);
97 g.fR.assign(nJ * nc, 0);
98 g.fB.assign(nI * nc, 0);
99 g.fT.assign(nI * nc, 0);
100 for (int J = g.J0; J <= g.J1; ++J)
101 for (int k = 0; k < nc; ++k) {
102 g.cL[(J - g.J0) * nc + k] = FX(g.I0, J, k);
103 g.cR[(J - g.J0) * nc + k] = FX(g.I1 + 1, J, k);
104 }
105 for (int I = g.I0; I <= g.I1; ++I)
106 for (int k = 0; k < nc; ++k) {
107 g.cB[(I - g.I0) * nc + k] = FY(I, g.J0, k);
108 g.cT[(I - g.I0) * nc + k] = FY(I, g.J1 + 1, k);
109 }
110 }
111 }
112 mf_advance_faces(Uc, fxc, fyc, dxc, dyc, dt);
113 mf_apply_source(m, Uc, auxc, dt); // source S(U,aux) at the substep
114
115 // multi-box fine fluxes: one face-box per GLOBAL fine box, same dmap as Uf. Built on the global
116 // box_array() (not the local boxes) so that BoxArray and DistributionMapping have the same size
117 // under MPI: fxf.fab(li) then corresponds to Uf.fab(li) (same dmap, same global order). In
118 // serial it is identical (local == global).
119 std::vector<Box2D> fxb, fyb;
120 for (int g = 0; g < Uf.box_array().size(); ++g) {
121 fxb.push_back(xface_box(Uf.box_array()[g]));
122 fyb.push_back(yface_box(Uf.box_array()[g]));
123 }
124 MultiFab fxf(BoxArray(std::move(fxb)), Uf.dmap(), nc, 0);
125 MultiFab fyf(BoxArray(std::move(fyb)), Uf.dmap(), nc, 0);
126 const Box2D fdom = Box2D::from_extents(2 * NX, 2 * NY);
127
128 for (int s = 0; s < sched.count(); ++s) {
129 mf_fill_fine_ghosts_multi(Uf, Uc_old, Uc, sched.frac(s));
130 fill_boundary(Uf, fdom, Periodicity{false, false}); // fine-fine halos
131 compute_face_fluxes<Limiter, NumericalFlux>(m, Uf, auxf, fxf, fyf, dxf, dyf);
132 device_fence();
133 for (int li = 0; li < Uf.local_size(); ++li) {
134 Reg& g = regs[li];
135 const ConstArray4 FX = fxf.fab(li).const_array(), FY = fyf.fab(li).const_array();
136 for (int J = g.J0; J <= g.J1; ++J)
137 for (int k = 0; k < nc; ++k) {
138 g.fL[(J - g.J0) * nc + k] +=
139 Real(0.5) * (FX(2 * g.I0, 2 * J, k) + FX(2 * g.I0, 2 * J + 1, k)) * dtf;
140 g.fR[(J - g.J0) * nc + k] +=
141 Real(0.5) * (FX(2 * g.I1 + 2, 2 * J, k) + FX(2 * g.I1 + 2, 2 * J + 1, k)) * dtf;
142 }
143 for (int I = g.I0; I <= g.I1; ++I)
144 for (int k = 0; k < nc; ++k) {
145 g.fB[(I - g.I0) * nc + k] +=
146 Real(0.5) * (FY(2 * I, 2 * g.J0, k) + FY(2 * I + 1, 2 * g.J0, k)) * dtf;
147 g.fT[(I - g.I0) * nc + k] +=
148 Real(0.5) * (FY(2 * I, 2 * g.J1 + 2, k) + FY(2 * I + 1, 2 * g.J1 + 2, k)) * dtf;
149 }
150 }
151 mf_advance_faces(Uf, fxf, fyf, dxf, dyf, dtf);
152 mf_apply_source(m, Uf, auxf, dtf); // source S(U,aux) at the substep
153 }
154
155 // DISTRIBUTED average_down + reflux, the coarse level being REPLICATED (each rank holds an
156 // identical copy after the deterministic coarse advance). Each rank deposits, for its LOCAL
157 // fine patches, into two global-indexed coarse buffers:
158 // avg: the average-down over the COVERED cells (overwrite semantics; a single contribution
159 // per cell since the patches are disjoint);
160 // ref: the reflux correction on the uncovered BORDERING cells (addition).
161 // all_reduce_sum -> each rank has the total, then applies to ITS copy: covered = avg,
162 // bordering += ref. All copies stay identical. In serial (np=1) the all-reduce is the identity
163 // and it is bit-for-bit identical to the direct path (0 + average = average exactly; advance +
164 // correction). Cost: two NX*NY*nc buffers per rank (coarse replication).
165 device_fence();
166 // register restricted to the coarse-fine INTERFACE (bounding box of the fine footprints, grown
167 // by 1 for the bordering reflux cells, clamped to the domain): the gather all_reduce goes from
168 // O(NX*NY) to O(interface). Bit-identical: cells outside the interface were zero (uncovered,
169 // without a face), skipped at application.
170 const Box2D fpc = coarsen(Uf.box_array(), kAmrRefRatio).bounding_box();
171 const Box2D rbox{{std::max(fpc.lo[0] - 1, 0), std::max(fpc.lo[1] - 1, 0)},
172 {std::min(fpc.hi[0] + 1, NX - 1), std::min(fpc.hi[1] + 1, NY - 1)}};
173 FluxRegister avg(rbox, nc); // average-down (overwrite of covered cells)
174 FluxRegister ref(rbox, nc); // reflux (addition to bordering cells)
175 for (int li = 0; li < Uf.local_size(); ++li) {
176 const ConstArray4 f = Uf.fab(li).const_array();
177 Reg& g = regs[li];
178 for (int J = g.J0; J <= g.J1; ++J)
179 for (int I = g.I0; I <= g.I1; ++I)
180 for (int k = 0; k < nc; ++k)
181 avg.set(I, J, k,
182 Real(0.25) * (f(2 * I, 2 * J, k) + f(2 * I + 1, 2 * J, k) +
183 f(2 * I, 2 * J + 1, k) + f(2 * I + 1, 2 * J + 1, k)));
184 cfi.route_reflux(g, dxc, dyc, dt, ref, nc); // coverage-aware bordering reflux
185 }
186 avg.gather();
187 ref.gather();
188 if (Uc.local_size() > 0) { // each rank holding a copy of the coarse level applies it
189 Array4 c = Uc.fab(0).array();
190 const Box2D cb = Uc.box(0);
191 for (int J = cb.lo[1]; J <= cb.hi[1]; ++J)
192 for (int I = cb.lo[0]; I <= cb.hi[0]; ++I) {
193 if (!ref.in(I, J))
194 continue; // outside interface: neither average nor reflux (was 0)
195 for (int k = 0; k < nc; ++k) {
196 if (covered(I, J))
197 c(I, J, k) = avg.at(I, J, k); // average-down
198 c(I, J, k) += ref.at(I, J, k); // reflux (0 if no face)
199 }
200 }
201 }
202}
203
204// --- N-LEVEL MULTI-PATCH (multi-box at EACH level) ---
205// Generalizes subcycle_level_mf: each level is a multi-box MultiFab. Reflux (FluxRegister) is
206// coverage-aware AND routes the correction to the PARENT box containing the adjacent coarse cell.
207// Reduces BIT-FOR-BIT to the single-box path when each level has only one box (validation guard).
208//
209// Distributed state (MPI): DISTRIBUTED and tested bit-for-bit identical np=1/2/4
210// (test_mpi_amr_multipatch3, 3 levels with a distributed multi-box intermediate level whose fine
211// patch PARENT falls on another rank). Level 0 (coarse) is REPLICATED as in the 2-level case;
212// levels >0 are distributed and play the role of both child and parent simultaneously. The five
213// points assuming a local parent (via mf_find_box) are resolved:
214// 1. mf_fill_fine_ghosts_mb: REPLICATED parent (lev==1) read locally; DISTRIBUTED parent
215// (lev>=2) brought in by parallel_copy (parent -> fine-coarsen) then interpolated;
216// 2. coarse register sampling: REPLICATED parent read locally, DISTRIBUTED parent brought in by
217// parallel_copy onto a child-coarsen FACE grid;
218// 3. mf_average_down_mb: average deposited in a GLOBAL-indexed coarse buffer + all_reduce_sum,
219// applied to the local parent boxes (replicated: all; distributed: the owner);
220// 4. reflux: same global buffer + all_reduce, application guarded by local ownership of the
221// parent box (no double counting since the distributed parent has a single owner);
222// 5. coverage: already built on the global box_array() (MPI-safe).
223// In serial all_reduce is the identity and parallel_copy reduces to memory copies: the
224// distributed path runs the same floating-point operations as the single-rank one -> bit-
225// identical.
226// Note: AmrCouplerMP remains limited to single-rank beyond 2 distributed levels, because its
227// parent->child aux injection (inject_aux_mb) still assumes the parent is local via mf_find_box;
228// the integrator amr_step_multilevel_multipatch, on the other hand, is distributed.
229
230// LOCAL (valid) box containing cell (I,J), or -1.
231inline int mf_find_box(const MultiFab& mf, int I, int J) {
232 for (int li = 0; li < mf.local_size(); ++li)
233 if (mf.box(li).contains(I, J))
234 return li;
235 return -1;
236}
237
238// BoxArray of the child boxes grown by ngrow then coarsened (ratio 2). Each box covers all the
239// coarse cells the child needs, ghosts included: this is the FillPatch fine-coarsen grid (cf.
240// refinement.hpp::interpolate).
241inline BoxArray coarsen_grown(const BoxArray& ba, int ngrow, int r) {
242 std::vector<Box2D> b;
243 b.reserve(ba.size());
244 for (int i = 0; i < ba.size(); ++i)
245 b.push_back(ba[i].grow(ngrow).coarsen(r));
246 return BoxArray{std::move(b)};
247}
248
249// multi-box fine ghosts from a MULTI-BOX parent (constant-space + linear-time interp),
250// DISTRIBUTED. Two parent cases:
251// - REPLICATED (level 0, replicated_parent=true): the parent is fully local on each rank, read
252// directly via mf_find_box (always found); no collective. This is the replicated-coarse path,
253// like the 2-level case (parallel_copy would violate the replicated-metadata assumption of the
254// parent, per-rank dmap).
255// - DISTRIBUTED (intermediate): the parent may be on another rank; its valid regions are brought
256// onto a LOCAL child-coarsen grid by parallel_copy (MPI routing handled there), then
257// interpolated. No more silent remote failures.
258// In serial both paths are identical (parent local everywhere, parallel_copy = memory copy).
259inline void mf_fill_fine_ghosts_mb(MultiFab& Uf, const MultiFab& Po, const MultiFab& Pn, Real frac,
260 bool replicated_parent = true, Real pos_floor = Real(0),
261 int pos_comp = 0) {
262 const int nc = Uf.ncomp(), ng = Uf.n_grow();
263 if (replicated_parent) {
264 device_fence();
265 for (int li = 0; li < Uf.local_size(); ++li) {
266 Array4 f = Uf.fab(li).array();
267 const Box2D v = Uf.box(li), g = Uf.fab(li).grown_box();
268 for (int j = g.lo[1]; j <= g.hi[1]; ++j)
269 for (int i = g.lo[0]; i <= g.hi[0]; ++i)
270 if (!v.contains(i, j)) {
271 const int ci = coarsen_index(i, kAmrRefRatio), cj = coarsen_index(j, kAmrRefRatio);
272 const int pb = mf_find_box(Po, ci, cj);
273 if (pb < 0)
274 continue; // outside parent coverage -> leave to fill_boundary
275 const ConstArray4 po = Po.fab(pb).const_array(), pn = Pn.fab(pb).const_array();
276 fill_cf_ghost_cell(f, po, pn, i, j, nc, frac, pos_floor, pos_comp);
277 }
278 }
279 return;
280 }
281 const BoxArray pcoarse_ba = coarsen_grown(Uf.box_array(), ng, kAmrRefRatio);
282 MultiFab Pco(pcoarse_ba, Uf.dmap(), nc, 0), Pcn(pcoarse_ba, Uf.dmap(), nc, 0);
283 parallel_copy(Pco, Po); // parent regions (from any rank) -> local grid
284 parallel_copy(Pcn, Pn);
285 device_fence();
286 for (int li = 0; li < Uf.local_size(); ++li) {
287 Array4 f = Uf.fab(li).array();
288 const ConstArray4 po = Pco.fab(li).const_array(), pn = Pcn.fab(li).const_array();
289 const Box2D v = Uf.box(li), g = Uf.fab(li).grown_box();
290 for (int j = g.lo[1]; j <= g.hi[1]; ++j)
291 for (int i = g.lo[0]; i <= g.hi[0]; ++i)
292 if (!v.contains(i, j))
293 fill_cf_ghost_cell(f, po, pn, i, j, nc, frac, pos_floor, pos_comp);
294 }
295}
296
297// multi-box fine average -> multi-box parent (each cell routed to its parent box), DISTRIBUTED.
298// The parent box of a coarse cell may be on another rank, and the parent may be either REPLICATED
299// (level 0, each rank has a copy) or DISTRIBUTED (intermediate, a single owner). Both are covered
300// by a GLOBAL-indexed coarse buffer: each rank deposits the 2x2 average of ITS local fine patches
301// (0 elsewhere; disjoint patches so a single contribution per covered cell), all_reduce_sum ->
302// each rank has the total, then applies to ITS local parent boxes (overwrite). Replicated: all
303// apply the same value to their copy. Distributed: only the owner applies. In serial all_reduce
304// is the identity (0 + average = average) -> bit-for-bit identical to the direct routing.
305inline void mf_average_down_mb(const MultiFab& Uf, MultiFab& Uc) {
306 const int nc = std::min(Uf.ncomp(), Uc.ncomp());
307 // coarse bounding box (GLOBAL indices) covering all the child footprints.
308 const BoxArray cba = coarsen(Uf.box_array(), kAmrRefRatio);
309 Box2D bb{{0, 0}, {-1, -1}};
310 for (int g = 0; g < cba.size(); ++g)
311 bb = (g == 0) ? cba[g]
312 : Box2D{{std::min(bb.lo[0], cba[g].lo[0]), std::min(bb.lo[1], cba[g].lo[1])},
313 {std::max(bb.hi[0], cba[g].hi[0]), std::max(bb.hi[1], cba[g].hi[1])}};
314 if (bb.empty()) {
315 all_reduce_sum_inplace(nullptr, 0);
316 return;
317 } // empty matched collective
318 FluxRegister avg(bb, nc); // multi-box average-down (region = bounding box)
319 // GLOBAL coverage (all child footprints): only these cells are overwritten; the bounding box
320 // may contain holes between disjoint patches that must NOT be overwritten.
321 CoverageMask cmask(bb);
322 for (int g = 0; g < cba.size(); ++g)
323 cmask.mark(cba[g]);
324 auto covered = [&](int I, int J) { return cmask.covered(I, J); };
325 device_fence();
326 for (int lf = 0; lf < Uf.local_size(); ++lf) {
327 const ConstArray4 f = Uf.fab(lf).const_array();
328 const PatchRange pr(Uf.box(lf));
329 for (int J = pr.J0; J <= pr.J1; ++J)
330 for (int I = pr.I0; I <= pr.I1; ++I)
331 for (int k = 0; k < nc; ++k)
332 avg.set(I, J, k,
333 Real(0.25) * (f(2 * I, 2 * J, k) + f(2 * I + 1, 2 * J, k) +
334 f(2 * I, 2 * J + 1, k) + f(2 * I + 1, 2 * J + 1, k)));
335 }
336 avg.gather();
337 for (int pb = 0; pb < Uc.local_size(); ++pb) {
338 Array4 c = Uc.fab(pb).array();
339 const Box2D inter = Uc.box(pb).intersect(bb);
340 for (int J = inter.lo[1]; J <= inter.hi[1]; ++J)
341 for (int I = inter.lo[0]; I <= inter.hi[0]; ++I)
342 if (covered(I, J))
343 for (int k = 0; k < nc; ++k)
344 c(I, J, k) = avg.at(I, J, k);
345 }
346}
347
348// one level of the multi-patch hierarchy (U + multi-box aux, same BoxArray).
354
355// per child-patch register (PARENT coords I0..J1). c* = coarse flux (without dt);
356// f* = time-integrated fine flux accumulated by the child during subcycling.
357struct RegMP {
358 int I0, I1, J0, J1;
359 std::vector<Real> cL, cR, cB, cT, fL, fR, fB, fT;
360};
361
362namespace detail { // INTERNAL N-level multi-patch engine; the public facade is advance_amr
363
364// Fills the ghosts of an AMR level: level 0 = base-domain BC (fill_boundary); level > 0 =
365// time-interpolated coarse-fine ghosts from the parent at position frac (mf_fill_fine_ghosts_mb)
366// THEN fine-fine halos (fill_boundary). Factored out of the head of subcycle_level_mp, REUSED by
367// the SSPRK3 advance which must refill the ghosts BEFORE each stage flux evaluation. The parent
368// is REPLICATED only for lev == 1 (replicated level 0), otherwise distributed (internal
369// parallel_copy).
370inline void ssprk3_refill_level_ghosts(MultiFab& U, int lev, const Box2D& base_dom,
371 Periodicity base_per, const MultiFab* pOld,
372 const MultiFab* pNew, Real frac, bool coarse_replicated,
373 Real pos_floor = Real(0), int pos_comp = 0) {
374 if (lev == 0) {
375 fill_boundary(U, base_dom, base_per);
376 } else {
377 mf_fill_fine_ghosts_mb(U, *pOld, *pNew, frac, (lev == 1) && coarse_replicated, pos_floor,
378 pos_comp);
379 const Box2D fdom = Box2D::from_extents(base_dom.nx() << lev, base_dom.ny() << lev);
380 fill_boundary(U, fdom, Periodicity{false, false});
381 }
382}
383
384// SSPRK3 (Shu-Osher, 3 stages, order 3) on ONE AMR level. (1) Advance lv.U from t to t+dt:
385// U1 = U0 + dt L(U0); U2 = 3/4 U0 + 1/4 (U1 + dt L(U1)); U_new = 1/3 U0 + 2/3 (U2 + dt L(U2))
386// with L(U) = -div F(U) + S(U) (EXPLICIT source per stage, evaluated at the same state as the
387// flux: true SSPRK method-of-lines, cf. mf_eval_rhs -- IMEX is NOT supported, rejected upstream).
388// (2) Fills (fx, fy) with the EFFECTIVE FLUX of the step Feff = 1/6 F(U0) + 1/6 F(U1) + 2/3 F(U2)
389// which is EXACTLY the transport flux seen by the final state (U_new = U0 - dt div Feff + dt Seff).
390// This is the flux the conservative reflux must record (coarse side g.c* and fine side g.f*), hence
391// its write into (fx, fy) where the Euler path leaves the single flux F(U0). On INPUT (fx, fy)
392// already contain F(U0) (stage 0, computed by the caller before the call). Between stages, the
393// ghosts are refreshed by ssprk3_refill_level_ghosts at the SAME frac: the coarse-fine boundary is
394// FROZEN over the substep (the levels do not cross their stages, cf. subcycle_level_mp header /
395// subcycling). saxpy/lincomb and the RHS functor are device-clean kernels (named functors), no
396// extended lambda.
397template <class Limiter = NoSlope, class NumericalFlux = RusanovFlux, class Model>
398void ssprk3_advance_level(const Model& m, AmrLevelMP& lv, Real dt, MultiFab& fx, MultiFab& fy,
399 bool recon_prim, int lev, const Box2D& base_dom, Periodicity base_per,
400 const MultiFab* pOld, const MultiFab* pNew, Real frac,
401 bool coarse_replicated, Real pos_floor = Real(0)) {
402 const int nc = lv.U.ncomp();
403 // Density-role component for the C/F ghost floor (ADC-259), resolved ONCE on the host. pos_floor<=0
404 // -> 0 without model introspection (positivity_comp short-circuit) -> bit-identical historical path.
405 const int pos_comp = detail::positivity_comp<Model>(pos_floor);
406 MultiFab U0 = lv.U; // starting state t (Shu-Osher convex combinations)
407 MultiFab R(lv.U.box_array(), lv.U.dmap(), nc, 0);
408 MultiFab Fxs(fx.box_array(), fx.dmap(), nc, 0),
409 Fys(fy.box_array(), fy.dmap(), nc, 0); // stage flux
410
411 // --- stage 0: F(U0) already in (fx, fy), R0 = -div F0 + S(U0) ---
412 mf_eval_rhs(m, lv.U, *lv.aux, fx, fy, lv.dx, lv.dy, R);
413 saxpy(lv.U, dt, R); // lv.U = U1 = U0 + dt R0
414 lincomb(fx, Real(1) / 6, fx, Real(0), fx); // Feff <- 1/6 F0 (pointwise aliasing, safe)
415 lincomb(fy, Real(1) / 6, fy, Real(0), fy);
416
417 // --- stage 1: F(U1) ---
418 ssprk3_refill_level_ghosts(lv.U, lev, base_dom, base_per, pOld, pNew, frac, coarse_replicated,
419 pos_floor, pos_comp);
420 compute_face_fluxes<Limiter, NumericalFlux>(m, lv.U, *lv.aux, Fxs, Fys, lv.dx, lv.dy, recon_prim,
421 pos_floor);
422 device_fence();
423 mf_eval_rhs(m, lv.U, *lv.aux, Fxs, Fys, lv.dx, lv.dy, R); // R1 = -div F1 + S(U1)
424 saxpy(lv.U, dt, R); // lv.U = U1 + dt R1
425 lincomb(lv.U, Real(3) / 4, U0, Real(1) / 4, lv.U); // lv.U = U2
426 saxpy(fx, Real(1) / 6, Fxs); // Feff += 1/6 F1
427 saxpy(fy, Real(1) / 6, Fys);
428
429 // --- stage 2: F(U2) ---
430 ssprk3_refill_level_ghosts(lv.U, lev, base_dom, base_per, pOld, pNew, frac, coarse_replicated,
431 pos_floor, pos_comp);
432 compute_face_fluxes<Limiter, NumericalFlux>(m, lv.U, *lv.aux, Fxs, Fys, lv.dx, lv.dy, recon_prim,
433 pos_floor);
434 device_fence();
435 mf_eval_rhs(m, lv.U, *lv.aux, Fxs, Fys, lv.dx, lv.dy, R); // R2 = -div F2 + S(U2)
436 saxpy(lv.U, dt, R); // lv.U = U2 + dt R2
437 lincomb(lv.U, Real(1) / 3, U0, Real(2) / 3, lv.U); // lv.U = U_new (t + dt)
438 saxpy(fx, Real(2) / 3, Fxs); // Feff += 2/3 F2
439 saxpy(fy, Real(2) / 3, Fys);
440 device_fence(); // (fx, fy) = Feff and lv.U = U_new consistent for host reads (parentRegs/reflux)
441}
442
443template <class Limiter = NoSlope, class NumericalFlux = RusanovFlux, class Model>
444void subcycle_level_mp(const Model& m, std::vector<AmrLevelMP>& L, int lev, Real dt,
445 const Box2D& base_dom, Periodicity base_per, const MultiFab* pOld,
446 const MultiFab* pNew, Real frac, std::vector<RegMP>* parentRegs,
447 bool coarse_replicated = true, bool recon_prim = false, bool imex = false,
448 const NewtonOptions& nopts = {},
449 AmrTimeMethod tmethod = AmrTimeMethod::kEuler, Real pos_floor = Real(0)) {
450 // SSPRK3 + IMEX: combination NOT VALIDATED (the per-stage implicit stiff source under SSP has
451 // not been verified), rejected EXPLICITLY rather than run silently. The facade cannot produce it
452 // (time.kind is a single selector: "ssprk3" XOR "imex"), defense-in-depth guard here.
453 if (tmethod == AmrTimeMethod::kSsprk3 && imex)
454 throw std::runtime_error(
455 "subcycle_level_mp: SSPRK3 + IMEX unsupported (combination not validated); use "
456 "time='ssprk3' (explicit source per stage) or time='imex' (forward Euler + implicit "
457 "source)");
458 const SubcyclingSchedule sched(2);
459 const int nc = L[lev].U.ncomp();
460 // Density-role component for the C/F ghost floor (ADC-259), resolved ONCE on the host. pos_floor<=0
461 // -> 0 without model introspection (positivity_comp short-circuit) -> bit-identical historical path.
462 const int pos_comp = detail::positivity_comp<Model>(pos_floor);
463 AmrLevelMP& lv = L[lev];
464 const int np = lv.U.local_size();
465 const bool ssprk3 = (tmethod == AmrTimeMethod::kSsprk3);
466
467 if (lev == 0) {
468 fill_boundary(lv.U, base_dom, base_per);
469 } else {
470 // parent (level lev-1) REPLICATED only if it is level 0 (lev == 1); otherwise distributed ->
471 // FillPatch by parallel_copy.
472 mf_fill_fine_ghosts_mb(lv.U, *pOld, *pNew, frac,
473 /*replicated_parent=*/(lev == 1) && coarse_replicated, pos_floor,
474 pos_comp);
475 const Box2D fdom = Box2D::from_extents(base_dom.nx() << lev, base_dom.ny() << lev);
476 fill_boundary(lv.U, fdom, Periodicity{false, false}); // fine-fine halos
477 }
478
479 // face-box per GLOBAL box + same dmap (cf. amr_step_2level_multipatch): BoxArray and
480 // DistributionMapping of the same size under MPI, fx.fab(li) <-> lv.U.fab(li). Identical in
481 // serial (local == global).
482 std::vector<Box2D> fxb, fyb;
483 for (int g = 0; g < lv.U.box_array().size(); ++g) {
484 fxb.push_back(xface_box(lv.U.box_array()[g]));
485 fyb.push_back(yface_box(lv.U.box_array()[g]));
486 }
487 MultiFab fx(BoxArray(std::move(fxb)), lv.U.dmap(), nc, 0);
488 MultiFab fy(BoxArray(std::move(fyb)), lv.U.dmap(), nc, 0);
489 compute_face_fluxes<Limiter, NumericalFlux>(m, lv.U, *lv.aux, fx, fy, lv.dx, lv.dy, recon_prim,
490 pos_floor);
491 device_fence();
492
493 // SSPRK3: we FIRST advance lv.U from t to t+dt (3 stages) AND replace (fx, fy) -- which contain
494 // the single flux F(U0) of the Euler path -- with the EFFECTIVE FLUX Feff = 1/6 F0 + 1/6 F1 +
495 // 2/3 F2. All the rest of the function (parent register, child registers, saved coarse flux,
496 // reflux) reads (fx, fy) and the advanced state EXACTLY as in Euler: recording Feff (instead of
497 // F0) makes the reflux conservative for the full SSP step (the coarse side g.c* = coarse Feff,
498 // the fine side g.f* = sum of the subcycled fine Feff, and the correction -(g.f - g.c*dt)/dx
499 // correctly replaces the effective coarse flux with the effective fine flux). The starting state
500 // is saved BEFORE the advance for the temporal interpolation of the children (coarse role). In
501 // Euler (ssprk3 == false) this block is skipped and the advance stays the original one, in place
502 // below -> strictly bit-identical.
503 const bool is_leaf = (lev + 1 >= static_cast<int>(L.size()));
504 MultiFab ssp_U_old; // state t (pre-advance capture); filled only for SSPRK3 + coarse role
505 if (ssprk3) {
506 if (!is_leaf)
507 ssp_U_old = lv.U; // the children interpolate between this state (t) and advanced lv.U (t+dt)
508 ssprk3_advance_level<Limiter, NumericalFlux>(m, lv, dt, fx, fy, recon_prim, lev, base_dom,
509 base_per, pOld, pNew, frac, coarse_replicated,
510 pos_floor);
511 }
512
513 if (parentRegs) { // FINE role: fine fluxes of THIS level into the parent register
514 for (int li = 0; li < np; ++li) {
515 RegMP& g = (*parentRegs)[li];
516 const ConstArray4 FX = fx.fab(li).const_array(), FY = fy.fab(li).const_array();
517 for (int J = g.J0; J <= g.J1; ++J)
518 for (int k = 0; k < nc; ++k) {
519 g.fL[(J - g.J0) * nc + k] +=
520 Real(0.5) * (FX(2 * g.I0, 2 * J, k) + FX(2 * g.I0, 2 * J + 1, k)) * dt;
521 g.fR[(J - g.J0) * nc + k] +=
522 Real(0.5) * (FX(2 * g.I1 + 2, 2 * J, k) + FX(2 * g.I1 + 2, 2 * J + 1, k)) * dt;
523 }
524 for (int I = g.I0; I <= g.I1; ++I)
525 for (int k = 0; k < nc; ++k) {
526 g.fB[(I - g.I0) * nc + k] +=
527 Real(0.5) * (FY(2 * I, 2 * g.J0, k) + FY(2 * I + 1, 2 * g.J0, k)) * dt;
528 g.fT[(I - g.I0) * nc + k] +=
529 Real(0.5) * (FY(2 * I, 2 * g.J1 + 2, k) + FY(2 * I + 1, 2 * g.J1 + 2, k)) * dt;
530 }
531 }
532 }
533
534 if (is_leaf) { // leaf
535 if (!ssprk3) { // forward Euler (legacy path); SSPRK3 already advanced lv.U above
536 mf_advance_faces(lv.U, fx, fy, lv.dx, lv.dy, dt);
537 mf_apply_source_treatment(m, lv.U, *lv.aux, dt, imex,
538 nopts); // explicit or IMEX source (Newton options)
539 }
540 return;
541 }
542
543 // COARSE role for lev+1: coarse-fine interface (GLOBAL MPI-safe coverage + bordering reflux
544 // routing) + registers + saved coarse flux.
545 const int NX = base_dom.nx() << lev, NY = base_dom.ny() << lev;
546 const CoarseFineInterface cfi(Box2D{{0, 0}, {NX - 1, NY - 1}}, L[lev + 1].U.box_array());
547 auto covered = [&](int I, int J) { return cfi.covered(I, J); };
548
549 // Distributed point 2: the coarse flux fx/fy lives on the PARENT dmap (lv.U), so fx.fab is on
550 // the rank owning the parent box, not necessarily the child's. Two cases:
551 // - REPLICATED parent (lev == 0): fx/fy fully local, sampled directly via mf_find_box (always
552 // found); no collective (parallel_copy would violate parent replication);
553 // - DISTRIBUTED parent (lev >= 1): the needed coarse fluxes are brought onto a child-coarsen
554 // FACE grid (child dmap) by parallel_copy, each child then reads locally.
555 // Level 0 is REPLICATED only if coarse_replicated: at de-replication (distributed multi-box
556 // coarse level), it becomes DISTRIBUTED like the fine levels, and mf_find_box(lv.U, I, J) would
557 // return -1 for a bordering coarse cell owned by a REMOTE rank (-> fab(-1), segfault). We then
558 // route to the parallel_copy path (per-child coarse footprint), MPI-correct.
559 const bool replicated_parent = (lev == 0) && coarse_replicated;
560 const BoxArray cba =
561 coarsen(L[lev + 1].U.box_array(), kAmrRefRatio); // per-child coarse footprint
562 MultiFab cfx, cfy;
563 if (!replicated_parent) {
564 std::vector<Box2D> cfxb, cfyb;
565 for (int g = 0; g < cba.size(); ++g) {
566 cfxb.push_back(xface_box(cba[g]));
567 cfyb.push_back(yface_box(cba[g]));
568 }
569 cfx = MultiFab(BoxArray(std::move(cfxb)), L[lev + 1].U.dmap(), nc, 0);
570 cfy = MultiFab(BoxArray(std::move(cfyb)), L[lev + 1].U.dmap(), nc, 0);
571 parallel_copy(cfx, fx); // coarse face fluxes -> local child-coarsen grid
572 parallel_copy(cfy, fy);
573 }
574 device_fence();
575
576 std::vector<RegMP> regs(L[lev + 1].U.local_size());
577 for (int lc = 0; lc < L[lev + 1].U.local_size(); ++lc) {
578 const PatchRange pr(L[lev + 1].U.box(lc));
579 RegMP& g = regs[lc];
580 g.I0 = pr.I0;
581 g.I1 = pr.I1;
582 g.J0 = pr.J0;
583 g.J1 = pr.J1;
584 const int nJ = g.J1 - g.J0 + 1, nI = g.I1 - g.I0 + 1;
585 g.cL.assign(nJ * nc, 0);
586 g.cR.assign(nJ * nc, 0);
587 g.cB.assign(nI * nc, 0);
588 g.cT.assign(nI * nc, 0);
589 g.fL.assign(nJ * nc, 0);
590 g.fR.assign(nJ * nc, 0);
591 g.fB.assign(nI * nc, 0);
592 g.fT.assign(nI * nc, 0);
593 if (replicated_parent) {
594 for (int J = g.J0; J <= g.J1; ++J) {
595 const int bL = mf_find_box(lv.U, g.I0, J), bR = mf_find_box(lv.U, g.I1, J);
596 // replicated-parent invariant: parent fully local (cf. above), mf_find_box always finds
597 // the box; a -1 would index fab(-1) (segfault). The distributed case goes through the else.
598 assert(bL >= 0 && bR >= 0 &&
599 "subcycle_level_mp: replicated-parent invariant violated (coarse box x not found)");
600 const ConstArray4 FXL = fx.fab(bL).const_array(), FXR = fx.fab(bR).const_array();
601 for (int k = 0; k < nc; ++k) {
602 g.cL[(J - g.J0) * nc + k] = FXL(g.I0, J, k);
603 g.cR[(J - g.J0) * nc + k] = FXR(g.I1 + 1, J, k);
604 }
605 }
606 for (int I = g.I0; I <= g.I1; ++I) {
607 const int bB = mf_find_box(lv.U, I, g.J0), bT = mf_find_box(lv.U, I, g.J1);
608 // same replicated-parent invariant as above (y faces): coarse box always found.
609 assert(bB >= 0 && bT >= 0 &&
610 "subcycle_level_mp: replicated-parent invariant violated (coarse box y not found)");
611 const ConstArray4 FYB = fy.fab(bB).const_array(), FYT = fy.fab(bT).const_array();
612 for (int k = 0; k < nc; ++k) {
613 g.cB[(I - g.I0) * nc + k] = FYB(I, g.J0, k);
614 g.cT[(I - g.I0) * nc + k] = FYT(I, g.J1 + 1, k);
615 }
616 }
617 } else {
618 const ConstArray4 FX = cfx.fab(lc).const_array(), FY = cfy.fab(lc).const_array();
619 for (int J = g.J0; J <= g.J1; ++J)
620 for (int k = 0; k < nc; ++k) {
621 g.cL[(J - g.J0) * nc + k] = FX(g.I0, J, k);
622 g.cR[(J - g.J0) * nc + k] = FX(g.I1 + 1, J, k);
623 }
624 for (int I = g.I0; I <= g.I1; ++I)
625 for (int k = 0; k < nc; ++k) {
626 g.cB[(I - g.I0) * nc + k] = FY(I, g.J0, k);
627 g.cT[(I - g.I0) * nc + k] = FY(I, g.J1 + 1, k);
628 }
629 }
630 }
631
632 // state t for the temporal interpolation of the children. SSPRK3: lv.U is ALREADY advanced (the
633 // advance happened above, in ssprk3_advance_level), the state t is the pre-advance copy
634 // ssp_U_old; Euler: lv.U is still the state t here (the advance is just below), so U_old = lv.U
635 // (legacy copy).
636 MultiFab U_old = ssprk3 ? ssp_U_old : lv.U;
637 if (!ssprk3) { // forward Euler (legacy path); SSPRK3 already advanced lv.U
638 mf_advance_faces(lv.U, fx, fy, lv.dx, lv.dy, dt);
639 mf_apply_source_treatment(m, lv.U, *lv.aux, dt, imex,
640 nopts); // explicit or IMEX source (Newton options)
641 }
642 for (int s = 0; s < sched.count();
643 ++s) // each fine substep = one full SSP step (tmethod propagated)
644 subcycle_level_mp<Limiter, NumericalFlux>(
645 m, L, lev + 1, sched.dt_sub(dt), base_dom, base_per, &U_old, &lv.U, sched.frac(s), &regs,
646 coarse_replicated, recon_prim, imex, nopts, tmethod, pos_floor);
647 mf_average_down_mb(L[lev + 1].U, lv.U); // distributed point 3 (parallel_copy)
648
649 // Distributed point 4: coverage-aware reflux. The bordering coarse cell may belong to a REMOTE
650 // parent box. For each LOCAL child, we deposit the correction (cL/fL already local after
651 // parallel_copy) into a GLOBAL-indexed coarse buffer, all_reduce -> each rank has the full
652 // register, then each rank applies to ITS local parent boxes (the parent being distributed,
653 // each cell has only one owner: no double counting). In serial all_reduce is the identity and
654 // application is direct -> bit-for-bit identical.
655 device_fence();
656 // register restricted to the INTERFACE: bounding box of the fine footprints (coarsen of level
657 // lev+1), grown by 1, clamped to level lev. all_reduce O(interface), bit-identical.
658 const Box2D fpcn = coarsen(L[lev + 1].U.box_array(), kAmrRefRatio).bounding_box();
659 const Box2D rbox{{std::max(fpcn.lo[0] - 1, 0), std::max(fpcn.lo[1] - 1, 0)},
660 {std::min(fpcn.hi[0] + 1, NX - 1), std::min(fpcn.hi[1] + 1, NY - 1)}};
661 FluxRegister ref(rbox, nc); // N-level reflux (interface)
662 for (int lc = 0; lc < static_cast<int>(regs.size()); ++lc)
663 cfi.route_reflux(regs[lc], lv.dx, lv.dy, dt, ref, nc); // coverage-aware bordering reflux
664 ref.gather();
665 for (int pb = 0; pb < lv.U.local_size(); ++pb) { // application to the local parent boxes
666 Array4 c = lv.U.fab(pb).array();
667 const Box2D pbx = lv.U.box(pb);
668 for (int J = pbx.lo[1]; J <= pbx.hi[1]; ++J)
669 for (int I = pbx.lo[0]; I <= pbx.hi[0]; ++I) {
670 if (!ref.in(I, J))
671 continue; // outside interface: zero reflux
672 for (int k = 0; k < nc; ++k)
673 c(I, J, k) += ref.at(I, J, k);
674 }
675 }
676}
677
678// Driver: one dt step of the N-level multi-patch hierarchy (level 0 = coarse).
679template <class Limiter = NoSlope, class NumericalFlux = RusanovFlux, class Model>
680void amr_step_multilevel_multipatch(const Model& m, std::vector<AmrLevelMP>& L, const Box2D& dom,
681 Real dt, Periodicity per = Periodicity{true, true},
682 bool coarse_replicated = true, bool recon_prim = false,
683 bool imex = false, const NewtonOptions& nopts = {},
685 Real pos_floor = Real(0)) {
686 subcycle_level_mp<Limiter, NumericalFlux>(m, L, 0, dt, dom, per, nullptr, nullptr, Real(0),
687 nullptr, coarse_replicated, recon_prim, imex, nopts,
688 tmethod, pos_floor);
689}
690
691} // namespace detail
692
693} // namespace pops
Basic MultiFab building blocks of an AMR step: AmrTimeMethod enum, device-clean functors and advance ...
Named types of the multi-patch coarse-fine interface: PatchRange (coarse footprint of a fine patch),...
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
Box2D bounding_box() const
Smallest box enclosing all boxes (empty box if the tiling is empty).
Definition box_array.hpp:57
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 DistributionMapping & dmap() const
GLOBAL distribution (owner rank per box).
Definition multifab.hpp:58
int n_grow() const
Number of ghost layers.
Definition multifab.hpp:62
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
MultiFab arithmetic (saxpy, lincomb, norm_inf, dot) over VALID cells.
void ssprk3_advance_level(const Model &m, AmrLevelMP &lv, Real dt, MultiFab &fx, MultiFab &fy, bool recon_prim, int lev, const Box2D &base_dom, Periodicity base_per, const MultiFab *pOld, const MultiFab *pNew, Real frac, bool coarse_replicated, Real pos_floor=Real(0))
Definition amr_subcycling.hpp:398
void ssprk3_refill_level_ghosts(MultiFab &U, int lev, const Box2D &base_dom, Periodicity base_per, const MultiFab *pOld, const MultiFab *pNew, Real frac, bool coarse_replicated, Real pos_floor=Real(0), int pos_comp=0)
Definition amr_subcycling.hpp:370
void subcycle_level_mp(const Model &m, std::vector< AmrLevelMP > &L, int lev, Real dt, const Box2D &base_dom, Periodicity base_per, const MultiFab *pOld, const MultiFab *pNew, Real frac, std::vector< RegMP > *parentRegs, bool coarse_replicated=true, bool recon_prim=false, bool imex=false, const NewtonOptions &nopts={}, AmrTimeMethod tmethod=AmrTimeMethod::kEuler, Real pos_floor=Real(0))
Definition amr_subcycling.hpp:444
void amr_step_multilevel_multipatch(const Model &m, std::vector< AmrLevelMP > &L, const Box2D &dom, Real dt, Periodicity per=Periodicity{true, true}, bool coarse_replicated=true, bool recon_prim=false, bool imex=false, const NewtonOptions &nopts={}, AmrTimeMethod tmethod=AmrTimeMethod::kEuler, Real pos_floor=Real(0))
Definition amr_subcycling.hpp:680
Definition amr_hierarchy.hpp:29
int mf_find_box(const MultiFab &mf, int I, int J)
Definition amr_subcycling.hpp:231
void mf_apply_source(const Model &m, MultiFab &U, const MultiFab &aux, Real dt)
Definition amr_flux_helpers.hpp:128
double Real
Definition types.hpp:30
void mf_average_down_mb(const MultiFab &Uf, MultiFab &Uc)
Definition amr_subcycling.hpp:305
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
void mf_fill_fine_ghosts_mb(MultiFab &Uf, const MultiFab &Po, const MultiFab &Pn, Real frac, bool replicated_parent=true, Real pos_floor=Real(0), int pos_comp=0)
Definition amr_subcycling.hpp:259
void saxpy(MultiFab &y, Real a, const MultiFab &x)
y <- y + a x over ALL components of the valid cells. Identical layouts required.
Definition mf_arith.hpp:102
BoxArray coarsen_grown(const BoxArray &ba, int ngrow, int r)
Definition amr_subcycling.hpp:241
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
Box2D xface_box(const Box2D &v)
xface_box / yface_box: face boxes normal to x (resp.
Definition face_flux.hpp:147
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
void fill_boundary(MultiFab &mf, const Box2D &domain, Periodicity per={})
BLOCKING halo exchange: begin then end immediately (no overlap).
Definition fill_boundary.hpp:333
void mf_apply_source_treatment(const Model &m, MultiFab &U, const MultiFab &aux, Real dt, bool imex, const NewtonOptions &nopts={})
Definition amr_flux_helpers.hpp:158
Box2D yface_box(const Box2D &v)
Definition face_flux.hpp:150
AmrTimeMethod
Definition amr_flux_helpers.hpp:46
constexpr int kAmrRefRatio
The native AMR refinement ratio between two consecutive levels.
Definition refinement_ratio.hpp:30
void mf_advance_faces(MultiFab &U, const MultiFab &Fx, const MultiFab &Fy, Real dx, Real dy, Real dt)
Definition amr_flux_helpers.hpp:96
void lincomb(MultiFab &z, Real a, const MultiFab &x, Real b, const MultiFab &y)
z <- a x + b y over ALL components of the valid cells. Identical layouts; aliasing safe.
Definition mf_arith.hpp:133
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
void mf_fill_fine_ghosts_multi(MultiFab &Uf, const MultiFab &Uc_old, const MultiFab &Uc_new, Real frac)
Definition amr_patch_range.hpp:54
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
void amr_step_2level_multipatch(const Model &m, MultiFab &Uc, const Box2D &dom, Real dxc, Real dyc, MultiFab &Uf, const MultiFab &auxc, const MultiFab &auxf, Real dt)
Definition amr_subcycling.hpp:56
void mf_eval_rhs(const Model &m, const MultiFab &U, const MultiFab &aux, const MultiFab &Fx, const MultiFab &Fy, Real dx, Real dy, MultiFab &R)
Definition amr_flux_helpers.hpp:73
AMR inter-level transfer operators (integer ratio r) + parallel_copy.
Single source of truth for the native AMR refinement ratio.
Definition amr_subcycling.hpp:349
const MultiFab * aux
Definition amr_subcycling.hpp:351
Real dy
Definition amr_subcycling.hpp:352
Real dx
Definition amr_subcycling.hpp:352
MultiFab U
Definition amr_subcycling.hpp:350
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
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
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
Box2D intersect(const Box2D &o) const
Intersection of the two boxes (possibly empty: hi < lo if they do not overlap).
Definition box2d.hpp:95
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
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
void mark(const Box2D &b)
Definition amr_patch_range.hpp:161
bool covered(int I, int J) const
Definition amr_patch_range.hpp:168
Definition amr_patch_range.hpp:124
Real at(int I, int J, int k) const
Definition amr_patch_range.hpp:143
void gather()
Definition amr_patch_range.hpp:144
void set(int I, int J, int k, Real v)
Definition amr_patch_range.hpp:138
bool in(int I, int J) const
Definition amr_patch_range.hpp:137
Options of the local Newton of the implicit source (backward-Euler).
Definition implicit_stepper.hpp:114
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
Per-direction periodicity: halo wrapping in x and/or y during the exchange (false = open edge,...
Definition fill_boundary.hpp:37
Definition amr_subcycling.hpp:357
std::vector< Real > fT
Definition amr_subcycling.hpp:359
std::vector< Real > cR
Definition amr_subcycling.hpp:359
std::vector< Real > cL
Definition amr_subcycling.hpp:359
std::vector< Real > cT
Definition amr_subcycling.hpp:359
int J1
Definition amr_subcycling.hpp:358
std::vector< Real > fR
Definition amr_subcycling.hpp:359
int J0
Definition amr_subcycling.hpp:358
int I0
Definition amr_subcycling.hpp:358
std::vector< Real > fL
Definition amr_subcycling.hpp:359
int I1
Definition amr_subcycling.hpp:358
std::vector< Real > fB
Definition amr_subcycling.hpp:359
std::vector< Real > cB
Definition amr_subcycling.hpp:359
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