include/pops/numerics/spatial/embedded_boundary/operator.hpp Source FileΒΆ

adc_cpp: include/pops/numerics/spatial/embedded_boundary/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
operator.hpp
Go to the documentation of this file.
1#pragma once
2
10#include <pops/numerics/elliptic/eb/cut_fraction.hpp> // detail::cut_fraction, CutFraction (PR1)
13#include <pops/numerics/spatial/embedded_boundary/domain.hpp> // detail::DiscDomain (level-set domain; numerics, not runtime)
14#include <pops/numerics/spatial_operator.hpp> // reconstruct<>, load_state/load_aux, *_face_box (REUSED verbatim)
15
16#include <utility>
17#include <vector>
18
96
97namespace pops {
98
99namespace detail {
100
104constexpr Real kEbFaceOpenEps = Real(1e-6);
105
109constexpr Real kEbKappaMin = Real(1e-2);
110
118 POPS_HD Real operator()(Real x, Real y) const { return disc.level_set(x, y); }
119};
120
123 return DiscLevelSet{d};
124}
125
127template <class LevelSet>
128POPS_HD inline bool eb_cell_active(const LevelSet& ls, Real xc, Real yc) {
129 return ls(xc, yc) < Real(0);
130}
131
148 if (ln >= Real(0))
149 return Real(0); // inactive neighbor: closed face (wall, no-penetration)
150 return cut_distance(lc, ln, h) / h; // active neighbor: linear aperture (== elliptic wall)
151}
152
161template <class Limiter, class NumericalFlux, class Model, class LevelSet>
163 Model model;
165 Array4 fx; // output: alpha_x * Fx at the face between i-1 and i (ncomp components)
167 Geometry geom; // cell centers (level set evaluated at the center, like cut_fraction)
168 LevelSet ls;
169 Limiter lim;
170 NumericalFlux nflux;
173 int pos_comp = 0;
174 POPS_HD void operator()(int i, int j) const {
175 const Real xL = geom.x_cell(i - 1), xR = geom.x_cell(i), yc = geom.y_cell(j);
176 const Real lL = ls(xL, yc), lR = ls(xR, yc);
177 const bool aL = lL < Real(0), aR = lR < Real(0);
178 Real alpha;
179 if (aL && aR) {
180 alpha = eb_face_aperture(lL, lR, dx); // internal active/active face: symmetric aperture
181 } else if (aR) {
182 alpha = eb_face_aperture(lR, lL, dx); // (i) active, (i-1) inactive: closed face (0)
183 } else if (aL) {
184 alpha = eb_face_aperture(lL, lR, dx); // (i-1) active, (i) inactive: closed face (0)
185 } else {
186 alpha = Real(0); // both inactive: face outside the active domain
187 }
188 if (alpha < kEbFaceOpenEps) { // closed face (immersed wall): zero normal flux
189 for (int c = 0; c < Model::n_vars; ++c)
190 fx(i, j, c) = Real(0);
191 return;
192 }
193 const auto L =
194 reconstruct_pp<Model>(model, u, i - 1, j, 0, +1, lim, recon_prim, pos_floor, pos_comp);
195 const auto Rr =
196 reconstruct_pp<Model>(model, u, i, j, 0, -1, lim, recon_prim, pos_floor, pos_comp);
197 const auto F = nflux(model, L, load_aux<aux_comps<Model>()>(ax, i - 1, j), Rr,
198 load_aux<aux_comps<Model>()>(ax, i, j), 0);
199 for (int c = 0; c < Model::n_vars; ++c)
200 fx(i, j, c) = alpha * F[c];
201 }
202};
203
206template <class Limiter, class NumericalFlux, class Model, class LevelSet>
208 Model model;
213 LevelSet ls;
214 Limiter lim;
215 NumericalFlux nflux;
218 int pos_comp = 0;
219 POPS_HD void operator()(int i, int j) const {
220 const Real xc = geom.x_cell(i), yL = geom.y_cell(j - 1), yR = geom.y_cell(j);
221 const Real lL = ls(xc, yL), lR = ls(xc, yR);
222 const bool aL = lL < Real(0), aR = lR < Real(0);
223 Real alpha;
224 if (aL && aR) {
225 alpha = eb_face_aperture(lL, lR, dy);
226 } else if (aR) {
227 alpha = eb_face_aperture(lR, lL, dy);
228 } else if (aL) {
229 alpha = eb_face_aperture(lL, lR, dy);
230 } else {
231 alpha = Real(0);
232 }
233 if (alpha < kEbFaceOpenEps) {
234 for (int c = 0; c < Model::n_vars; ++c)
235 fy(i, j, c) = Real(0);
236 return;
237 }
238 const auto L =
239 reconstruct_pp<Model>(model, u, i, j - 1, 1, +1, lim, recon_prim, pos_floor, pos_comp);
240 const auto Rr =
241 reconstruct_pp<Model>(model, u, i, j, 1, -1, lim, recon_prim, pos_floor, pos_comp);
242 const auto F = nflux(model, L, load_aux<aux_comps<Model>()>(ax, i, j - 1), Rr,
243 load_aux<aux_comps<Model>()>(ax, i, j), 1);
244 for (int c = 0; c < Model::n_vars; ++c)
245 fy(i, j, c) = alpha * F[c];
246 }
247};
248
255template <class Model, class LevelSet>
257 Model model;
258 ConstArray4 u, ax, fx, fy; // state, aux, x flux weighted by alpha, y flux weighted by alpha
259 Array4 r; // output: residual
262 LevelSet ls;
264 POPS_HD void operator()(int i, int j) const {
265 const Real xc = geom.x_cell(i), yc = geom.y_cell(j);
266 if (!eb_cell_active(ls, xc, yc)) { // outside the disc: zero residual, not advanced (cf. T2)
267 for (int c = 0; c < Model::n_vars; ++c)
268 r(i, j, c) = Real(0);
269 return;
270 }
271 // Volume fraction kappa derived EXACTLY from the same cut_fraction as the elliptic wall: the
272 // cut geometry is the single source of truth (face apertures AND volume). kappa in (0, 1].
273 const CutFraction cf = cut_fraction(ls, xc, yc, dx, dy);
274 // SMALL-CELL CLAMP: bounds 1/kappa to 1/kappa_min -> finite residual, stable fixed step. Acts ONLY on
275 // the denominator (volume); the fluxes (numerator) are unchanged -> GLOBAL conservation preserved.
276 const Real kappa_eff = cf.kappa > kappa_min ? cf.kappa : kappa_min;
277 const Real inv_kappa = Real(1) / kappa_eff;
278
279 const Aux Ac = load_aux<aux_comps<Model>()>(ax, i, j);
280 const auto S = model.source(load_state<Model>(u, i, j), Ac);
281
282 // Immersed WALL flux (no-penetration): F_wall = 0 -> zero term. Single attachment point for a
283 // future nonzero wall flux. Stays at 0 in PR2 (solid wall, like the elliptic Dirichlet wall).
284 constexpr Real wall_flux = Real(0);
285
286 for (int c = 0; c < Model::n_vars; ++c) {
287 const Real div_x = (fx(i + 1, j, c) - fx(i, j, c)) / dx; // discrete d_x(alpha Fx)
288 const Real div_y = (fy(i, j + 1, c) - fy(i, j, c)) / dy; // discrete d_y(alpha Fy)
289 // TERM-BY-TERM accumulation (and not inv_kappa*(div_x + div_y)): when kappa_eff = 1 and all
290 // alpha = 1 (case WITHOUT cut), inv_kappa = 1 and each inv_kappa*div_* = div_* by IEEE identity
291 // (x*1.0 == x), so r = S - div_x - div_y - 0 reproduces BIT FOR BIT the Cartesian operator
292 // (S - (Fxp-Fxm)/dx - (Fyp-Fym)/dy), term by term. A grouping (div_x + div_y) would break
293 // floating-point associativity and the bit-identity of the default path.
294 r(i, j, c) = S[c] - inv_kappa * div_x - inv_kappa * div_y - inv_kappa * wall_flux;
295 }
296 }
297};
298
299} // namespace detail
300
323template <class Limiter = NoSlope, class NumericalFlux = RusanovFlux, class Model, class LevelSet>
324void assemble_rhs_eb(const Model& model, const MultiFab& U, const MultiFab& aux, const LevelSet& ls,
325 const Geometry& geom, MultiFab& R, bool recon_prim = false,
326 Real kappa_min = detail::kEbKappaMin, Real pos_floor = Real(0)) {
327 // STATE-GHOST WIDTH: exactly Limiter::n_ghost, like the Cartesian operator. The EB face kernels
328 // (EbFaceFluxXKernel / EbFaceFluxYKernel) reuse reconstruct_pp<> VERBATIM at the SAME i-1/i
329 // offsets over the SAME face boxes (xface_box/yface_box, up to hi+1) as compute_face_fluxes ->
330 // identical reconstruction stencil (i+-Limiter::n_ghost at the edge of the valid box). The EB
331 // geometry (level set ls, cut_fraction/cut_distance) is evaluated at cell-center COORDINATES
332 // (geom.x_cell/y_cell), never on U, so it adds NO state-ghost width; aux is read at i+-1 only
333 // (1 ghost, narrower). HOST-only guard, BEFORE the pass-1/pass-2 loops -- never inside a kernel.
334 detail::require_reconstruction_ghosts<Limiter>(U); // state ghosts >= stencil (otherwise OOB)
335 const Real dx = geom.dx(), dy = geom.dy();
336 const Limiter lim{};
337 const NumericalFlux nflux{};
338 const int pos_comp = detail::positivity_comp<Model>(pos_floor);
339 // FACE BoxArrays (cf. compute_face_fluxes / polar operator): x faces = surroundingNodes in x
340 // (xface_box), y faces in y (yface_box). fx(i, .) is the face between i-1 and i, fy(., j) between j-1 and j.
341 std::vector<Box2D> xfaces, yfaces;
342 xfaces.reserve(U.box_array().size());
343 yfaces.reserve(U.box_array().size());
344 for (const Box2D& b : U.box_array().boxes()) {
345 xfaces.push_back(xface_box(b));
346 yfaces.push_back(yface_box(b));
347 }
348 MultiFab Fx(BoxArray(std::move(xfaces)), U.dmap(), Model::n_vars, 0);
349 MultiFab Fy(BoxArray(std::move(yfaces)), U.dmap(), Model::n_vars, 0);
350 // PASS 1: face fluxes weighted by alpha_f.
351 for (int li = 0; li < U.local_size(); ++li) {
352 const ConstArray4 u = U.fab(li).const_array();
353 const ConstArray4 ax = aux.fab(li).const_array();
354 Array4 fx = Fx.fab(li).array();
355 Array4 fy = Fy.fab(li).array();
356 const Box2D v = R.box(li);
359 model, u, ax, fx, dx, geom, ls, lim, nflux, recon_prim, pos_floor, pos_comp});
362 model, u, ax, fy, dy, geom, ls, lim, nflux, recon_prim, pos_floor, pos_comp});
363 }
364 // PASS 2: EB divergence / kappa_eff + source; inactive cell -> residual 0.
365 for (int li = 0; li < U.local_size(); ++li) {
366 const ConstArray4 u = U.fab(li).const_array();
367 const ConstArray4 ax = aux.fab(li).const_array();
368 const ConstArray4 fx = Fx.fab(li).const_array();
369 const ConstArray4 fy = Fy.fab(li).const_array();
370 Array4 r = R.fab(li).array();
371 const Box2D v = R.box(li);
372 for_each_cell(v, detail::EbAssembleRhsKernel<Model, LevelSet>{model, u, ax, fx, fy, r, dx, dy,
373 geom, ls, kappa_min});
374 }
375}
376
377} // 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
SHARED cut-fraction primitive (cut-cell / embedded boundary).
Generic EMBEDDED-BOUNDARY / LEVEL-SET DOMAIN contract (ADC-327).
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 DiscLevelSet disc_level_set(const DiscDomain &d)
Builds the disc level set callable from a DiscDomain (sugar: disc_level_set(d)).
Definition operator.hpp:122
constexpr Real kEbKappaMin
Definition operator.hpp:109
POPS_HD CutFraction cut_fraction(const LevelSet &ls, Real xc, Real yc, Real dx, Real dy)
Computes the cut geometry of an ACTIVE cell (center (xc, yc) with ls < 0) from a level-set ls evaluat...
Definition cut_fraction.hpp:76
POPS_HD bool eb_cell_active(const LevelSet &ls, Real xc, Real yc)
Activity indicator (center in the disc, ls < 0) from a callable level set. POPS_HD.
Definition operator.hpp:128
POPS_HD Real eb_face_aperture(Real lc, Real ln, Real h)
Definition operator.hpp:147
POPS_HD Real cut_distance(Real lc, Real ln, Real h)
Cut distance of ONE face along a direction, starting from the active center (ls < 0).
Definition cut_fraction.hpp:33
constexpr Real kEbFaceOpenEps
Default aperture below which a face is treated as CLOSED (immersed wall).
Definition operator.hpp:104
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_eb(const Model &model, const MultiFab &U, const MultiFab &aux, const LevelSet &ls, const Geometry &geom, MultiFab &R, bool recon_prim=false, Real kappa_min=detail::kEbKappaMin, Real pos_floor=Real(0))
assemble_rhs_eb<Limiter, NumericalFlux>: residual R = -div_eb F + S on a DISC in cut-cell / EB,...
Definition operator.hpp:324
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
READ-only handle (const counterpart of Array4): same layout and same contract (POD device-copyable,...
Definition fab2d.hpp:44
Cartesian geometry of a level: index domain + physical bounds [xlo, xhi] x [ylo, yhi].
Definition geometry.hpp:20
POPS_HD Real x_cell(int i) const
Abscissa at the CENTER of cell index i (i = 0 -> xlo + dx/2; defined for negative i)....
Definition geometry.hpp:35
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
POPS_HD Real y_cell(int j) const
Ordinate at the CENTER of cell index j. POPS_HD.
Definition geometry.hpp:37
Geometric result of crossing a cut cell: 4 cut distances per face, the 4 apertures alpha_f normalized...
Definition cut_fraction.hpp:52
Real kappa
volume fraction of the cell (share in the active domain), in (0, 1]
Definition cut_fraction.hpp:61
CIRCLE / DISC level-set domain: the canonical instance of the contract and the SINGLE SOURCE of truth...
Definition domain.hpp:69
POPS_HD Real level_set(Real x, Real y) const
Level set ls(x, y) = hypot(x - cx, y - cy) - R: < 0 inside, 0 at the boundary, > 0 outside.
Definition domain.hpp:82
Device-safe adapter wrapping a DiscDomain as the Real(Real, Real) callable expected by cut_fraction a...
Definition operator.hpp:116
POPS_HD Real operator()(Real x, Real y) const
Definition operator.hpp:118
DiscDomain disc
Definition operator.hpp:117
Kernel assembling the EB residual at cell (i, j): INACTIVE cell -> residual 0 (not advanced,...
Definition operator.hpp:256
Geometry geom
Definition operator.hpp:261
POPS_HD void operator()(int i, int j) const
Definition operator.hpp:264
Model model
Definition operator.hpp:257
LevelSet ls
Definition operator.hpp:262
ConstArray4 u
Definition operator.hpp:258
Real dx
Definition operator.hpp:260
Array4 r
Definition operator.hpp:259
Real kappa_min
Definition operator.hpp:263
Real dy
Definition operator.hpp:260
ConstArray4 fy
Definition operator.hpp:258
ConstArray4 ax
Definition operator.hpp:258
ConstArray4 fx
Definition operator.hpp:258
FACE FLUX kernel for x (dir 0) of the EB transport: numerical flux at the face between (i-1,...
Definition operator.hpp:162
Model model
Definition operator.hpp:163
ConstArray4 u
Definition operator.hpp:164
int pos_comp
component of the Density role (resolved by the host caller)
Definition operator.hpp:173
bool recon_prim
Definition operator.hpp:171
ConstArray4 ax
Definition operator.hpp:164
Real dx
Definition operator.hpp:166
NumericalFlux nflux
Definition operator.hpp:170
Real pos_floor
Zhang-Shu positivity limiter (<= 0: inactive, bit-identical)
Definition operator.hpp:172
Geometry geom
Definition operator.hpp:167
POPS_HD void operator()(int i, int j) const
Definition operator.hpp:174
Limiter lim
Definition operator.hpp:169
Array4 fx
Definition operator.hpp:165
LevelSet ls
Definition operator.hpp:168
FACE FLUX kernel for y (dir 1) of the EB transport: analogue of EbFaceFluxXKernel in j.
Definition operator.hpp:207
int pos_comp
component of the Density role (resolved by the host caller)
Definition operator.hpp:218
NumericalFlux nflux
Definition operator.hpp:215
POPS_HD void operator()(int i, int j) const
Definition operator.hpp:219
Limiter lim
Definition operator.hpp:214
Real pos_floor
Zhang-Shu positivity limiter (<= 0: inactive, bit-identical)
Definition operator.hpp:217
Geometry geom
Definition operator.hpp:212
ConstArray4 ax
Definition operator.hpp:209
LevelSet ls
Definition operator.hpp:213
Real dy
Definition operator.hpp:211
Model model
Definition operator.hpp:208
ConstArray4 u
Definition operator.hpp:209
bool recon_prim
Definition operator.hpp:216
Array4 fy
Definition operator.hpp:210
Base scalar types and the POPS_HD macro (host+device portability).
#define POPS_HD
Definition types.hpp:25