include/pops/numerics/spatial/operators/polar_operator.hpp Source File

adc_cpp: include/pops/numerics/spatial/operators/polar_operator.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
polar_operator.hpp
Go to the documentation of this file.
1#pragma once
2
8#include <pops/mesh/geometry/geometry.hpp> // PolarGeometry
12#include <pops/numerics/spatial_operator.hpp> // reconstruct<>, load_state/load_aux (REUSED verbatim)
13
14#include <concepts>
15#include <utility>
16#include <vector>
17
46
47namespace pops {
48
49namespace detail {
50
51// The source term is OPTIONAL in polar transport (Phase 1): a pure transport brick
52// (ExBVelocityPolar) does not expose source(), only a composite model (CompositeModel) does. We
53// detect it and fall back to 0 otherwise -> the polar operator works just as well on the standalone
54// brick as on a composite model, without requiring source() (the Cartesian path, for its part,
55// assumes a composite model).
60template <class M>
61concept PolarHasSource = requires(const M m, const typename M::State u, const Aux a) {
62 { m.source(u, a) } -> std::convertible_to<typename M::State>;
63};
64
68template <class Model>
69POPS_HD inline typename Model::State polar_source(const Model& m, const typename Model::State& u,
70 const Aux& a) {
71 if constexpr (PolarHasSource<Model>)
72 return m.source(u, a);
73 else
74 return typename Model::State{};
75}
76
84template <class M>
85concept PolarHasGeomSource = requires(const M m, const typename M::State u, Real r) {
86 { m.polar_geom_source(u, r) } -> std::convertible_to<typename M::State>;
87};
88
92template <class Model>
93POPS_HD inline typename Model::State polar_geom_source(const Model& m,
94 const typename Model::State& u, Real r) {
95 if constexpr (PolarHasGeomSource<Model>)
96 return m.polar_geom_source(u, r);
97 else
98 return typename Model::State{};
99}
100
101// RADIAL FACE FLUX kernel (dir 0): numerical flux at the radial face i (between i-1 and i), ALREADY
102// weighted by the face radius r_face(i). We store r_{i-1/2} Fr_{i-1/2} so the divergence is a
103// simple difference. NAMED FUNCTOR (device-clean cross-TU).
110template <class Limiter, class NumericalFlux, class Model>
112 Model model;
114 Array4 fr; // output: r_face(i) * Fr at the radial face i (ncomp components)
115 Real r_min, dr; // radial geometry (r_face(i) = r_min + i*dr)
116 Limiter lim;
117 NumericalFlux nflux;
119 // Optional RADIAL WALL (no-penetration). wall_radial == false (default): no effect, boundary flux
120 // computed like the interior (BIT-IDENTICAL to the history: MMS, azimuthal conservation). true:
121 // the radial flux at BOTH physical boundary faces (i = i_lo_face = lo, i = i_hi_face = hi+1) is
122 // forced to ZERO -> the radial term telescopes EXACTLY (each interior face is shared, the
123 // boundaries no longer count) -> mass Sum n r dr dtheta conserved to machine precision, whatever
124 // v_r (solid wall).
127 i_hi_face; // FACE indices of physical boundaries (lo and hi+1); ignored if !wall_radial
129 int pos_comp = 0;
130 POPS_HD void operator()(int i, int j) const {
131 const Real rf = r_min + i * dr; // r_face(i) (positive on the annulus: r_min >= 0, i >= 0)
132 if (wall_radial && (i == i_lo_face || i == i_hi_face)) {
133 for (int c = 0; c < Model::n_vars; ++c)
134 fr(i, j, c) = Real(0); // wall: zero radial flux
135 return;
136 }
137 // Reconstructed states on either side of the radial face i (REUSES Cartesian reconstruct_pp<>,
138 // dir == 0). L = extrapolation from cell i-1 toward its + face; R = from cell i toward its
139 // - face.
140 const auto L =
141 reconstruct_pp<Model>(model, u, i - 1, j, 0, +1, lim, recon_prim, pos_floor, pos_comp);
142 const auto Rr =
143 reconstruct_pp<Model>(model, u, i, j, 0, -1, lim, recon_prim, pos_floor, pos_comp);
144 const auto F = nflux(model, L, load_aux<aux_comps<Model>()>(ax, i - 1, j), Rr,
145 load_aux<aux_comps<Model>()>(ax, i, j), 0);
146 for (int c = 0; c < Model::n_vars; ++c)
147 fr(i, j, c) = rf * F[c];
148 }
149};
150
151// AZIMUTHAL FACE FLUX kernel (dir 1): numerical flux at the theta face j (between j-1 and j). NO
152// weighting by r (the azimuthal metric 1/r is applied in cell, cf. PolarAssembleRhsKernel).
157template <class Limiter, class NumericalFlux, class Model>
159 Model model;
161 Array4 ft; // output: Ftheta at the azimuthal face j (ncomp components)
162 Limiter lim;
163 NumericalFlux nflux;
166 int pos_comp = 0;
167 POPS_HD void operator()(int i, int j) const {
168 const auto L =
169 reconstruct_pp<Model>(model, u, i, j - 1, 1, +1, lim, recon_prim, pos_floor, pos_comp);
170 const auto Rr =
171 reconstruct_pp<Model>(model, u, i, j, 1, -1, lim, recon_prim, pos_floor, pos_comp);
172 const auto F = nflux(model, L, load_aux<aux_comps<Model>()>(ax, i, j - 1), Rr,
173 load_aux<aux_comps<Model>()>(ax, i, j), 1);
174 for (int c = 0; c < Model::n_vars; ++c)
175 ft(i, j, c) = F[c];
176 }
177};
178
179// Polar residual assembly kernel in cell (i, j):
180// R = S - (1/r_i) (Fr_weighted_{i+1} - Fr_weighted_i) / dr - (1/r_i) (Ftheta_{j+1} - Ftheta_j) / dtheta
181// Fr_weighted = r_face * Fr (already produced by PolarFaceFluxRKernel). NAMED FUNCTOR.
186template <class Model>
188 Model model;
189 ConstArray4 u, ax, fr, ft; // state, aux, r-weighted radial flux, azimuthal flux
190 Array4 r; // output: residual
192 POPS_HD void operator()(int i, int j) const {
193 const Real ri = r_min + (i + Real(0.5)) * dr; // r_cell(i)
194 const Real inv_r = Real(1) / ri;
195 const Aux Ac = load_aux<aux_comps<Model>()>(ax, i, j);
196 const auto Us = load_state<Model>(u, i, j);
197 const auto S = polar_source<Model>(model, Us, Ac);
198 // GEOMETRIC curvature term (centrifugal + cross curvature): not captured by the conservative
199 // divergence in the rotating local basis (e_r, e_theta). Zero for scalar bricks (polar ExB =
200 // bit-identical); non-zero for a polar fluid (IsothermalFluxPolar). See PolarHasGeomSource /
201 // IsothermalFluxPolar for the derivation. r_cell(i) > 0 (annulus).
202 const auto Sg = polar_geom_source<Model>(model, Us, ri);
203 for (int c = 0; c < Model::n_vars; ++c) {
204 const Real div_r = (fr(i + 1, j, c) - fr(i, j, c)) / dr; // d_r(r Fr) discrete
205 const Real div_t = (ft(i, j + 1, c) - ft(i, j, c)) / dtheta; // d_theta(Ftheta) discrete
206 r(i, j, c) = S[c] + Sg[c] - inv_r * (div_r + div_t);
207 }
208 }
209};
210
211// Radial / azimuthal FACE boxes (cf. Cartesian xface_box/yface_box): radial faces = nr+1 x ntheta,
212// azimuthal faces = nr x ntheta+1. Reuses the Cartesian helpers (same face index conventions) to
213// avoid duplication.
214} // namespace detail
215
229template <class Limiter = NoSlope, class NumericalFlux = RusanovFlux, class Model>
230void assemble_rhs_polar(const Model& model, const MultiFab& U, const MultiFab& aux,
231 const PolarGeometry& geom, MultiFab& R, bool recon_prim = false,
232 bool wall_radial = false, Real pos_floor = Real(0)) {
233 // STATE-GHOST WIDTH: exactly Limiter::n_ghost, like the Cartesian operator. The polar face kernels
234 // (PolarFaceFluxRKernel / PolarFaceFluxThetaKernel) reuse reconstruct_pp<> VERBATIM at the SAME
235 // i-1/i (radial) and j-1/j (azimuthal) offsets over the SAME face boxes (xface_box/yface_box, up
236 // to hi+1) as compute_face_fluxes -> identical reconstruction stencil (i+-Limiter::n_ghost at the
237 // edge of the valid box). The polar metric (r_face = r_min + i*dr, r_cell, 1/r) is computed from
238 // INDICES, never read from U, so it adds NO state-ghost width; aux is read at i+-1 only (1 ghost,
239 // narrower). HOST-only guard, BEFORE the pass-1/pass-2 loops -- never inside a kernel.
240 detail::require_reconstruction_ghosts<Limiter>(U); // state ghosts >= stencil (otherwise OOB)
241 const int pos_comp = detail::positivity_comp<Model>(pos_floor);
242 const Real r_min = geom.r_min, dr = geom.dr(), dtheta = geom.dtheta();
243 // Physical radial boundary faces (wall): r_min at the lo face of the index domain, r_max at the
244 // hi+1 face. geom.domain is the GLOBAL domain (System's PolarGeometry covers the whole annulus,
245 // mono-box).
246 const int i_lo_face = geom.domain.lo[0];
247 const int i_hi_face = geom.domain.hi[0] + 1;
248 const Limiter lim{};
249 const NumericalFlux nflux{};
250 // FACE BoxArrays (cf. Cartesian compute_face_fluxes): radial faces = surroundingNodes in x
251 // (xface_box), azimuthal faces in y (yface_box). Same face index conventions -> fr(i, .) is the
252 // radial face between i-1 and i, ft(., j) the azimuthal face between j-1 and j.
253 std::vector<Box2D> rfaces, tfaces;
254 rfaces.reserve(U.box_array().size());
255 tfaces.reserve(U.box_array().size());
256 for (const Box2D& b : U.box_array().boxes()) {
257 rfaces.push_back(xface_box(b));
258 tfaces.push_back(yface_box(b));
259 }
260 MultiFab Fr(BoxArray(std::move(rfaces)), U.dmap(), Model::n_vars, 0);
261 MultiFab Ft(BoxArray(std::move(tfaces)), U.dmap(), Model::n_vars, 0);
262 for (int li = 0; li < U.local_size(); ++li) {
263 const ConstArray4 u = U.fab(li).const_array();
264 const ConstArray4 ax = aux.fab(li).const_array();
265 Array4 fr = Fr.fab(li).array();
266 Array4 ft = Ft.fab(li).array();
267 const Box2D v = R.box(li);
268 // Radial faces: i in [lo..hi+1], j in [lo..hi] (cf. xface_box).
270 model, u, ax, fr, r_min, dr, lim, nflux, recon_prim,
271 wall_radial, i_lo_face, i_hi_face, pos_floor, pos_comp});
272 // Azimuthal faces: i in [lo..hi], j in [lo..hi+1] (cf. yface_box).
274 model, u, ax, ft, lim, nflux, recon_prim, pos_floor, pos_comp});
275 }
276 for (int li = 0; li < U.local_size(); ++li) {
277 const ConstArray4 u = U.fab(li).const_array();
278 const ConstArray4 ax = aux.fab(li).const_array();
279 const ConstArray4 fr = Fr.fab(li).const_array();
280 const ConstArray4 ft = Ft.fab(li).const_array();
281 Array4 r = R.fab(li).array();
282 const Box2D v = R.box(li);
284 v, detail::PolarAssembleRhsKernel<Model>{model, u, ax, fr, ft, r, r_min, dr, dtheta});
285 }
286}
287
288} // namespace pops
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
const std::vector< Box2D > & boxes() const
View on the underlying vector (element-by-element equality = same boxes AND same order).
Definition box_array.hpp:46
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
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
PolarHasGeomSource<M>: internal concept – true if M exposes polar_geom_source(u, r) -> State.
Definition polar_operator.hpp:85
PolarHasSource<M>: internal concept – true if M exposes source(U, aux) -> State.
Definition polar_operator.hpp:61
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...
Geometry: index-space (Box2D) <-> Cartesian physical-space mapping; PolarGeometry: SIBLING for a glob...
MultiFab: a field DISTRIBUTED over a level (equivalent of AMReX's MultiFab).
POPS_HD Model::State polar_geom_source(const Model &m, const typename Model::State &u, Real r)
polar_geom_source<Model>: returns m.polar_geom_source(u, r) if PolarHasGeomSource<Model>,...
Definition polar_operator.hpp:93
POPS_HD Model::State polar_source(const Model &m, const typename Model::State &u, const Aux &a)
polar_source<Model>: returns m.source(u, a) if PolarHasSource<Model>, otherwise the zero state.
Definition polar_operator.hpp:69
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
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 assemble_rhs_polar(const Model &model, const MultiFab &U, const MultiFab &aux, const PolarGeometry &geom, MultiFab &R, bool recon_prim=false, bool wall_radial=false, Real pos_floor=Real(0))
assemble_rhs_polar<Limiter, NumericalFlux>: R = -div_polar F* + S on a PolarGeometry.
Definition polar_operator.hpp:230
Box2D xface_box(const Box2D &v)
xface_box / yface_box: face boxes normal to x (resp.
Definition face_flux.hpp:147
Box2D yface_box(const Box2D &v)
Definition face_flux.hpp:150
Single-interface numerical flux policies: Rusanov, HLL, HLLC, Roe.
Interface reconstruction policies: MUSCL limiters and WENO5-Z.
Cartesian spatial operator: assembles R(U, aux) = -div F + S over the cells of a level.
Pointwise types of the physics layer: StateVec<N> (conserved state) and Aux (auxiliary fields from th...
WRITE POD handle (raw pointer + strides) over a Fab2D buffer, indexed by (i, j, c) IN GLOBAL INDICES ...
Definition fab2d.hpp:29
POINTWISE auxiliary fields shared with the physics: single coupling channel.
Definition state.hpp:123
2D integer index space, cell-centered.
Definition box2d.hpp:37
int hi[2]
Definition box2d.hpp:39
int lo[2]
Definition box2d.hpp:38
READ-only handle (const counterpart of Array4): same layout and same contract (POD device-copyable,...
Definition fab2d.hpp:44
Definition geometry.hpp:59
POPS_HD Real dr() const
Definition geometry.hpp:70
Real r_min
Definition geometry.hpp:61
Box2D domain
nx() = nr (radial cells), ny() = ntheta (azimuthal cells)
Definition geometry.hpp:60
POPS_HD Real dtheta() const
Definition geometry.hpp:71
PolarAssembleRhsKernel: device kernel of the polar residual in cell (i,j).
Definition polar_operator.hpp:187
ConstArray4 ax
Definition polar_operator.hpp:189
Model model
Definition polar_operator.hpp:188
Array4 r
Definition polar_operator.hpp:190
ConstArray4 u
Definition polar_operator.hpp:189
ConstArray4 fr
Definition polar_operator.hpp:189
Real dtheta
Definition polar_operator.hpp:191
POPS_HD void operator()(int i, int j) const
Definition polar_operator.hpp:192
Real dr
Definition polar_operator.hpp:191
ConstArray4 ft
Definition polar_operator.hpp:189
Real r_min
Definition polar_operator.hpp:191
Definition polar_operator.hpp:111
NumericalFlux nflux
Definition polar_operator.hpp:117
ConstArray4 ax
Definition polar_operator.hpp:113
Real r_min
Definition polar_operator.hpp:115
ConstArray4 u
Definition polar_operator.hpp:113
Array4 fr
Definition polar_operator.hpp:114
bool wall_radial
Definition polar_operator.hpp:125
POPS_HD void operator()(int i, int j) const
Definition polar_operator.hpp:130
Model model
Definition polar_operator.hpp:112
Real pos_floor
Zhang-Shu positivity limiter (<= 0: inactive, bit-identical)
Definition polar_operator.hpp:128
Limiter lim
Definition polar_operator.hpp:116
int i_lo_face
Definition polar_operator.hpp:126
Real dr
Definition polar_operator.hpp:115
int i_hi_face
Definition polar_operator.hpp:127
bool recon_prim
Definition polar_operator.hpp:118
int pos_comp
component of the Density role (resolved by the host caller)
Definition polar_operator.hpp:129
PolarFaceFluxThetaKernel: device kernel of the flux at the azimuthal face j.
Definition polar_operator.hpp:158
int pos_comp
component of the Density role (resolved by the host caller)
Definition polar_operator.hpp:166
Limiter lim
Definition polar_operator.hpp:162
POPS_HD void operator()(int i, int j) const
Definition polar_operator.hpp:167
Real pos_floor
Zhang-Shu positivity limiter (<= 0: inactive, bit-identical)
Definition polar_operator.hpp:165
bool recon_prim
Definition polar_operator.hpp:164
Model model
Definition polar_operator.hpp:159
ConstArray4 ax
Definition polar_operator.hpp:160
Array4 ft
Definition polar_operator.hpp:161
NumericalFlux nflux
Definition polar_operator.hpp:163
ConstArray4 u
Definition polar_operator.hpp:160
Base scalar types and the POPS_HD macro (host+device portability).
#define POPS_HD
Definition types.hpp:25