include/pops/coupling/single/coupler.hpp Source FileΒΆ

adc_cpp: include/pops/coupling/single/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
coupler.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <pops/coupling/base/aux_fill.hpp> // detail::derive_aux_bc + detail::fill_bz_box (shared)
11#include <pops/numerics/time/integrators/time_steppers.hpp> // SSPRK2Step / SSPRK3Step (shared scheme)
24
25#include <functional>
26#include <type_traits>
27#include <utility>
28
40
41namespace pops {
42
43namespace detail {
44// Namespace-scope helpers: an extended __host__ __device__ lambda CANNOT be
45// defined in a private/protected method (nvcc restriction), hence the
46// extraction out of the Coupler class.
47
48// Single-model compatibility: f = model.elliptic_rhs(U) on valid cells,
49// delegated to a named assembler so this responsibility is not buried in Coupler.
52template <class Model>
53inline void coupler_eval_rhs(const MultiFab& state, MultiFab& rhs, const Model& model) {
54 SingleModelEllipticRhs<Model>{model}(state, rhs);
55}
56
57// aux = (phi, d phi/dx, d phi/dy) by centered differences. Delegates to the
58// named FieldPostProcess convention with GradSign::Plus and store_phi=true: the
59// coupler stores +grad phi (the physical sign E = -grad phi is carried by the
60// transport drift velocity). Multiplicative form *cx / *cy kept identical
61// -> bit-identical.
64inline void coupler_grad_phi(const MultiFab& phi, MultiFab& aux, Real cx, Real cy) {
66}
67} // namespace detail
68
73template <class Model, class Elliptic = GeometricMG>
74class Coupler {
75 static_assert(EllipticSolver<Elliptic>, "the Coupler elliptic backend must model EllipticSolver");
76
77 public:
78 // active: optional "inside the conductor" predicate (embedded wall for
79 // the Poisson solver). Empty => no internal wall.
80 // bz: out-of-plane magnetic field B_z(x, y) PROVIDED by the user (constant or
81 // field). Only has effect if the model declares the B_z aux component (aux_comps>3);
82 // then fills aux component 3 once and for all (B_z static, external to the
83 // elliptic solve: derive_aux does not touch it). Empty => no B_z. The aux channel is
84 // allocated to the MODEL width: a base model (3) stays bit-identical.
85 Coupler(const Model& model, const Geometry& geom, const BoxArray& ba, const BCRec& bcU,
86 const BCRec& bcPhi, std::function<bool(Real, Real)> active = {},
87 std::function<Real(Real, Real)> bz = {})
88 : model_(model),
89 geom_(geom),
90 ba_(ba),
91 dm_(ba.size(), n_ranks()),
92 bcU_(bcU),
93 bcPhi_(bcPhi),
94 aux_bc_(detail::derive_aux_bc(bcPhi)),
95 mg_(geom, ba, bcPhi, std::move(active)),
96 aux_(ba, dm_, aux_comps<Model>(), 1),
97 bz_(std::move(bz)) {
98 fill_bz(); // fills the B_z component (no-op if base model or empty bz)
99 }
100
101 // Coupled SSPRK2. Three orthogonal axes, all template parameters:
102 // - Limiter: reconstruction (NoSlope / Minmod / VanLeer ...)
103 // - Policy: time coupling (PerStage = phi at each stage; OncePerStep
104 // = a single solve per step, aux frozen)
105 // - NumericalFlux: Riemann flux (Rusanov by default, HLL, HLLC ...)
106 // U must carry at least Limiter::n_ghost ghosts. The historical signature
107 // advance<Limiter, Policy> stays valid (NumericalFlux default = Rusanov).
108 template <class Limiter = NoSlope, class Policy = PerStageCoupling,
109 class NumericalFlux = RusanovFlux>
110 void advance(MultiFab& U, Real dt) {
111 static_assert(
112 std::is_same_v<Policy, PerStageCoupling> || std::is_same_v<Policy, OncePerStepCoupling>,
113 "Policy must be PerStageCoupling or OncePerStepCoupling");
114 constexpr bool per = std::is_same_v<Policy, PerStageCoupling>;
115 // DELEGATES the scheme to the core SSPRK2Step object (dedup, sec.8.2 A4). The residual
116 // evaluator counts the stages: recompute_aux=true at stage 0, =per afterward (PerStage:
117 // phi recomputed for the intermediate state; OncePerStep: aux frozen). Bit-identical.
118 int stage = 0;
120 [&](MultiFab& s, MultiFab& R) {
121 stage_rhs<Limiter, NumericalFlux>(s, R, (stage++ == 0) ? true : per);
122 },
123 U, dt);
124 }
125
126 // Coupled SSPRK3 (Shu-Osher, 3 stages). Same axes as advance.
127 template <class Limiter = NoSlope, class Policy = PerStageCoupling,
128 class NumericalFlux = RusanovFlux>
130 static_assert(
131 std::is_same_v<Policy, PerStageCoupling> || std::is_same_v<Policy, OncePerStepCoupling>,
132 "Policy must be PerStageCoupling or OncePerStepCoupling");
133 constexpr bool per = std::is_same_v<Policy, PerStageCoupling>;
134 // Same as advance: delegates to SSPRK3Step, recompute_aux=true at stage 0, =per afterward.
135 int stage = 0;
137 [&](MultiFab& s, MultiFab& R) {
138 stage_rhs<Limiter, NumericalFlux>(s, R, (stage++ == 0) ? true : per);
139 },
140 U, dt);
141 }
142
143 // Unified entry point: give the coupler a SPATIAL DISCRETISATION
144 // (limiter + flux) and an explicit time policy. The old SSPRK2/SSPRK3 tags
145 // stay valid; the new form also enables sub-cycling:
146 // sim.step<MusclVanLeerHLLC, ExplicitTime<SSPRK3, 4>>(U, dt);
147 template <class Disc = FirstOrder, class TimeInteg = SSPRK2, class Policy = PerStageCoupling>
148 void step(MultiFab& U, Real dt) {
149 using L = typename Disc::Limiter;
150 using F = typename Disc::NumericalFlux;
151 using T = typename TimePolicyTraits<TimeInteg>::Method;
153 "Coupler::step can only run explicit policies; "
154 "use a scheduler/system for IMEX or implicit");
155 static_assert(std::is_same_v<T, SSPRK2> || std::is_same_v<T, SSPRK3>,
156 "Coupler::step supports SSPRK2 or SSPRK3");
158 const Real h = dt / static_cast<Real>(n);
159 for (int s = 0; s < n; ++s) {
160 if constexpr (std::is_same_v<T, SSPRK3>)
161 advance_ssprk3<L, Policy, F>(U, h);
162 else
163 advance<L, Policy, F>(U, h);
164 }
165 }
166
169 void solve_fields(const MultiFab& U) { update_aux(U); }
170
171 MultiFab& phi() { return mg_.phi(); }
172 const MultiFab& aux() const { return aux_; }
173
174 private:
175 void update_aux(const MultiFab& state) {
176 detail::coupler_eval_rhs(state, mg_.rhs(), model_);
177 mg_.solve(); // EllipticSolver concept interface (backend-agnostic)
178 derive_aux();
179 }
180
181 // One stage: (optional) elliptic solve, halos, hyperbolic residual into R.
182 // Shared by advance (SSPRK2) and advance_ssprk3; order of operations kept
183 // to stay bit-identical to the old advance.
184 template <class Limiter, class NumericalFlux>
185 void stage_rhs(MultiFab& s, MultiFab& R, bool recompute_aux) {
186 if (recompute_aux)
187 update_aux(s);
188 fill_ghosts(s, geom_.domain, bcU_);
189 assemble_rhs<Limiter, NumericalFlux>(model_, s, aux_, geom_, R);
190 }
191
192 void derive_aux() {
193 fill_ghosts(mg_.phi(), geom_.domain, bcPhi_);
194 const Real cx = Real(1) / (2 * geom_.dx());
195 const Real cy = Real(1) / (2 * geom_.dy());
196 detail::coupler_grad_phi(mg_.phi(), aux_, cx, cy);
197 fill_ghosts(aux_, geom_.domain, aux_bc_);
198 }
199
200 // Fills the B_z aux component (index kAuxBaseComps) on valid cells from
201 // bz_(x, y), once only (B_z static). Compile-time guard: without a B_z field in the
202 // model (aux_comps == 3) the component does not exist -> no code, no out-of-bound access.
203 // The B_z halos are then maintained by derive_aux (Foextrap/periodic of aux_bc_,
204 // cf. grad); field_postprocess only writes phi/grad (components 0..2), B_z is preserved.
205 void fill_bz() {
206 if constexpr (aux_comps<Model>() > kAuxBaseComps) {
207 if (!bz_)
208 return;
209 for (int li = 0; li < aux_.local_size(); ++li)
210 detail::fill_bz_box(aux_.fab(li), aux_.box(li), geom_, bz_); // valid box
211 fill_ghosts(aux_, geom_.domain, aux_bc_); // B_z halos before the 1st solve
212 }
213 }
214
215 Model model_;
216 Geometry geom_;
217 BoxArray ba_;
218 DistributionMapping dm_;
219 BCRec bcU_, bcPhi_, aux_bc_;
220 Elliptic mg_;
221 MultiFab aux_;
222 std::function<Real(Real, Real)> bz_; // external B_z(x, y) (empty if not provided)
223};
224
225// The coupler elliptic backend honors the common contract: swapping
226// GeometricMG for another conforming solver (FFT wrapper, PETSc) will only
227// require changing the member type, not the coupling logic.
228static_assert(EllipticSolver<GeometricMG>, "GeometricMG must model the EllipticSolver concept");
229
230} // namespace pops
Helpers shared by the three couplers (single-block Coupler, SystemAssembler, AmrSystemCoupler) for th...
BoxArray: the set of boxes tiling a level (disjoint, covering).
Ordered list of boxes tiling a level.
Definition box_array.hpp:22
Single-block hyperbolic-elliptic coupler.
Definition coupler.hpp:74
Coupler(const Model &model, const Geometry &geom, const BoxArray &ba, const BCRec &bcU, const BCRec &bcPhi, std::function< bool(Real, Real)> active={}, std::function< Real(Real, Real)> bz={})
Definition coupler.hpp:85
void step(MultiFab &U, Real dt)
Definition coupler.hpp:148
void advance_ssprk3(MultiFab &U, Real dt)
Definition coupler.hpp:129
const MultiFab & aux() const
Definition coupler.hpp:172
MultiFab & phi()
Definition coupler.hpp:171
void advance(MultiFab &U, Real dt)
Definition coupler.hpp:110
void solve_fields(const MultiFab &U)
Solve phi and derive aux = (phi, grad phi) for U WITHOUT advancing in time (useful to estimate the E ...
Definition coupler.hpp:169
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
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 elliptic_solver.hpp:30
Hyperbolic-elliptic temporal coupling policies (compile-time tag types).
DistributionMapping: maps each box (by global index) to its owning MPI rank.
DESCRIPTIVE types of the elliptic stage: EllipticProblem (problem definition) and FieldPostProcess (f...
Elliptic (Poisson) RIGHT-HAND-SIDE assemblers: single-model and N-species.
EllipticSolver concept: common contract for elliptic solvers at the MultiFab level (solve D phi = f),...
Fab2D: single-grid data on a Box2D (in-house equivalent of AMReX's FArrayBox); Array4 / ConstArray4: ...
for_each_cell and reductions: the parallelism SEAM over the cells of a Box2D; sync_host / sync_device...
GeometricMG: in-house geometric multigrid (V-cycle) for the elliptic operator, Gauss-Seidel smoother ...
Geometry: index-space (Box2D) <-> Cartesian physical-space mapping; PolarGeometry: SIBLING for a glob...
MultiFab arithmetic (saxpy, lincomb, norm_inf, dot) over VALID cells.
MultiFab: a field DISTRIBUTED over a level (equivalent of AMReX's MultiFab).
void coupler_eval_rhs(const MultiFab &state, MultiFab &rhs, const Model &model)
Assemble the single-model elliptic RHS: rhs = model.elliptic_rhs(U) on valid cells (delegated to Sing...
Definition coupler.hpp:53
void fill_bz_box(Fab2D &f, const Box2D &box, const Geometry &g, const Bz &bz)
Writes B_z(x, y) at component kAuxBaseComps on box box of fab f, sampling bz at the cell centers of g...
Definition aux_fill.hpp:42
void coupler_grad_phi(const MultiFab &phi, MultiFab &aux, Real cx, Real cy)
Set aux = (phi, d phi/dx, d phi/dy) by centered differences (factors cx, cy = 1/(2 dx),...
Definition coupler.hpp:64
Definition amr_hierarchy.hpp:29
constexpr int kAuxBaseComps
Definition state.hpp:147
double Real
Definition types.hpp:30
int n_ranks()
Definition comm.hpp:139
POPS_HD constexpr int aux_comps()
Width of the aux channel a model CONSUMES.
Definition physical_model.hpp:67
void field_postprocess(const MultiFab &phi, MultiFab &out, Real cx, Real cy, FieldPostProcess spec)
Definition elliptic_problem.hpp:104
void fill_ghosts(MultiFab &mf, const Box2D &domain, const BCRec &bc)
COMPLETE ghost filling: fill_boundary (interior + periodic, periodicity deduced from bc) THEN fill_ph...
Definition physical_bc.hpp:227
PHYSICAL boundary conditions at the domain edge (BCType, BCRec, fill_physical_bc, fill_ghosts).
Interface reconstruction policies: MUSCL limiters and WENO5-Z.
"Spatial method" aggregate: reconstruction (limiter) + numerical flux in one named type.
Cartesian spatial operator: assembles R(U, aux) = -div F + S over the cells of a level.
Boundary conditions for the FOUR faces of the domain (type + associated Dirichlet value).
Definition physical_bc.hpp:29
Definition elliptic_problem.hpp:73
Cartesian geometry of a level: index domain + physical bounds [xlo, xhi] x [ylo, yhi].
Definition geometry.hpp:20
POPS_HD Real dy() const
Grid spacing in y (= (yhi - ylo) / domain.ny()). POPS_HD.
Definition geometry.hpp:33
POPS_HD Real dx() const
Grid spacing in x (= (xhi - xlo) / domain.nx()). POPS_HD.
Definition geometry.hpp:31
Box2D domain
Definition geometry.hpp:21
First-order reconstruction (piecewise constant): zero slope, 1 ghost.
Definition reconstruction.hpp:26
Tag: solves the elliptic problem at EVERY RK stage (aux follows the intermediate state,...
Definition coupling_policy.hpp:17
RusanovFlux (local Lax-Friedrichs): robust flux, compatible with any minimal PhysicalModel.
Definition numerical_flux.hpp:55
Definition time_steppers.hpp:61
void take_step(RhsEval &&rhs, MultiFab &U, Real dt, Scratch &s) const
Definition time_steppers.hpp:73
Definition time_steppers.hpp:90
void take_step(RhsEval &&rhs, MultiFab &U, Real dt, Scratch &s) const
Definition time_steppers.hpp:104
SINGLE-model RHS assembler: rhs(.,.,0) = model.elliptic_rhs(U) over the valid cells.
Definition elliptic_rhs.hpp:43
Definition time_integrator.hpp:46
T Method
Definition time_integrator.hpp:47
Scheme tags (SSPRK2, SSPRK3, UserTimeIntegrator), TimeTreatment enum and per-block time policies: tem...
Time integrators as first-class OBJECTS with a take_step method: TimeStepper concept,...
Base scalar types and the POPS_HD macro (host+device portability).