include/pops/numerics/time/amr/reflux/amr_flux_helpers.hpp Source FileΒΆ

adc_cpp: include/pops/numerics/time/amr/reflux/amr_flux_helpers.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_flux_helpers.hpp
Go to the documentation of this file.
1#pragma once
2
9#include <pops/mesh/layout/refinement.hpp> // coarsen_index
10#include <pops/numerics/spatial_operator.hpp> // compute_face_fluxes, xface_box, yface_box
11#include <pops/numerics/time/integrators/implicit_stepper.hpp> // backward_euler_source (IMEX implicit step)
12
13#include <vector>
14
36
37namespace pops {
38
39static_assert(kAmrRefRatio == 2, "ratio-2-structural kernels below assume kAmrRefRatio == 2");
40
41// Time method of an AMR step (Berger-Oliger subcycling). kEuler (DEFAULT) = forward Euler
42// advance at each substep (legacy path, strictly bit-identical); kSsprk3 = SSPRK3
43// (Shu-Osher, 3 stages, order 3) with per-stage reflux (convex effective flux). The enum is passed
44// BY VALUE (POD) along the advance_amr -> subcycle_level_mp path; the flat ABI of the .so loader
45// carries it as an integer (AmrBuildParams::time_method), 0 == kEuler.
46enum class AmrTimeMethod : int { kEuler = 0, kSsprk3 = 1 };
47
48// Device-clean NAMED functor (same recipe as mf_arith.hpp: a first instantiation possible
49// from an external loader TU, or an extended lambda makes nvcc choke) of the method-of-lines RHS
50// at ONE AMR level: R = -div(Fx,Fy) + S(U, aux), evaluated at ONE SAME state. It is the divergence of
51// mf_advance_faces (opposite sign, without dt) FUSED with the source of mf_apply_source. Used
52// ONLY by the SSPRK3 stages (mf_eval_rhs), where L(U) = -div F + S must be taken at the same
53// stage state (true method-of-lines SSPRK), unlike the transport-then-source splitting of the
54// Euler path. Without a source (model with S == 0) R reduces to -div F.
55template <class Model>
57 Model m;
61 POPS_HD void operator()(int i, int j) const {
62 const auto S = m.source(load_state<Model>(u, i, j), load_aux<aux_comps<Model>()>(ax, i, j));
63 for (int c = 0; c < Model::n_vars; ++c)
64 R(i, j, c) =
65 -((fx(i + 1, j, c) - fx(i, j, c)) / dx + (fy(i, j + 1, c) - fy(i, j, c)) / dy) + S[c];
66 }
67};
68
69// R <- -div(Fx,Fy) + S(U, aux) on the valid cells (method-of-lines RHS at ONE level, evaluated
70// at the state U). Fused "combine" of mf_advance_faces + mf_apply_source for the SSPRK3 stages (the
71// stage flux Fx/Fy is assumed already computed by compute_face_fluxes at the state U).
72template <class Model>
73inline void mf_eval_rhs(const Model& m, const MultiFab& U, const MultiFab& aux, const MultiFab& Fx,
74 const MultiFab& Fy, Real dx, Real dy, MultiFab& R) {
75 for (int li = 0; li < U.local_size(); ++li)
76 for_each_cell(U.box(li),
77 AmrSspRhsKernel<Model>{m, U.fab(li).const_array(), aux.fab(li).const_array(),
78 Fx.fab(li).const_array(), Fy.fab(li).const_array(),
79 R.fab(li).array(), dx, dy});
80}
81
87 int nc;
88 POPS_HD void operator()(int i, int j) const {
89 for (int c = 0; c < nc; ++c)
90 u(i, j, c) -=
91 dt * ((fx(i + 1, j, c) - fx(i, j, c)) / dx + (fy(i, j + 1, c) - fy(i, j, c)) / dy);
92 }
93};
94
95// U <- U - dt div(Fx,Fy) on the valid cells (GPU via for_each_cell).
96inline void mf_advance_faces(MultiFab& U, const MultiFab& Fx, const MultiFab& Fy, Real dx, Real dy,
97 Real dt) {
98 const int nc = U.ncomp();
99 for (int li = 0; li < U.local_size(); ++li) {
100 Array4 u = U.fab(li).array();
101 const ConstArray4 fx = Fx.fab(li).const_array(), fy = Fy.fab(li).const_array();
102 for_each_cell(U.box(li), AmrAdvanceFacesKernel{u, fx, fy, dx, dy, dt, nc});
103 }
104}
105
106// U <- U + dt S(U, aux) on the valid cells: source term applied with forward Euler
107// at each AMR substep (cell-local, no reflux). Without it the AMR path
108// (compute_face_fluxes -> divergence) would ignore model.source. For a model with a null
109// source (pure scalar transport) this adds dt*0: bit-identical. DIFFUSION, in contrast, is carried
110// by compute_face_fluxes as a Fickian face FLUX (-nu grad u), thus seen by the
111// reflux and conservative at coarse-fine interfaces: it is NOT a local source.
114template <class Model>
116 Model m;
120 POPS_HD void operator()(int i, int j) const {
121 const auto S = m.source(load_state<Model>(uc, i, j), load_aux<aux_comps<Model>()>(ax, i, j));
122 for (int c = 0; c < Model::n_vars; ++c)
123 u(i, j, c) += dt * S[c];
124 }
125};
126
127template <class Model>
128inline void mf_apply_source(const Model& m, MultiFab& U, const MultiFab& aux, Real dt) {
129 for (int li = 0; li < U.local_size(); ++li) {
130 Array4 u = U.fab(li).array();
131 const ConstArray4 uc = U.fab(li).const_array();
132 const ConstArray4 ax = aux.fab(li).const_array();
133 for_each_cell(U.box(li), AmrApplySourceKernel<Model>{m, u, uc, ax, dt});
134 }
135}
136
137// Temporal treatment of the SOURCE at an AMR substep, after the transport advance
138// (mf_advance_faces, already without source since compute_face_fluxes only carries model.flux):
139// - EXPLICIT (imex == false, DEFAULT): forward Euler, U += dt S(U, aux) -- the legacy
140// mf_apply_source call, thus bit-identical to the existing path.
141// - IMEX (imex == true): stiff IMPLICIT source, W = U + dt S(W, aux) solved IN PLACE by
142// backward_euler_source (local Newton, finite-difference Jacobian, NAMED device functor
143// BackwardEulerSourceKernel). It is the AMR counterpart of the System IMEX advance
144// (block_builder.hpp::AdvanceImex): same explicit half-step (transport is carried by the
145// conservative reflux) + same implicit step on the source. The source remaining CELL-LOCAL
146// (no face flux), it does NOT enter the reflux registers: the implicit split thus does
147// not touch conservation at coarse-fine interfaces. The CHOICE is a runtime flag
148// (no lambda injected into the device path): it selects two HOST functions, each
149// launching its own named-functor kernel.
150//
151// NEWTON OPTIONS (@p nopts): drive the local Newton of the implicit source (iteration budget,
152// tolerances, fd_eps, damping, fail_policy). DEFAULT {} = legacy constants (2 iters, 1e-7, ...)
153// -> path (2a) bit-identical to the old call backward_euler_source(m, aux, U, dt). The AMR mono-block
154// (AmrCouplerMP::step) threads them from AmrSystem (wave 3 -> mono-block options wired). The partial
155// IMEX mask is NOT carried by this path (mono-block coupler = full backward-Euler): so the
156// default mask (inactive) is passed. No diagnostics report here (report == nullptr implicit).
157template <class Model>
158inline void mf_apply_source_treatment(const Model& m, MultiFab& U, const MultiFab& aux, Real dt,
159 bool imex, const NewtonOptions& nopts = {}) {
160 if (imex)
161 // OPTIONS form (Newton driven by nopts), inactive mask, no report. Default nopts={} =>
162 // identical to the legacy form with fixed iters (2), thus bit-identical as long as nopts is default.
163 backward_euler_source(m, aux, U, dt, nopts, ImplicitMask<Model::n_vars>{});
164 else
165 mf_apply_source(m, U, aux, dt); // legacy forward Euler (bit-identical)
166}
167
172 int nc;
173 POPS_HD void operator()(int I, int J) const {
174 for (int k = 0; k < nc; ++k)
175 c(I, J, k) = Real(0.25) * (f(2 * I, 2 * J, k) + f(2 * I + 1, 2 * J, k) +
176 f(2 * I, 2 * J + 1, k) + f(2 * I + 1, 2 * J + 1, k));
177 }
178};
179
180// average fine -> coarse (ratio 2) on the covered region (coarse coords).
181inline void mf_average_down(const MultiFab& Uf, MultiFab& Uc, int CI0, int CI1, int CJ0, int CJ1) {
182 const int nc = Uc.ncomp();
183 const ConstArray4 f = Uf.fab(0).const_array();
184 Array4 c = Uc.fab(0).array();
185 for_each_cell(Box2D{{CI0, CJ0}, {CI1, CJ1}}, AmrAverageDownKernel{f, c, nc});
186}
187
188// First-level coarse-fine helper (review, point ghosts): fills ONE fine ghost
189// cell (i,j) by spatial interpolation (piecewise constant: covering coarse cell)
190// + time (linear between the old/new parent state). frac = temporal position of the
191// substep within the parent step. Centralizes the arithmetic shared by mf_fill_fine_ghosts_t
192// (mono-box), mf_fill_fine_ghosts_multi (multi-box) and mf_fill_fine_ghosts_mb (multi-level):
193// a single formula (1-frac)*co + frac*cn, bit-identical to the three previous bodies.
194inline void fill_cf_ghost_cell(Array4 f, const ConstArray4& co, const ConstArray4& cn, int i, int j,
195 int nc, Real frac, Real pos_floor = Real(0), int pos_comp = 0) {
196 const int ci = coarsen_index(i, kAmrRefRatio), cj = coarsen_index(j, kAmrRefRatio);
197 for (int k = 0; k < nc; ++k)
198 f(i, j, k) = (1 - frac) * co(ci, cj, k) + frac * cn(ci, cj, k);
199 // Zhang-Shu positivity floor on the C/F fine GHOST MEAN (ADC-259): clamp the Density role only
200 // (pos_comp, resolved on the host by the caller via positivity_comp<Model>) to >= pos_floor. The
201 // refined-patch C/F interface is the highest-risk site: reconstruct_pp's order-1 fallback brings a
202 // sub-floor face back to its SOURCE-CELL mean, and at a fine cell bordering the interface that
203 // source is a ghost; without this clamp the fallback target itself could be sub-floor (the coarse
204 // mean is not floored), defeating the guarantee. Momenta/energy stay interpolated -> the ghost
205 // velocity m/rho only DROPS at quasi-vacuum (bounded, mirror of the single-block mean fallback).
206 // pos_floor <= 0 short-circuits (bit-identical). Ghost cells are never averaged-down nor summed in
207 // mass, so the clamp is conservation-safe (cf. ADC-259 design: average-down immunity + the reflux
208 // coarse-side register reads a separate fab, so the two-sided telescoping is preserved exactly).
209 if (pos_floor > Real(0) && f(i, j, pos_comp) < pos_floor)
210 f(i, j, pos_comp) = pos_floor;
211}
212
213// fine ghosts = spatial interp (piecewise constant) + time (linear) from the
214// old/new coarse. frac = temporal position of the substep within the coarse step.
215inline void mf_fill_fine_ghosts_t(MultiFab& Uf, const MultiFab& Uc_old, const MultiFab& Uc_new,
216 Real frac, Real pos_floor = Real(0), int pos_comp = 0) {
217 device_fence(); // host read/write on unified memory
218 const int nc = Uf.ncomp();
219 Array4 f = Uf.fab(0).array();
220 const ConstArray4 co = Uc_old.fab(0).const_array();
221 const ConstArray4 cn = Uc_new.fab(0).const_array();
222 const Box2D v = Uf.box(0), g = Uf.fab(0).grown_box();
223 for (int j = g.lo[1]; j <= g.hi[1]; ++j)
224 for (int i = g.lo[0]; i <= g.hi[0]; ++i)
225 if (!v.contains(i, j))
226 fill_cf_ghost_cell(f, co, cn, i, j, nc, frac, pos_floor, pos_comp);
227}
228
229} // namespace pops
Box2D: the integer index space of a 2D cell-centered Cartesian grid.
BoxArray: the set of boxes tiling a level (disjoint, covering).
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
fill_boundary: INTRA-level halo exchange (fills ghosts from neighbors).
for_each_cell and reductions: the parallelism SEAM over the cells of a Box2D; sync_host / sync_device...
Implicit / IMEX block step as a named CONTRACT.
MultiFab: a field DISTRIBUTED over a level (equivalent of AMReX's MultiFab).
Definition amr_hierarchy.hpp:29
void mf_apply_source(const Model &m, MultiFab &U, const MultiFab &aux, Real dt)
Definition amr_flux_helpers.hpp:128
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
double Real
Definition types.hpp:30
POPS_HD Aux load_aux(const ConstArray4 &a, int i, int j)
load_aux<NComp>: reads NComp components of the auxiliary from an Array4 at (i,j).
Definition state_access.hpp:141
void backward_euler_source(const Model &model, const MultiFab &aux, MultiFab &U, Real dt, const NewtonOptions &opts, const ImplicitMask< Model::n_vars > &mask={}, NewtonReport *report=nullptr)
Definition implicit_stepper.hpp:486
void mf_fill_fine_ghosts_t(MultiFab &Uf, const MultiFab &Uc_old, const MultiFab &Uc_new, Real frac, Real pos_floor=Real(0), int pos_comp=0)
Definition amr_flux_helpers.hpp:215
void mf_average_down(const MultiFab &Uf, MultiFab &Uc, int CI0, int CI1, int CJ0, int CJ1)
Definition amr_flux_helpers.hpp:181
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 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
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
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 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.
Cartesian spatial operator: assembles R(U, aux) = -div F + S over the cells of a level.
Device-clean NAMED functor: U <- U - dt div(Fx,Fy) on a valid cell.
Definition amr_flux_helpers.hpp:83
Real dx
Definition amr_flux_helpers.hpp:86
Real dy
Definition amr_flux_helpers.hpp:86
ConstArray4 fy
Definition amr_flux_helpers.hpp:85
int nc
Definition amr_flux_helpers.hpp:87
ConstArray4 fx
Definition amr_flux_helpers.hpp:85
Array4 u
Definition amr_flux_helpers.hpp:84
Real dt
Definition amr_flux_helpers.hpp:86
POPS_HD void operator()(int i, int j) const
Definition amr_flux_helpers.hpp:88
Device-clean NAMED functor (template Model, see AmrSspRhsKernel): U <- U + dt S(U,...
Definition amr_flux_helpers.hpp:115
ConstArray4 uc
Definition amr_flux_helpers.hpp:118
Model m
Definition amr_flux_helpers.hpp:116
Real dt
Definition amr_flux_helpers.hpp:119
ConstArray4 ax
Definition amr_flux_helpers.hpp:118
POPS_HD void operator()(int i, int j) const
Definition amr_flux_helpers.hpp:120
Array4 u
Definition amr_flux_helpers.hpp:117
Device-clean NAMED functor: 2x2 average fine -> coarse on a coarse cell.
Definition amr_flux_helpers.hpp:169
ConstArray4 f
Definition amr_flux_helpers.hpp:170
Array4 c
Definition amr_flux_helpers.hpp:171
int nc
Definition amr_flux_helpers.hpp:172
POPS_HD void operator()(int I, int J) const
Definition amr_flux_helpers.hpp:173
Definition amr_flux_helpers.hpp:56
POPS_HD void operator()(int i, int j) const
Definition amr_flux_helpers.hpp:61
ConstArray4 fx
Definition amr_flux_helpers.hpp:58
Real dy
Definition amr_flux_helpers.hpp:60
Real dx
Definition amr_flux_helpers.hpp:60
Model m
Definition amr_flux_helpers.hpp:57
Array4 R
Definition amr_flux_helpers.hpp:59
ConstArray4 ax
Definition amr_flux_helpers.hpp:58
ConstArray4 fy
Definition amr_flux_helpers.hpp:58
ConstArray4 u
Definition amr_flux_helpers.hpp:58
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 contains(int i, int j) const
true if cell (i, j) is inside the box (lo/hi bounds inclusive).
Definition box2d.hpp:61
READ-only handle (const counterpart of Array4): same layout and same contract (POD device-copyable,...
Definition fab2d.hpp:44
Options of the local Newton of the implicit source (backward-Euler).
Definition implicit_stepper.hpp:114
#define POPS_HD
Definition types.hpp:25