12#ifndef PECLET_FLOW_FLOW_IBM_SCALARS_CUTCELL_HPP
13#define PECLET_FLOW_FLOW_IBM_SCALARS_CUTCELL_HPP
20 scalarOpenValid_ =
false;
26 throw std::runtime_error(
27 "scalar cut-cell geometry: no geometry yet -- call set_solid, set_solid_from_scene or "
28 "set_pressure_geometry first");
29 if (scg_.
version == scgVersion_)
31 const std::size_t nInner = (std::size_t)nx_ * ny_ * nz_;
33 r.kappa =
CCField(
"peclet::flow::scg_kappa", n_);
34 r.sax =
CCField(
"peclet::flow::scg_sax", n_);
35 r.say =
CCField(
"peclet::flow::scg_say", n_);
36 r.saz =
CCField(
"peclet::flow::scg_saz", n_);
37 r.unknown =
CCField(
"peclet::flow::scg_unknown", n_);
42 scg::CellPass cp{Kokkos::View<std::int8_t*, CCMem>(
"peclet::flow::scg_nfac", nInner),
43 Kokkos::View<std::int8_t*, CCMem>(
"peclet::flow::scg_kind", nInner)};
51 for (
int face = 0; face < 6; ++face)
52 if (bc_[face] != 0 && touchesGlobalFace(face))
56 if (hasScene_ && sceneQ_ && r.fac.n > 0) {
57 const auto q = sceneQ_->view();
59 const C3 og = og_, e = e_;
61 const double h0 = u_.
hp[0],
h1 = u_.
hp[1], h2 = u_.
hp[2];
62 auto fcell = r.fac.cell;
63 auto fcen = r.fac.centroid;
64 auto fbody = r.fac.body;
67 "peclet::flow::scg_body", Kokkos::RangePolicy<CCExec>(
space, 0, r.fac.n),
69 const long i = fcell(f);
70 const long x = i % e.x, y = (i / e.x) % e.y, z = i / ((
long)e.x * e.y);
71 const peclet::core::Vec3<double> p{
73 sm.a[1] +
sm.b[1] * ((
double)(y - g + og.y) +
fcen(f, 1) /
h1),
74 sm.a[2] +
sm.b[2] * ((
double)(z - g + og.z) +
fcen(f, 2) / h2)};
75 fbody(f) = q.owner(p);
87 long cnt[10] = {c.numUnknowns, c.numCutCells, c.numFacets, c.numTwoSided, c.numThinSolid,
88 c.numSealed, c.rungs[0], c.rungs[1], c.rungs[2], c.rungs[3]};
91 double v = c.sealedVolume, vs = 0.0;
93 double vf = c.fluidVolume,
vfs = 0.0;
96 c.numUnknowns = sum[0];
97 c.numCutCells = sum[1];
99 c.numTwoSided = sum[3];
100 c.numThinSolid = sum[4];
101 c.numSealed = sum[5];
102 for (
int k = 0; k < 4; ++k)
103 c.rungs[k] = sum[6 + k];
108 c.sealedVolume *= l * l * l;
109 c.fluidVolume *= l * l * l;
111 r.version = scgVersion_;
126 throw std::runtime_error(
"scalarGeometryField: which must be 0..4");
142 for (
const auto& sc : scalars_)
150 for (
auto& sc : scalars_)
151 if (sc.name == name) {
153 throw std::invalid_argument(std::string(
who) +
": '" + name +
154 "' is not a cut-cell scalar (add_scalar(..., cutcell=True))");
157 throw std::invalid_argument(std::string(
who) +
": no scalar named '" + name +
"'");
162 return (hasScene_ && nInst_ > 0) ? nInst_ : 1;
167 double coefficient,
int instance) {
170 throw std::invalid_argument(
"set_scalar_wall: type must be 'neumann', 'dirichlet' or 'robin'");
171 if (!std::isfinite(value))
172 throw std::invalid_argument(
"set_scalar_wall: value must be finite");
173 if (type == 2 && !(coefficient >= 0.0))
174 throw std::invalid_argument(
"set_scalar_wall: the robin coefficient k must be >= 0");
175 const ScalarWallSpec w{type, value, type == 2 ? coefficient : 0.0};
178 st.wallInstance.clear();
182 throw std::invalid_argument(
183 "set_scalar_wall: instance= needs a scene (set_scene + set_solid_from_scene); raw-SDF "
184 "geometry has one body, set with instance=None");
185 if (instance >= nInst_)
186 throw std::invalid_argument(
"set_scalar_wall: instance " + std::to_string(instance) +
187 " out of range (the scene has " + std::to_string(nInst_) +
")");
188 for (
auto it = st.wallInstance.begin(); it != st.wallInstance.end(); ++it)
189 if (it->first == instance) {
190 st.wallInstance.erase(it);
193 st.wallInstance.emplace_back(instance, w);
201 if (!std::isfinite(D) || !(D >= 0.0))
202 throw std::invalid_argument(
"set_scalar_solid: diffusivity must be finite and >= 0");
204 throw std::invalid_argument(
"set_scalar_solid: capacity must be finite and > 0");
206 throw std::invalid_argument(
"set_scalar_solid: partition must be finite and > 0");
208 throw std::invalid_argument(
"set_scalar_solid: contact_resistance must be finite and >= 0");
209 if (instance >= 0 && !hasScene_)
210 throw std::invalid_argument(
211 "set_scalar_solid: instance= needs a scene (set_scene + set_solid_from_scene); raw-SDF "
212 "geometry has one body, set with instance=None");
213 if (instance >= 0 && instance >= nInst_)
214 throw std::invalid_argument(
"set_scalar_solid: instance " + std::to_string(instance) +
215 " out of range (the scene has " + std::to_string(nInst_) +
")");
222 if (st.solid.extent(0) != n_)
223 st.solid =
addField(name +
"_solid");
226 st.wallInstance.clear();
229 for (
auto it = st.wallInstance.begin(); it != st.wallInstance.end(); ++it)
230 if (it->first == instance) {
231 st.wallInstance.erase(it);
234 st.wallInstance.emplace_back(instance, w);
241 std::vector<ScalarWallSpec> spec((std::size_t)nb, st.wallDefault);
242 for (
const auto& [inst, w] : st.wallInstance)
243 if (inst >= 0 && inst < nb)
244 spec[(std::size_t)inst] = w;
245 conj.assign((std::size_t)nb, 0);
246 st.mtLam.assign((std::size_t)nb, 0.0);
247 st.mtCK.assign((std::size_t)nb, 1.0);
248 st.mtK.assign((std::size_t)nb, 1.0);
249 st.mtRc.assign((std::size_t)nb, 0.0);
252 for (
int b = 0; b < nb; ++b) {
256 conj[(std::size_t)b] = 1;
258 st.mtLam[(std::size_t)b] = (w.solidC * w.solidD * w.solidK) *
dI;
259 st.mtCK[(std::size_t)b] = w.solidC * w.solidK;
260 st.mtK[(std::size_t)b] = w.solidK;
261 st.mtRc[(std::size_t)b] = w.solidRc *
rI;
262 st.maxSolidD = std::fmax(st.maxSolidD, w.solidD *
dI);
264 if (st.mtConjV.extent(0) != (std::size_t)nb) {
265 st.mtConjV = Kokkos::View<int*, CCMem>(
"peclet::flow::sco_mt_conj", nb);
266 st.mtLamV = Kokkos::View<double*, CCMem>(
"peclet::flow::sco_mt_lam", nb);
267 st.mtCKV = Kokkos::View<double*, CCMem>(
"peclet::flow::sco_mt_ck", nb);
268 st.mtKV = Kokkos::View<double*, CCMem>(
"peclet::flow::sco_mt_k", nb);
269 st.mtRcV = Kokkos::View<double*, CCMem>(
"peclet::flow::sco_mt_rc", nb);
273 mt.conj = st.mtConjV;
278 auto hc = Kokkos::create_mirror_view(
mt.conj);
279 auto hl = Kokkos::create_mirror_view(
mt.lam);
280 auto hk = Kokkos::create_mirror_view(
mt.ck);
281 auto hp = Kokkos::create_mirror_view(
mt.kp);
282 auto hr = Kokkos::create_mirror_view(
mt.rc);
283 for (
int b = 0; b < nb; ++b) {
284 hc(b) =
conj[(std::size_t)b];
285 hl(b) = st.mtLam[(std::size_t)b];
286 hk(b) = st.mtCK[(std::size_t)b];
287 hp(b) = st.mtK[(std::size_t)b];
288 hr(b) = st.mtRc[(std::size_t)b];
290 Kokkos::deep_copy(
mt.conj,
hc);
291 Kokkos::deep_copy(
mt.lam,
hl);
292 Kokkos::deep_copy(
mt.ck,
hk);
293 Kokkos::deep_copy(
mt.kp, hp);
294 Kokkos::deep_copy(
mt.rc,
hr);
302 if (st.solidVersion == scg_.
version && st.solidKey ==
conj && st.mat.extent(0) == n_)
305 const auto&
fo = gm.
fac;
306 if (st.mat.extent(0) != n_) {
307 st.mat =
CCField(sc.name +
"_cc_mat", n_);
308 st.sunk =
CCField(sc.name +
"_cc_sunk", n_);
312 const int nb = (int)
conj.size();
314 mt.conj = Kokkos::View<int*, CCMem>(
"peclet::flow::sco_rec_conj", nb);
316 auto hc = Kokkos::create_mirror_view(
mt.conj);
317 for (
int b = 0; b < nb; ++b)
318 hc(b) =
conj[(std::size_t)b];
319 Kokkos::deep_copy(
mt.conj,
hc);
321 const std::size_t nInner = (std::size_t)nx_ * ny_ * nz_;
322 const bool hasOwner = hasScene_ && cutOwner_.extent(0) == nInner;
323 double svol[2] = {0.0, 0.0};
329 for (
int face = 0; face < 6; ++face)
330 if (bc_[face] != 0 && touchesGlobalFace(face))
333 st.conjF = Kokkos::View<std::uint8_t*, CCMem>(sc.name +
"_cc_conjF",
fo.n);
334 st.sS = Kokkos::View<double*, CCMem>(sc.name +
"_cc_sS",
fo.n);
335 st.offS =
decltype(st.offS)(sc.name +
"_cc_offS",
fo.n);
336 st.wS =
decltype(st.wS)(sc.name +
"_cc_wS",
fo.n);
337 st.rungS = Kokkos::View<std::uint8_t*, CCMem>(sc.name +
"_cc_rungS",
fo.n);
343 Kokkos::parallel_for(
344 "peclet::flow::sco_conj_facets", Kokkos::RangePolicy<CCExec>(
space, 0,
fo.n),
351 Kokkos::View<const std::uint8_t*, CCMem>(st.conjF),
fo,
CCConst(sdf_),
354 long cnt[5] = {0, 0, 0, 0, 0};
361 "peclet::flow::sco_count_solid",
C3{g, g, g},
C3{e.x - g, e.y - g, e.z - g},
363 acc +=
su((
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y) > 0.5 ? 1.0 : 0.0;
369 for (
int k = 0; k < 4; ++k) {
371 Kokkos::parallel_reduce(
372 "peclet::flow::sco_solid_rungs", Kokkos::RangePolicy<CCExec>(0,
fo.n),
374 acc += (cf(f) != 0 && (int)
rs(f) == k) ? 1 : 0;
380#ifdef PECLET_FLOW_MPI
384 for (
int k = 0; k < 5; ++k)
393 st.solidVolume =
svol[0] * u_.
vol * lv * lv * lv;
394 st.sealedSolidVolume =
svol[1] * u_.
vol * lv * lv * lv;
395 st.numSolidUnknowns = cnt[0];
396 for (
int k = 0; k < 4; ++k)
397 st.solidRungs[k] = cnt[1 + k];
406 const std::size_t nInner = (std::size_t)nx_ * ny_ * nz_;
407 std::vector<double> out(nInner, std::numeric_limits<double>::quiet_NaN());
408 if (!st.built || !st.conj || st.solid.extent(0) != n_ || st.sunk.extent(0) != n_)
413 const int nb = (int)st.mtK.size();
414 for (std::size_t i = 0; i < nInner; ++i) {
420 out[i] = st.mtK[(std::size_t)b] *
ps[i];
429 std::vector<char>
conj;
443 if (!std::isfinite(S))
444 throw std::invalid_argument(
"set_scalar_source: the source must be finite");
447 st.sourceIsField =
false;
453 if (S.size() != (std::size_t)nx_ * ny_ * nz_)
454 throw std::invalid_argument(
455 "set_scalar_source: the array must have this rank's (nx, ny, nz) "
457 if (st.sourceField.extent(0) != n_)
458 st.sourceField =
CCField(name +
"_cc_source", n_);
460 st.sourceConst = 0.0;
461 st.sourceIsField =
true;
467 if (!(rtol > 0.0) || !std::isfinite(rtol))
468 throw std::invalid_argument(
"set_scalar_tolerance: rtol must be finite and > 0");
476 if (!std::isfinite(v))
477 throw std::invalid_argument(
"set_scalar_mean_gradient: the gradient must be finite");
478 for (
int a = 0; a < 3; ++a)
479 st.meanGrad[a] =
G[(std::size_t)a];
485 const int og[3] = {og_.
x, og_.
y, og_.
z};
486 for (
int a = 0; a < 3; ++a) {
488 p.org[a] = u_.
org[a];
490 p.base[a] = (
long)og[a] -
G;
501 std::array<double, 3> out{0.0, 0.0, 0.0};
507 const bool adv = st.advecting;
511 CCConst(adv ? st.phi[1] : sc.c),
CCConst(adv ? st.phi[2] : sc.c), adv, st.lam,
512 st.gInt, u_.
hp, st.ubar, e_,
G, d, v);
518 for (
int a = 0; a < 3; ++a)
521 double s[6] = {d[0], d[1], d[2], v[0], v[1], v[2]};
522#ifdef PECLET_FLOW_MPI
526 for (
int j = 0; j < 6; ++j)
534 for (
int a = 0; a < 3; ++a)
535 out[(std::size_t)a] = (s[a] + s[3 + a]) /
nBox *
toPhys;
543 throw std::invalid_argument(
"set_scalar_max_iterations: maxit must be >= 1");
549 const std::vector<double>&
prof,
int n1,
int n2) {
552 throw std::invalid_argument(
"set_scalar_bc: face out of range");
555 const int n[3] = {nx_, ny_, nz_};
557 throw std::invalid_argument(
"set_scalar_bc: a profile must have shape (N_t1, N_t2) = (" +
558 std::to_string(n[
t1]) +
", " + std::to_string(n[
t2]) +
559 ") -- this rank's face cells in the tangential axes in x, y, z "
561 for (
double v :
prof)
562 if (!std::isfinite(v))
563 throw std::invalid_argument(
"set_scalar_bc: the profile must be finite");
565 sc.bcVal[face] = 0.0;
566 sc.cut->hasProfile[face] =
true;
567 sc.cut->profile[face] =
prof;
568 sc.cut->profileN[face][0] =
n1;
569 sc.cut->profileN[face][1] =
n2;
574 const std::string
who =
"cut-cell scalar '" + sc.name +
"': ";
576 throw std::runtime_error(
who +
"porous (volume-averaged) continuity is not supported");
578 throw std::runtime_error(
who +
"moving scene instances are not supported (static geometry)");
579 if (sc.dmask.extent(0) == n_)
580 throw std::runtime_error(
who +
"the per-cell Dirichlet mask is not supported");
586 if (ghostProjection_)
587 throw std::runtime_error(
588 who +
"the ghost projection ('ghost' collocated scheme, or diagnostics." +
589 "set_ghost_projection) leaves no discretely divergence-free face flux to advect with " +
590 "(doc/scalar_ibm_design.md §6.1, gate G9b); the accepted projections are the collocated " +
591 "schemes 'gauge-exact', 'plain' and 'embed' (set_collocated_scheme, before set_solid) "
593 "the staggered Solver without set_ghost_projection");
594 static const char*
kFace[6] = {
"-x",
"+x",
"-y",
"+y",
"-z",
"+z"};
596 for (
int f = 0; f < 6; ++f)
597 if (sc.cut->meanGrad[f / 2] != 0.0 && (bc_[f] != 0 || sc.bc[f] != 0))
598 throw std::runtime_error(
who +
"the mean gradient has a component along " +
599 std::string(1,
"xyz"[f / 2]) +
" but the domain face " +
kFace[f] +
600 " is not periodic (set_scalar_mean_gradient needs periodic faces "
601 "on every axis it has a component on)");
602 for (
int f = 0; f < 6; ++f) {
605 throw std::runtime_error(
607 " for the scalar but " + (
flowPeriodic ?
"periodic" :
"non-periodic") +
608 " for the flow (set_scalar_bc / set_domain_bc must agree)");
609 if (bc_[f] == 2 && sc.bc[f] != 2)
610 throw std::runtime_error(
who +
"the inflow face " +
kFace[f] +
611 " needs a scalar 'dirichlet' value (set_scalar_bc)");
617 if constexpr (Grid::collocated)
618 if (bc_[f] == 2 || bc_[f] == 3)
619 throw std::runtime_error(
620 who +
"face " +
kFace[f] +
" is a flow " + (bc_[f] == 2 ?
"inflow" :
"outflow") +
621 " face: on the collocated grid the projected face field does not keep the boundary "
622 "flux the projection constrained, so open domain faces are not supported for a "
623 "cut-cell scalar there yet -- use the staggered Solver (doc/scalar_ibm_design.md "
632 applyCutcellOp(y,
CCConst(x), sc.AC, sc.AW, sc.AE, sc.AS, sc.AN, sc.AB, sc.AT, e_,
G);
642 applyCutcellOp(
yf,
CCConst(xf), sc.AC, sc.AW, sc.AE, sc.AS, sc.AN, sc.AB, sc.AT, e_,
G);
643 applyCutcellOp(
ys,
CCConst(xs), st.ASC, st.ASW, st.ASE, st.ASS, st.ASN, st.ASB, st.AST, e_,
G);
653 const bool gp = ghostProjection_ && oxb_.extent(0) > 0;
654 const CCField o[3] = {
gp ? oxb_ : ox_,
gp ? oyb_ : oy_,
gp ? ozb_ : oz_};
657 if constexpr (Grid::collocated)
658 f.vel = a == 0 ? uf_ : (a == 1 ? vf_ : wf_);
676 for (
int f = 0; f < 6; ++f)
680 const int ext[3] = {e_.
x, e_.
y, e_.
z};
681 const long st[3] = {1, (
long)e_.
x, (
long)e_.
x * e_.
y};
682 for (
int f = 0; f < 6; ++f) {
683 if (!(bc_[f] == 2 || bc_[f] == 3) || !touchesGlobalFace(f))
685 const int a = f / 2, side = f % 2;
688 const int m1 = ext[
t1], m2 = ext[
t2];
689 if (scalarOpenFlux_[f].extent(0) != (std::size_t)m1 * m2)
690 scalarOpenFlux_[f] =
CCField(
"peclet::flow::scalar_open_flux", (std::size_t)m1 * m2);
693 CCField pl = scalarOpenFlux_[f];
694 const long sa = st[a], s1 = st[
t1], s2 = st[
t2];
695 const long bfa = side == 0 ?
G : ext[a] -
G;
697 Kokkos::parallel_for(
701 pl((
long)j1 + (
long)
j2 * m1) = open(
bf) * vel(
bf);
705 scalarOpenValid_ =
true;
713 for (
int a = 0; a < 3; ++a)
714 if (st.phi[a].extent(0) != n_)
715 st.phi[a] =
CCField(sc.name +
"_cc_phi", n_);
722 for (
int f = 0; f < 6; ++f) {
723 const bool o = bc_[f] == 2 || bc_[f] == 3;
725 openHere[f] = o && scalarOpenValid_ && touchesGlobalFace(f);
726 st.openFace[f] =
false;
727 st.openInflow[f] = bc_[f] == 2;
731 for (
int a = 0; a < 3; ++a) {
737 for (
int side = 0; side < 2; ++side)
749 if constexpr (Grid::collocated)
750 for (
int a = 0; a < 3; ++a)
755 st.lam, u_.
w, e_,
G),
757#ifdef PECLET_FLOW_MPI
759 double o[4] = {0.0, 0.0, 0.0, 0.0};
761 for (
int k = 0; k < 4; ++k)
765 st.maxCellPeclet = mx[2];
770 if constexpr (Grid::collocated)
771 if (!faceFieldValid_ && mx[3] > 0.0)
772 throw std::runtime_error(
773 "cut-cell scalar '" + sc.name +
774 "': SolverColocated advects with the projected MAC FACE field uf_/vf_/wf_ (the only "
775 "discretely divergence-free field on this grid), and it has never been built since the "
776 "geometry was set -- it is all zeros, so the moving fluid would be silently ignored. "
777 "Call set_state()/set_velocity() (which seed the face field through the same "
778 "centerToFace map project() uses) or step() first (doc/scalar_ibm_design.md §6.1)");
783 if (!cutcellPressure_ && mx[1] > 0.0)
784 throw std::runtime_error(
"cut-cell scalar '" + sc.name +
785 "': the fluid moves but no cut-cell projection exists to advect "
786 "with -- use set_solid(..., cutcell_pressure=True) or "
787 "set_pressure_geometry (doc/scalar_ibm_design.md §6.1)");
790 if (
anyOpen && !scalarOpenValid_ && mx[1] > 0.0)
791 throw std::runtime_error(
"cut-cell scalar '" + sc.name +
792 "': the fluid moves and the domain has an inflow/outflow face, but no "
793 "projection has supplied that face's flux yet -- advance with step() "
794 "(doc/scalar_ibm_design.md §6.5)");
795 st.advecting = mx[0] > 0.0;
797 st.numImplicitFaces = 0;
798 st.bulkCourant = 0.0;
800 st.numGuardedFaces = 0;
801 st.numBackflowFaces = 0;
804#ifdef PECLET_FLOW_MPI
811 st.numGuardedFaces = cnt[0];
814 if (st.small.extent(0) != n_)
815 st.small =
CCField(sc.name +
"_cc_small", n_);
816 if (st.outflow.extent(0) != n_)
817 st.outflow =
CCField(sc.name +
"_cc_outflow", n_);
818 const CCConst px(st.phi[0]), py(st.phi[1]), pz(st.phi[2]);
820 Kokkos::deep_copy(
CCExec(), st.small, 0.0);
825#ifdef PECLET_FLOW_MPI
835 if (sc.scheme == 1 && cb > 0.5 && !st.warnedKoren) {
836 st.warnedKoren =
true;
839 "peclet.flow: cut-cell scalar '%s': scheme 'koren' at bulk Courant %.3f > 1/2 "
840 "-- the explicit limiter is bounded only to 1/2 and overshoots above it; "
841 "reduce dt or use scheme='fou' (diagnostics.scalar_census 'bulk_courant', "
843 sc.name.c_str(), cb);
845 sco::smallCells(st.small, px, py, pz, kap, unk, dt_, std::fmax(0.5, cb), e_,
G);
855 for (
int a = 0; a < 3; ++a)
857 for (
int f = 0; f < 6; ++f) {
861 long n1 = 0,
n2 = 0,
n3 = 0;
863 f / 2, f % 2, steady, e_,
G,
n1,
n2,
n3);
868#ifdef PECLET_FLOW_MPI
872 for (
int k = 0; k < 5; ++k)
876 st.numSmall = cnt[0];
877 st.numImplicitFaces = cnt[1];
878 st.numFluxFaces = cnt[2];
879 st.numGuardedFaces = cnt[3];
880 st.numBackflowFaces = cnt[4];
884 if (steady && st.numBackflowFaces > 0 && !st.warnedBackflow) {
885 st.warnedBackflow =
true;
888 "peclet.flow: cut-cell scalar '%s': %ld outflow-face row(s) carry BACKFLOW in "
889 "the steady solve (flow entering through an 'outflow' face). Their inflow "
890 "takes the zero-gradient value c_i, which is not a well-posed inflow condition "
891 "(doc/scalar_ibm_design.md §6.5): the matrix loses column dominance there, and "
892 "the advective preconditioner's coarse mass omega_open is clamped at 0 on those "
893 "rows (A2). Prefer an 'inflow' face where fluid enters "
894 "(diagnostics.scalar_census 'num_backflow_faces', printed once)\n",
895 sc.name.c_str(), st.numBackflowFaces);
913 if (!steady && !(dt_ > 0.0))
914 throw std::runtime_error(
"cut-cell scalar '" + sc.name +
"': set_dt first");
919 if (f.extent(0) != n_)
922 alloc(st.SAC,
"_cc_sac");
923 alloc(st.kr,
"_cc_r");
924 alloc(st.krh,
"_cc_rh");
925 alloc(st.kp,
"_cc_p");
926 alloc(st.kv,
"_cc_v");
927 alloc(st.kt,
"_cc_t");
928 alloc(st.kz,
"_cc_z");
929 alloc(st.kz2,
"_cc_z2");
930 if (st.cw.extent(0) != (std::size_t)gm.
fac.
n) {
931 st.cw = Kokkos::View<double*, CCMem>(sc.name +
"_cc_cw", gm.
fac.
n);
932 st.rw = Kokkos::View<double*, CCMem>(sc.name +
"_cc_rw", gm.
fac.
n);
935 const double lam = sc.Dphys * u_.
diffToInt();
936 const double idt = steady ? 0.0 : 1.0 / dt_;
937 const double srcFactor = u_.
divToInt();
940 st.dt = steady ? 0.0 : dt_;
941 st.srcFactor = srcFactor;
945 st.meanGradOn = st.meanGrad[0] != 0.0 || st.meanGrad[1] != 0.0 || st.meanGrad[2] != 0.0;
946 for (
int a = 0; a < 3; ++a) {
947 st.gPhys[a] = st.meanGrad[a];
948 st.gInt[a] = st.meanGrad[a] * u_.
lenToPhys();
954 Kokkos::deep_copy(
CCExec(), sc.cOld, sc.c);
956 sco::buildBands(sc.AC, sc.AW, sc.AE, sc.AS, sc.AN, sc.AB, sc.AT, kap, unk,
CCConst(gm.
sax),
960 st.wtType.assign(nb, st.wallDefault.type);
961 st.wtK.assign(nb, st.wallDefault.coefficient);
962 st.wtG.assign(nb, st.wallDefault.value);
963 st.wtQ.assign(nb, st.wallDefault.value);
964 for (
const auto& [inst, w] : st.wallInstance)
965 if (inst >= 0 && inst < nb) {
966 st.wtType[inst] = w.type;
967 st.wtK[inst] = w.coefficient;
968 st.wtG[inst] = w.value;
969 st.wtQ[inst] = w.value;
972 for (
int b = 0; b < nb; ++b) {
973 st.wtK[b] = st.wtType[b] == 2 ? st.wtK[b] * sp : 0.0;
974 st.wtQ[b] = st.wtType[b] == 0 ? st.wtQ[b] * sp : 0.0;
975 st.wtG[b] = st.wtType[b] == 0 ? 0.0 : st.wtG[b];
977 if (st.wtTypeV.extent(0) != (std::size_t)nb) {
978 st.wtTypeV = Kokkos::View<int*, CCMem>(
"peclet::flow::sco_wt_type", nb);
979 st.wtKV = Kokkos::View<double*, CCMem>(
"peclet::flow::sco_wt_k", nb);
980 st.wtGV = Kokkos::View<double*, CCMem>(
"peclet::flow::sco_wt_g", nb);
981 st.wtQV = Kokkos::View<double*, CCMem>(
"peclet::flow::sco_wt_q", nb);
985 wt.type = st.wtTypeV;
990 auto ht = Kokkos::create_mirror_view(wt.type);
991 auto hk = Kokkos::create_mirror_view(wt.k);
992 auto hg = Kokkos::create_mirror_view(wt.gval);
993 auto hq = Kokkos::create_mirror_view(wt.q);
994 for (
int b = 0; b < nb; ++b) {
995 ht(b) = st.wtType[b];
1000 Kokkos::deep_copy(wt.type,
ht);
1001 Kokkos::deep_copy(wt.k,
hk);
1002 Kokkos::deep_copy(wt.gval,
hg);
1003 Kokkos::deep_copy(wt.q,
hq);
1011 for (
int b = 0; b < nb; ++b)
1012 st.conj = st.conj || st.wtType[(std::size_t)b] == 3;
1013 st.conj = st.conj && st.solid.extent(0) == n_;
1016 std::vector<char>
conjB;
1022 if (st.sourceIsField && st.sourceField.extent(0) != n_)
1023 throw std::runtime_error(
"cut-cell scalar '" + sc.name +
1024 "': the per-cell source no longer matches this rank's block "
1025 "(redistributed?) -- call set_scalar_source again");
1026 const bool useField = st.sourceIsField;
1033 for (
int f = 0; f < 6; ++f) {
1034 st.dirFace[f] =
false;
1035 if (sc.bc[f] != 2 || bc_[f] == 0 || !touchesGlobalFace(f))
1037 const int a = f / 2, side = f % 2;
1040 const int n[3] = {nx_, ny_, nz_};
1042 if (st.gFace[f].extent(0) != (std::size_t)
nf)
1043 st.gFace[f] = Kokkos::View<double*, CCMem>(sc.name +
"_cc_gface",
nf);
1044 Kokkos::View<double*, CCMem>
gv = st.gFace[f];
1045 if (st.hasProfile[f] && (st.profileN[f][0] != n[
t1] || st.profileN[f][1] != n[
t2]))
1046 throw std::runtime_error(
"cut-cell scalar '" + sc.name +
1047 "': the domain-face profile no longer matches this rank's block");
1048 if (st.hasProfile[f] || st.meanGradOn) {
1051 const int og[3] = {og_.
x, og_.
y, og_.
z};
1052 const double xa = u_.
org[a] + (
double)(og[a] + (side == 0 ? 0 : n[a])) * u_.
h[a];
1053 auto hv = Kokkos::create_mirror_view(
gv);
1055 for (
int j1 = 0; j1 < n[
t1]; ++j1) {
1056 const long k = (
long)j1 + (
long)
j2 * n[
t1];
1057 double v = st.hasProfile[f] ? st.profile[f][(std::size_t)k] : sc.bcVal[f];
1058 if (st.meanGradOn) {
1063 v -= (st.gPhys[0] * p[0] + st.gPhys[1] * p[1]) + st.gPhys[2] * p[2];
1067 Kokkos::deep_copy(
gv,
hv);
1069 Kokkos::deep_copy(
gv, sc.bcVal[f]);
1071 st.dirFace[f] =
true;
1075 if (st.meanGradOn) {
1077 for (
int a = 0; a < 3; ++a)
1078 gf[a] = lam * u_.
w[a] * u_.
hp[a] * st.gInt[a];
1087 const CCConst px(st.phi[0]), py(st.phi[1]), pz(st.phi[2]);
1088 sco::advectionBands(sc.AC, sc.AW, sc.AE, sc.AS, sc.AN, sc.AB, sc.AT, st.outflow, px, py, pz,
1089 CCConst(st.small), unk, steady, e_,
G);
1092 for (
int a = 0; a < 3; ++a) {
1093 if (st.Phi[a].extent(0) != n_)
1094 st.Phi[a] =
CCField(sc.name +
"_cc_Phi", n_);
1096 CCConst(st.small), a, sc.scheme,
false, e_,
G);
1105 if (st.omegaOpen.extent(0) != n_)
1106 st.omegaOpen =
CCField(sc.name +
"_cc_omega_open", n_);
1107 Kokkos::deep_copy(
CCExec(), st.omegaOpen, 0.0);
1109 for (
int f = 0; f < 6; ++f)
1112 CCConst(st.small), unk, st.gFace[f], st.openInflow[f], f / 2, f % 2,
1113 steady, e_,
G, st.omegaOpen, steady);
1116 if (st.meanGradOn) {
1119 const double hh[3] = {0.5 * u_.
hp[0], 0.5 * u_.
hp[1], 0.5 * u_.
hp[2]};
1124#ifdef PECLET_FLOW_MPI
1128 for (
int j = 0; j < 4; ++j)
1132 for (
int a = 0; a < 3; ++a)
1133 st.ubar[a] = m[3] > 0.0 ? m[a] / m[3] : 0.0;
1134 sco::meanGradientAdvectionRhs(sc.b, px, py, pz, kap, unk, st.gInt,
hh, st.ubar, e_,
G);
1142 alloc(st.spx,
"_cc_spx");
1143 alloc(st.spy,
"_cc_spy");
1144 alloc(st.spz,
"_cc_spz");
1145 alloc(st.ms,
"_cc_ms");
1146 alloc(st.ASC,
"_cc_asc");
1147 alloc(st.ASW,
"_cc_asw");
1148 alloc(st.ASE,
"_cc_ase");
1149 alloc(st.ASS,
"_cc_ass");
1150 alloc(st.ASN,
"_cc_asn");
1151 alloc(st.ASB,
"_cc_asb");
1152 alloc(st.AST,
"_cc_ast");
1153 alloc(st.bS,
"_cc_bs");
1154 alloc(st.sOld,
"_cc_sold");
1155 alloc(st.SACs,
"_cc_sacs");
1156 alloc(st.W0,
"_cc_w0");
1157 if (st.cc.extent(0) != (std::size_t)gm.
fac.
n)
1158 st.cc = Kokkos::View<double*, CCMem>(sc.name +
"_cc_cc", gm.
fac.
n);
1160 Kokkos::deep_copy(
CCExec(), st.sOld, st.solid);
1168 Kokkos::View<const double*, CCMem>(st.sS), unk, sunk,
mt, lam);
1169 if (st.meanGradOn) {
1171 for (
int a = 0; a < 3; ++a)
1172 gs[a] = u_.
w[a] * u_.
hp[a] * st.gInt[a];
1186#ifdef PECLET_FLOW_MPI
1205 for (
int f = 0; f < 6; ++f)
1212 if (!st.mg || st.mgVersion != gm.
version || st.mgN != n_) {
1213 st.mg = std::make_shared<ScalarMG>();
1214 st.mg->setMetric(u_.
w, u_.
hp);
1215 const bool per[3] = {bc_[0] == 0, bc_[2] == 0, bc_[4] == 0};
1216 st.mg->setPeriodic(per);
1218#ifdef PECLET_FLOW_MPI
1224 st.mg->init(nx_, ny_, nz_,
fill0);
1225 st.mg->setLevel0Wrap(
true);
1236 const double kA = 1.0 + 4.0 * st.dt * (st.conj ? std::fmax(lam, st.maxSolidD) : lam) *
1237 ((u_.
w[0] + u_.
w[1]) + u_.
w[2]);
1239 in.singular = st.singular;
1248 for (
int f = 0; f < 6; ++f)
1249 in.dirFace[f] = st.dirFace[f];
1250 if (steady && st.advecting) {
1254 in.advective =
true;
1261 for (
int a = 0; a < 3; ++a)
1264 }
else if (st.advecting) {
1266 in.hasOutflow =
true;
1280 st.mgLevels = st.mg->levelsUsed();
1286 if (
in.fullTable && !st.warnedBottom) {
1287 const auto&
lc = st.mg->level(st.mg->levelsUsed() - 1);
1289 if (
lc.gdim.x > be ||
lc.gdim.y > be ||
lc.gdim.z > be) {
1290 st.warnedBottom =
true;
1293 "peclet.flow: cut-cell scalar '%s': the ScalarMG level table stops at %d x "
1294 "%d x %d cells (%d levels), larger than the pressure's bottom extent %d on "
1295 "some axis -- an axis that cannot halve (odd or 2-poor size, or an odd MPI "
1296 "block). Its bottom is smoothing only, not an exact solve, so the iteration "
1297 "count grows with the grid (78 iterations at n = 77 against 6 at n = 80). "
1298 "Size the grid and the blocks with factors of two (printed once)\n",
1299 sc.name.c_str(),
lc.gdim.x,
lc.gdim.y,
lc.gdim.z, st.mg->levelsUsed(), be);
1305 auto allSum = [&](
double* v,
int k) {
1306#ifdef PECLET_FLOW_MPI
1308 std::vector<double> o((std::size_t)k);
1310 for (
int j = 0; j < k; ++j)
1311 v[j] = o[(std::size_t)j];
1317 auto allMax = [&](
double v) {
1318#ifdef PECLET_FLOW_MPI
1327 const double nUnk = (
double)st.numUnknowns;
1328 auto removeMean = [&](
CCField a) {
1339 st.incompatibility = 0.0;
1341 alloc(st.kb,
"_cc_bproj");
1342 alloc(st.kq,
"_cc_q");
1343 Kokkos::deep_copy(
CCExec(), st.kb, sc.b);
1347 st.incompatibility = s[1] > 0.0 ? std::fabs(s[0]) / s[1] : 0.0;
1354 double kc0 = 0.0, k0 = 0.0;
1359 kc0 = st.meanGradOn ? 0.0 : m[0];
1371 Kokkos::deep_copy(
CCExec(), st.kq, r.f);
1375 st.mg->apply(z.f,
rr);
1393 ops.removeMean = [&](
const ScalarVec& a) { removeMean(a.f); };
1394 if (!distributed_) {
1413 vec(st.kt),
vec(st.kz),
vec(st.kz2), st.maxit, st.rtol, st.singular);
1414 if (st.singular && k0 > 0.0) {
1424 ops.matvec(t,
vec(sc.c));
1437 alloc(st.krS,
"_cc_rs");
1438 alloc(st.krhS,
"_cc_rhs");
1439 alloc(st.kpS,
"_cc_ps");
1440 alloc(st.kvS,
"_cc_vs");
1441 alloc(st.ktS,
"_cc_ts");
1442 alloc(st.kzS,
"_cc_zs");
1443 alloc(st.kz2S,
"_cc_z2s");
1444 const double nTot = (
double)st.numUnknowns + (
double)st.numSolidUnknowns;
1446 double sf[2], ss[2];
1449 double v[1] = {sf[0] + ss[0]};
1457 st.incompatibility = 0.0;
1459 alloc(st.kb,
"_cc_bproj");
1460 alloc(st.kq,
"_cc_q");
1461 alloc(st.kbS,
"_cc_bprojs");
1462 alloc(st.kqS,
"_cc_qs");
1463 Kokkos::deep_copy(
CCExec(), st.kb, sc.b);
1464 Kokkos::deep_copy(
CCExec(), st.kbS, st.bS);
1465 double sf[2], ss[2];
1468 double v[2] = {sf[0] + ss[0], sf[1] + ss[1]};
1470 st.incompatibility = v[1] > 0.0 ? std::fabs(v[0]) / v[1] : 0.0;
1479 auto moments = [&](
double m[2]) {
1483 m[0] = a[0] +
b2[0];
1484 m[1] = a[1] +
b2[1];
1487 double kc0 = 0.0, k0 = 0.0;
1491 kc0 = st.meanGradOn ? 0.0 : m[0];
1503 Kokkos::deep_copy(
CCExec(), st.kq, r.f);
1504 Kokkos::deep_copy(
CCExec(), st.kqS, r.s);
1505 removeMeanTwo(st.kq, st.kqS);
1509 st.mg->applyTwo(z.f, z.s, rf,
rs);
1511 removeMeanTwo(z.f, z.s);
1531 ops.removeMean = [&](
const ScalarVec& a) { removeMeanTwo(a.f, a.s); };
1532 if (!distributed_) {
1555 vec2(st.krh, st.krhS),
vec2(st.kp, st.kpS),
vec2(st.kv, st.kvS),
1556 vec2(st.kt, st.ktS),
vec2(st.kz, st.kzS),
vec2(st.kz2, st.kz2S), st.maxit,
1557 st.rtol, st.singular);
1558 if (st.singular && k0 > 0.0) {
1561 const double d = (
kc0 - m[0]) / k0;
1566 ops.matvec(t,
vec2(sc.c, st.solid));
1579 if (!kr.
converged && !st.warnedNoConv) {
1580 st.warnedNoConv =
true;
1582#ifdef PECLET_FLOW_MPI
1591 "peclet.flow: cut-cell scalar '%s': the solve did not converge in %d "
1592 "iterations (residual %.3e of the reference, tolerance %.1e); the iterate is "
1593 "kept -- see diagnostics.scalar_census (printed once)\n",
1594 sc.name.c_str(), kr.
iterations, st.residual, st.rtol);
1598template <
class Gr
id>
1600#ifdef PECLET_FLOW_MPI
1610template <
class Gr
id>
1613 if (st.warnedGeometryVersion == scg_.
version)
1615 st.warnedGeometryVersion = scg_.
version;
1617#ifdef PECLET_FLOW_MPI
1627 const char*
nm = sc.name.c_str();
1628 if (c.numThinSolid > 0)
1631 "peclet.flow: cut-cell scalar '%s': %ld thin-solid cell(s) -- a solid thinner than "
1632 "about a cell, so one fluid unknown spans both of its sides and the scalar passes "
1633 "through it (a resolution warning; diagnostics.scalar_census 'num_thin_solid', "
1635 nm, c.numThinSolid);
1636 const auto r2Warn = [&](
const char* side,
long r2,
long total) {
1637 if (total > 0 && 100 * r2 > total)
1639 "peclet.flow: cut-cell scalar '%s': %ld of %ld %s probes (%.2f %%, more than "
1640 "1 %%) fell back to the first-order last rung R2 -- contacts, thin gaps or "
1641 "corners under-resolved (diagnostics.scalar_census 'probe_rungs', printed "
1643 nm, r2, total, side, 100.0 * (
double)r2 / (
double)total);
1645 r2Warn(
"fluid", c.rungs[3], c.rungs[0] + c.rungs[1] + c.rungs[2] + c.rungs[3]);
1647 r2Warn(
"solid", st.solidRungs[3],
1648 st.solidRungs[0] + st.solidRungs[1] + st.solidRungs[2] + st.solidRungs[3]);
1649 if (c.numSealed > 0 && c.sealedVolume > 1e-6 * c.fluidVolume)
1651 "peclet.flow: cut-cell scalar '%s': %ld sealed cell(s) (fluid behind closed "
1652 "faces, not unknowns) hold %.2e of the fluid volume, more than 1e-6 "
1653 "(diagnostics.scalar_census 'num_sealed' / 'sealed_volume', printed once)\n",
1654 nm, c.numSealed, c.fluidVolume > 0.0 ? c.sealedVolume / c.fluidVolume : 0.0);
1657template <
class Gr
id>
1662 std::vector<double> out((std::size_t)nb, 0.0);
1665 const auto&
fo = scg_.
fac;
1666 Kokkos::View<double*, CCMem>
up(
"peclet::flow::sco_up",
fo.n),
qin(
"peclet::flow::sco_qin",
fo.n);
1670 Kokkos::View<double*, CCMem> us(
"peclet::flow::sco_us",
fo.n);
1677 auto hq = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
qin);
1678 auto hb = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
fo.body);
1679 for (
long f = 0; f <
fo.n; ++f) {
1683 out[(std::size_t)b] += u_.
vol *
hq(f);
1685#ifdef PECLET_FLOW_MPI
1687 std::vector<double> o(out.size());
1698template <
class Gr
id>
1707 const double V = u_.
vol;
1719 st.offS, st.wS,
true);
1727 for (
int f = 0; f < 6; ++f)
1730 st.openInflow[f], f / 2, f % 2, st.steady, e_,
G);
1733 for (
int f = 0; f < 6; ++f)
1736 st.lam * u_.
w[f / 2], f / 2, f % 2, e_,
G);
1738 double v[6] = {0, 0, 0, 0, 0, 0};
1740 double sumAbs = 0.0, kc = 0.0, k = 0.0;
1748 const auto&
fo = gm.
fac;
1749 Kokkos::View<double*, CCMem>
up(
"peclet::flow::sco_up",
fo.n),
1750 qin(
"peclet::flow::sco_qin",
fo.n);
1752 auto hq = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
qin);
1754 for (
long f = 0; f <
fo.n; ++f)
1761 for (
int f = 0; f < 6; ++f)
1764 st.lam * u_.
w[f / 2], 1.0, f / 2, f % 2, e_,
G);
1766 for (
int f = 0; f < 6; ++f)
1769 CCConst(st.small), unk, st.gFace[f], st.openInflow[f], 1.0,
1770 f / 2, f % 2, st.steady, e_,
G);
1774 const bool useField = st.sourceField.extent(0) == n_;
1778#ifdef PECLET_FLOW_MPI
1782 for (
int j = 0; j < 6; ++j)
1787 double sumR = V * v[0];
1788 const double mass = V * v[1],
massOld = V * v[2], wall = V * v[3],
bnd = V * v[4], src = V * v[5];
1795 std::vector<char>
conjB;
1797 double vs[3] = {0.0, 0.0, 0.0},
sAbs = 0.0, w = 0.0;
1803#ifdef PECLET_FLOW_MPI
1807 for (
int j = 0; j < 3; ++j)
1811 sumR = V * (v[0] + vs[0]);
1816 out.mass = mass *
toMass;
1818 out.wallIn = wall *
toRate;
1820 out.sourceIn = src *
toRate;
1829 out.identityError = (dm - st.dt * (wall +
bnd + src) + st.dt *
sumR) *
toMass;
1834template <
class Gr
id>
1839 const auto&
fo = scg_.
fac;
1841 const long n =
fo.n;
1842 out.centroid.assign((std::size_t)n * 3, 0.0);
1843 out.normal.assign((std::size_t)n * 3, 0.0);
1844 out.area.assign((std::size_t)n, 0.0);
1845 out.wallValue.assign((std::size_t)n, std::numeric_limits<double>::quiet_NaN());
1846 out.flux.assign((std::size_t)n, 0.0);
1847 out.instance.assign((std::size_t)n, 0);
1848 out.rung.assign((std::size_t)n, 0);
1849 auto hcell = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
fo.cell);
1850 auto halpha = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
fo.alpha);
1851 auto hn = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
fo.normal);
1852 auto hc = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
fo.centroid);
1853 auto hb = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
fo.body);
1854 auto hs = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
fo.sF);
1855 auto hr = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
fo.rungF);
1856 auto hu = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), scg_.
unknown);
1857 Kokkos::View<double*, CCMem>
up(
"peclet::flow::sco_up", n),
qin(
"peclet::flow::sco_qin", n);
1858 Kokkos::View<double*, CCMem>
usv(
"peclet::flow::sco_us", n);
1859 const bool have = st.built && st.cw.extent(0) == (std::size_t)n;
1860 const bool conj =
have && st.conj && st.cc.extent(0) == (std::size_t)n;
1871 auto hup = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
up);
1872 auto hq = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
qin);
1874 const bool mgOn =
have && st.meanGradOn;
1876 auto hoff = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
fo.offF);
1877 auto hw = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
fo.wF);
1878 const int og[3] = {og_.
x, og_.
y, og_.
z};
1881 for (
long f = 0; f < n; ++f) {
1882 const long i =
hcell(f);
1883 const long xyz[3] = {i % e_.
x, (i / e_.
x) % e_.
y, i / ((
long)e_.
x * e_.
y)};
1884 for (
int d = 0; d < 3; ++d) {
1885 out.centroid[(std::size_t)(3 * f + d)] =
1887 out.normal[(std::size_t)(3 * f + d)] =
hn(f, d);
1890 out.instance[(std::size_t)f] = hb(f);
1891 out.rung[(std::size_t)f] = (
int)
hr(f);
1892 if (!
have || !(
hu(i) > 0.5))
1899 for (
int k = 0; k < 8; ++k)
1901 const double a =
halpha(f), s =
hs(f), lam = st.lam;
1902 const int t = st.wtType[(std::size_t)b];
1905 const double q = a > 0.0 ?
hq(f) / a : 0.0;
1906 cG = lam > 0.0 ? u + q * s / lam : u;
1908 cG = st.wtG[(std::size_t)b];
1910 const double k = st.wtK[(std::size_t)b];
1911 cG = (lam + k * s) > 0.0 ? (lam * u + k * s * st.wtG[(std::size_t)b]) / (lam + k * s) : u;
1913 cG = lam > 0.0 ? u + st.wtQ[(std::size_t)b] * s / lam : u;
1914 out.wallValue[(std::size_t)f] =
cG;
1915 out.flux[(std::size_t)f] = a > 0.0 ?
hq(f) / a / u_.
speedToInt() : 0.0;
std::function< void(CCField)> Fill
static constexpr double kFullTableKappa
The transient level rule (design §5.2, Amendment A3 + ruling D-WO9-3): kappa_A = 1 + 4 dt' D' sum_a w...
int pressureBottomExtent() const
void setScalarSource(const std::string &name, double S)
void scalarCutMatvecTwo(ScalarField &sc, CCField yf, CCField ys, CCField xf, CCField xs)
std::vector< double > getScalarSolid(const std::string &name)
void setScalarWall(const std::string &name, int type, double value, double coefficient, int instance)
std::vector< double > scalarGeometryField(int which)
void scatterInner(CCField fld, const std::vector< double > &in)
void setScalarSourceField(const std::string &name, const std::vector< double > &S)
std::array< double, 3 > scalarMeanFlux(const std::string &name)
bool hasMovingInstance() const
True when at least one instance carries a nonzero velocity – i.e.
void fillGhosts(CCField f)
sco::MeanGradPosition scalarMeanGradPosition(const double Gphys[3]) const
std::vector< double > scalarSolidGeometryField(const std::string &name, int which)
CCField addField(const std::string &name)
void scalarCutRefusals(const ScalarField &sc) const
void scalarCutMatvec(ScalarField &sc, CCField y, CCField x)
ScalarField & cutcellScalar(const std::string &name, const char *who)
ScalarCutFacets scalarFacets(const std::string &name)
void scalarCutAssembleSolve(ScalarField &sc, bool steady)
void advanceScalarCutCell(ScalarField &sc)
std::array< long, 3 > globalCells() const
The GLOBAL cell counts (this rank's block single-rank).
void setScalarBcProfile(const std::string &name, int face, const std::vector< double > &prof, int n1, int n2)
void scalarCutAdvection(ScalarField &sc, bool steady)
const scg::ScalarCutGeometry & scalarCutGeometry()
ScalarFaceFlux scalarFaceFlux(int a) const
ScalarCutBudget scalarBudget(const std::string &name)
std::vector< double > scalarWallFlux(const std::string &name)
void scalarCutWarnings(ScalarField &sc)
void ensureScalarCutGeometry()
void ensureScalarSolidRecord(ScalarField &sc, const std::vector< char > &conj)
bool scalarRootRank() const
rank 0 (or not distributed): the one that prints a warning
int scalarNumBodies() const
void setScalarMeanGradient(const std::string &name, const std::array< double, 3 > &G)
void setScalarSolid(const std::string &name, double D, double capacity, double partition, double contactResistance, int instance)
scg::ScalarCutCensus scalarCutCensus()
SceneMap sceneMap() const
void solveScalarSteady(const std::string &name)
void setScalarTolerance(const std::string &name, double rtol)
void scalarCaptureOpenFaceFlux()
void setScalarMaxIterations(const std::string &name, int maxit)
sco::MaterialTable scalarMaterialTable(ScalarField &sc, std::vector< char > &conj)
void invalidateScalarCutGeometry()
std::vector< double > gatherInner(CCField fld)
bool isCutcellScalar(const std::string &name) const
void buildFacets(ScalarFacetOverlay &fo, const CellPass &cp, CCConst sdf, CCConst sax, CCConst say, CCConst saz, C3 e, int g, const double hp[3])
Kernel 3 (§2.5): exclusive scans of the facet counts and of the cut-cell indicator in x-fastest inner...
void buildFluidProbes(ScalarFacetOverlay &fo, CCConst sdf, CCConst unknown, C3 e, const double hp[3])
Kernel 5 (§2.5, §3.3): the fluid probe ladder of every facet.
void zeroGhostSlab(CCField f, C3 e, int g, int face)
Zero f on every ghost layer beyond face face (0..5 = -x,+x,-y,+y,-z,+z), full transverse extent: the ...
void buildCells(CCField kappa, CCField unknown, const CellPass &cp, CCConst sdf, CCConst sax, CCConst say, CCConst saz, C3 e, int g, const double hp[3])
Kernel 2 (§2.5): kappa, the unknown flag and the per-cell facet count/kind on the inner cells.
void buildApertures(CCField sax, CCField say, CCField saz, CCConst sdf, C3 e, int g)
Kernel 1 (§2.5): the snapped scalar apertures of every face of every inner cell, including the high-s...
ScalarCutCensus localCensus(const ScalarFacetOverlay &fo, const CellPass &cp, CCConst kappa, CCConst unknown, CCConst sax, CCConst say, CCConst saz, C3 e, int g, double vol)
The census of the record on this rank (not yet summed over ranks); sealedVolume in internal volume un...
double dotTwoLocal(CCConst af, CCConst bf, CCConst as, CCConst bs, C3 e, int g)
void conjugateCoefficients(Kokkos::View< double *, CCMem > cc, const scg::ScalarFacetOverlay &fo, Kokkos::View< const std::uint8_t *, CCMem > conjF, Kokkos::View< const double *, CCMem > sS, CCConst unk, CCConst sunk, const MaterialTable &mt, double lamF)
Per facet (§1.4, §4.1): cc = alpha G_c, G_c = 1/(s_f/Lam_f + R_c + s_s/Lam_s), on a facet of a conjug...
void explicitFaceFluxes(CCField Phi, CCConst phi, CCConst c, CCConst unk, CCConst small, int a, int scheme, bool steady, C3 e, int g)
The explicit face fluxes of §6.2 on the low a-faces of the cells g .
void faceFluxes(CCField phi, CCConst vel, CCConst open, CCConst unk, int a, C3 e)
phi = open * vel on every face whose two cells are inside the block (0 on the block's lowest layer),...
void openFaceResidual(CCField r, CCConst c, CCConst phi, CCConst small, CCConst unk, bool inflow, int a, int side, bool steady, C3 e, int g)
r(i) -= F_out c_i on the implicit outflow faces of (a, side) (the budget's flux-form residual; the in...
void meanFluxLocal(CCConst th, CCConst kappa, CCConst unk, CCConst sax, CCConst say, CCConst saz, CCConst phx, CCConst phy, CCConst phz, bool advecting, double lam, const double gi[3], const double hp[3], const double ub[3], C3 e, int g, double diff[3], double adv[3])
scalar_mean_flux (§8.1), this rank's sums (internal; the caller divides by the global cell count): di...
void cellVelocitySumLocal(CCConst phx, CCConst phy, CCConst phz, CCConst unk, const double hh[3], C3 e, int g, double s[3])
(sum U'_x/V, sum U'_y/V, sum U'_z/V) over the unknown inner cells (the moving frame's numerator; its ...
void zeroNonUnknown(CCField c, CCConst unk, C3 e, int g)
Zero c on every inner cell that is not a fluid unknown (§1.3: re-zeroed at the start of every advance...
double openFaceInflux(CCConst c, CCConst cOld, CCConst phi, CCConst small, CCConst unk, Kokkos::View< const double *, CCMem > gv, bool inflow, double vol, int a, int side, bool steady, C3 e, int g)
Sum over one open face of V times the advective flux INTO the domain through it, per internal time (b...
void conjOverlayApply(CCField yf, CCField ys, CCConst xf, CCConst xs, const scg::ScalarFacetOverlay &fo, Kokkos::View< const double *, CCMem > cw, Kokkos::View< const double *, CCMem > cc, Kokkos::View< int *[8], Kokkos::LayoutLeft, CCMem > offS, Kokkos::View< double *[8], Kokkos::LayoutLeft, CCMem > wS, bool subtract=false)
The two-field overlay (§4.1 step 3), one thread per cut cell, facets in CSR order: y_f(i) += sum cw u...
void dot2TwoReduce(CCConst af, CCConst bf, CCConst cf, CCConst as, CCConst bs, CCConst cs, C3 e, int g, R1 &&r1, R2 &&r2)
(sum a b, sum c c) over inner cells, both fields, in one pass, into (r1, r2).
double sourceSumLocal(CCConst kappa, CCConst unk, double srcConst, CCConst src, bool useField, double srcFactor, C3 e, int g)
sum over unknowns of kappa S' (the source part of the right-hand side, per unit V).
void solidFaceProducts(CCField spx, CCField spy, CCField spz, CCConst sunk, CCConst mat, CCConst sax, CCConst say, CCConst saz, const MaterialTable &mt, C3 e)
The level-0 solid face products P_a(i) = Lam_face a_s on the LOW a-face of cell i when cells i - s_a ...
long countImplicitLocal(CCConst phi, CCConst small, CCConst unk, int a, bool steady, C3 e, int g)
Faces carrying flux that are implicit, over the low faces of the inner cells (census num_implicit_fac...
void dot2Reduce(CCConst a, CCConst b, CCConst c, C3 e, int g, R1 &&r1, R2 &&r2)
(sum a b, sum c c) over inner cells in one pass, into (r1, r2).
void meanGradientFaceRhs(CCField b, CCConst unk, CCConst sax, CCConst say, CCConst saz, const double gf[3], C3 e, int g)
Faces (§1.5): b(i) += sum_a gf_a (a_a^+ - a_a^-) over the coupled faces (the bands' guard),...
void dot2Local(CCConst a, CCConst b, CCConst c, C3 e, int g, double &ab, double &cc)
void solidBands(CCField AC, CCField AW, CCField AE, CCField AS, CCField AN, CCField AB, CCField AT, CCConst ms, CCConst sunk, CCConst spx, CCConst spy, CCConst spz, const double w[3], C3 e, int g)
The solid phase's 7 bands (§4.1): AC = m_s + the six face terms w_a P, off-diagonals -w_a P; identity...
void openFaceAdvection(CCField AC, CCField b, CCField outflow, CCConst cOld, CCConst phi, CCConst small, CCConst unk, Kokkos::View< const double *, CCMem > gv, bool inflow, int a, int side, bool steady, C3 e, int g, CCField omega=CCField(), bool wOmega=false)
The open face's row terms on the unknown inner boundary cells of (a, side) (table above): inflow b +=...
void maxabsReduce(CCConst a, C3 e, int g, Red red)
max over inner cells of |a|, through the Max reducer red (Kokkos::Max over a host double or a device ...
void conjFacetFlux(Kokkos::View< double *, CCMem > up, Kokkos::View< double *, CCMem > us, Kokkos::View< double *, CCMem > qin, CCConst xf, CCConst xs, const scg::ScalarFacetOverlay &fo, Kokkos::View< const double *, CCMem > cw, Kokkos::View< const double *, CCMem > rw, Kokkos::View< const double *, CCMem > cc, Kokkos::View< int *[8], Kokkos::LayoutLeft, CCMem > offS, Kokkos::View< double *[8], Kokkos::LayoutLeft, CCMem > wS, const MeanGradPosition &pos, bool mgOn)
Per facet: the probe values and the flux INTO the fluid per unit cell volume per internal time,...
long countSmallLocal(CCConst small, C3 e, int g)
Count of small inner cells (census num_small_cells).
double maxCellPecletLocal(CCConst phx, CCConst phy, CCConst phz, CCConst unk, double lam, const double w[3], C3 e, int g)
A2 census max_cell_peclet on this rank: the max over the interior flux faces (the low faces of the in...
void sumUnknownLocal(CCConst a, CCConst unk, C3 e, int g, double &sum, double &sumAbs)
(sum a, sum |a|) over the inner cells that are unknowns.
void solidRhs(CCField bs, CCConst ms, CCConst sOld, C3 e, int g)
The solid right-hand side b_s = m_s psi_s^n (no solid source in v1, §13 Q17); 0 elsewhere.
void openFaceFluxes(CCField phi, Kokkos::View< const double *, CCMem > plane, CCConst unk, int a, int side, C3 e, int g)
phi(bf) = plane value on the boundary face of (a, side), over the full transverse extent,...
double dotLocal(CCConst a, CCConst b, C3 e, int g)
double bulkCourantLocal(CCConst phx, CCConst phy, CCConst phz, CCConst kappa, CCConst unk, CCConst sax, CCConst say, CCConst saz, double dt, C3 e, int g)
C_bulk of §6.3 on this rank: max over the inner FULL cells (an unknown with kappa = 1 and all six sna...
void solidMomentsLocal(CCConst xs, CCConst kappa, CCConst sunk, CCConst mat, const MaterialTable &mt, C3 e, int g, double &m, double &w)
(sum C_s K kappa_s psi_s, sum C_s K kappa_s) over the solid unknowns (times V by the caller): the sol...
double dirichletFaceInflux(CCConst c, CCConst unk, CCConst saA, Kokkos::View< const double *, CCMem > gv, double lamW, double vol, int a, int side, C3 e, int g)
Sum over one Dirichlet domain face of V t (g_bf - c_i): the flux INTO the domain through it,...
void solidResidualFluxForm(CCField r, CCConst x, CCConst b, CCConst ms, CCConst sunk, CCConst spx, CCConst spy, CCConst spz, const double w[3], C3 e, int g)
The solid residual in FLUX form (the budget, §9): r = b - m_s psi - sum_faces t (psi_i - psi_nb); the...
void solidMeanGradientFaceRhs(CCField bs, CCConst sunk, CCConst spx, CCConst spy, CCConst spz, const double gs[3], C3 e, int g)
Mean-gradient mode, solid faces (§1.5): b_s(i) += sum_a gs_a (P_a(i + s_a) - P_a(i)),...
void dot2TwoLocal(CCConst af, CCConst bf, CCConst cf, CCConst as, CCConst bs, CCConst cs, C3 e, int g, double &ab, double &cc)
void clampNonNegative(CCField omega, C3 e, int g)
omega = max(omega, 0) on the inner cells (A2: implicit backflow never enters the coarse mass).
void buildSolidProbes(Kokkos::View< double *, CCMem > sS, Kokkos::View< int *[8], Kokkos::LayoutLeft, CCMem > offS, Kokkos::View< double *[8], Kokkos::LayoutLeft, CCMem > wS, Kokkos::View< std::uint8_t *, CCMem > rungS, Kokkos::View< const std::uint8_t *, CCMem > conjF, const scg::ScalarFacetOverlay &fo, CCConst sdf, CCConst sunk, C3 e, const double hp[3])
The solid probe ladder (§3.4): per facet of a conjugate body (conjF), the ladder of §3....
void solidMass(CCField ms, CCConst kappa, CCConst sunk, CCConst mat, const MaterialTable &mt, double idt, C3 e, int g)
The solid mass m_s = C_s K kappa_s idt on the solid unknowns, 0 elsewhere (inner cells).
void facetFlux(Kokkos::View< double *, CCMem > up, Kokkos::View< double *, CCMem > qin, CCConst x, const scg::ScalarFacetOverlay &fo, Kokkos::View< const double *, CCMem > cw, Kokkos::View< const double *, CCMem > rw)
Per facet: the probe value u_p of x and the wall flux INTO the fluid per unit cell volume per interna...
void overlayApply(CCField y, CCConst x, const scg::ScalarFacetOverlay &fo, Kokkos::View< const double *, CCMem > cw, bool subtract=false)
y(i) += sum over the facets of cut cell i of cw * (probe interpolation of x): the overlay pass of the...
double maxAbsFluxLocal(CCConst phx, CCConst phy, CCConst phz, C3 e, int g)
max |phi| over the low faces of the inner cells (0: no advection this step).
void solidMeanFluxLocal(CCConst th, CCConst sunk, CCConst spx, CCConst spy, CCConst spz, const double gi[3], const double hp[3], C3 e, int g, double diff[3])
The solid part of scalar_mean_flux's diffusive sum (§8.1): over the low faces of the inner cells whos...
void faceFluxCountsLocal(CCConst phi, CCConst vel, CCConst open, int a, C3 e, int g, long &nFlux, long &nGuarded)
(faces carrying flux, faces whose nonzero open * vel the guard zeroed) over the low faces of the inne...
void dotTwoReduce(CCConst af, CCConst bf, CCConst as, CCConst bs, C3 e, int g, R &&result)
sum over inner cells of af bf + as bs (the two-field dot).
void conjSurrogate(CCField SACf, CCField SACs, CCField W0, CCConst ACf, CCConst ACs, const scg::ScalarFacetOverlay &fo, Kokkos::View< const double *, CCMem > cw, Kokkos::View< const double *, CCMem > cc)
The two-field level-0 surrogate (§4.3): SAC_f = AC_f + sum (cw + cc), SAC_s = AC_s + sum cc,...
void residualFluxForm(CCField r, CCConst c, CCConst b, CCConst kappa, CCConst unk, CCConst sax, CCConst say, CCConst saz, double lam, double idt, const double w[3], C3 e, int g)
The budget's residual r = b - A c in FLUX form (§9): r = b - kappa idt c - sum_faces t (c_i - c_nb) w...
void smallCells(CCField small, CCConst phx, CCConst phy, CCConst phz, CCConst kappa, CCConst unk, double dt, double thr, C3 e, int g)
The small-cell flags of §6.3 over the inner cells: small(i) = 1 iff i is an unknown,...
void meanGradientAdvectionRhs(CCField b, CCConst phx, CCConst phy, CCConst phz, CCConst kappa, CCConst unk, const double gi[3], const double hh[3], const double ub[3], C3 e, int g)
Advection (§1.5): b(i) -= G'.
double maxabsLocal(CCConst a, C3 e, int g)
void buildSolidMaterial(CCField mat, CCField sunk, CCConst kappa, CCConst sax, CCConst say, CCConst saz, const scg::ScalarFacetOverlay &fo, Kokkos::View< const int *, CCMem > owner, bool hasOwner, const MaterialTable &mt, C3 e, int g, double vol[2])
The material per cell and the solid-unknown flags on the inner cells (§1.3, §2.7).
void advectionBands(CCField AC, CCField AW, CCField AE, CCField AS, CCField AN, CCField AB, CCField AT, CCField outflow, CCConst phx, CCConst phy, CCConst phz, CCConst small, CCConst unk, bool steady, C3 e, int g)
The implicit FOU part of the operator (§1.4, §6.3; all faces when steady, §6.7): on every unknown inn...
void buildBands(CCField AC, CCField AW, CCField AE, CCField AS, CCField AN, CCField AB, CCField AT, CCConst kappa, CCConst unk, CCConst sax, CCConst say, CCConst saz, double lam, double idt, const double w[3], C3 e, int g)
The 7 bands (§4.1): AC = kappa idt + sum of the six face terms Lam w_a a (guarded toward a cell that ...
void dirichletFaceFold(CCField AC, CCField b, CCConst unk, CCConst saA, Kokkos::View< const double *, CCMem > gv, double lamW, int a, int side, C3 e, int g)
Dirichlet domain face (§1.4 B_i): for the inner boundary cell i of face (a, side),...
int bodyIndex(double m, int nb)
A body id clamped into the table (out of range -> 0, as facetCoefficients does).
void explicitAdvectionRhs(CCField b, CCConst Px, CCConst Py, CCConst Pz, CCConst unk, C3 e, int g)
b(i) -= the net explicit outflow sum_a (Phi_a(i + s_a) - Phi_a(i)) on the unknown inner cells.
void kappaMomentsLocal(CCConst c, CCConst kappa, CCConst unk, C3 e, int g, double &kc, double &k)
(sum kappa c, sum kappa) over the unknowns (times V by the caller).
void openFaceCountsLocal(CCConst phi, CCConst small, CCConst unk, bool inflow, int a, int side, bool steady, C3 e, int g, long &nFlux, long &nImpl, long &nBack)
The census counts of one open face over this rank's inner boundary cells: (faces carrying flux on the...
void dirichletFaceResidual(CCField r, CCConst c, CCConst unk, CCConst saA, double lamW, int a, int side, C3 e, int g)
r(i) -= 2 Lam w_a a_bf c_i on the inner boundary cells of a Dirichlet domain face (the LHS part of di...
void facetCoefficients(Kokkos::View< double *, CCMem > cw, Kokkos::View< double *, CCMem > rw, const scg::ScalarFacetOverlay &fo, CCConst unk, const WallTable &wt, double lam)
Per-facet overlay coefficients (§4.1): cw = alpha G, rw = alpha G g (Dirichlet/Robin) or cw = 0,...
double maxFacetLocal(Kokkos::View< const double *, CCMem > cw)
max over facets of cw (0 when there is no Dirichlet/Robin facet: the steady singular test).
void faceTangents(int a, int &t1, int &t2)
The tangential axes of face axis a, in x, y, z order (t1 < t2): the layout of a domain-face profile (...
void surrogateDiagonal(CCField SAC, CCConst AC, const scg::ScalarFacetOverlay &fo, Kokkos::View< const double *, CCMem > cw)
The level-0 surrogate diagonal (§4.3): SAC = AC + sum of the cut cell's cw (the probe weights lumped,...
void dotReduce(CCConst a, CCConst b, C3 e, int g, R &&result)
sum over inner cells of a(i) b(i), into result.
void buildRhs(CCField b, CCConst cOld, CCConst kappa, CCConst unk, double idt, double srcConst, CCConst src, bool useField, double srcFactor, C3 e, int g)
Base right-hand side: b = kappa (idt c^n + S'), identity rows 0.
void advectionResidual(CCField r, CCConst c, CCConst phx, CCConst phy, CCConst phz, CCConst small, CCConst unk, bool steady, C3 e, int g)
r(i) -= the implicit faces' net outflow sum F_out c_up at c (the budget's flux-form residual,...
void meanGradientProbeShift(Kokkos::View< double *, CCMem > rw, const scg::ScalarFacetOverlay &fo, Kokkos::View< const double *, CCMem > cw, const MeanGradPosition &pos)
Dirichlet/Robin probes (§1.5): rw(f) -= cw(f) sum_k w_k G.x_k, so that rw = alpha G_phi (g - sum_k w_...
void addOnUnknown(CCField a, CCConst unk, double d, C3 e, int g)
a(i) += d on the inner cells that are unknowns.
void overlayRhs(CCField b, const scg::ScalarFacetOverlay &fo, Kokkos::View< const double *, CCMem > rw)
b(i) += sum over the facets of cut cell i of rw (the wall part of the right-hand side).
void conjMeanGradientShift(CCField bf, CCField bs, const scg::ScalarFacetOverlay &fo, Kokkos::View< const double *, CCMem > cc, Kokkos::View< int *[8], Kokkos::LayoutLeft, CCMem > offS, Kokkos::View< double *[8], Kokkos::LayoutLeft, CCMem > wS, const MeanGradPosition &pos)
Mean-gradient mode, conjugate facets (§1.5): the fluid row carries + cc (u_pf - u_ps) with u = theta ...
void residualFrom(CCField y, CCConst b, C3 e, int g)
y = b - y over inner cells (the residual from a matvec result held in y).
Kokkos::View< double, CCMem > Slot
ScalarKrylovResult scalarBiCGStab(Ops &ops, const ScalarVec &b, const ScalarVec &x, const ScalarVec &r, const ScalarVec &rh, const ScalarVec &p, const ScalarVec &v, const ScalarVec &t, const ScalarVec &z, const ScalarVec &z2, int maxit, double rtol, bool singular)
BiCGStab on A x = b.
void ccReduce3(const char *name, C3 lo, C3 hi, F f, R &&... reducers)
void residentKrylov(State &st, ScalarKrylovOps &ops)
Arm ops for the resident pass with the persistent slots of st (ks, pk, allocated once).
void applyCutcellOp(CCField y, CCConst x, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, C3 e, int g)
Kokkos::View< double *, CCMem > CCField
Kokkos::DefaultExecutionSpace CCExec
Kokkos::View< const double *, CCMem > CCConst
The per-cut-cell-scalar state (ScalarField::cut).
std::shared_ptr< ScalarCutState > cut
The injected pieces as callables (host-side calls that launch kernels; the overhead of the indirectio...
Outcome of one scalarBiCGStab call.
int iterations
BiCGStab iterations (both passes when it restarted)
double trueRes
max|b - A x| at exit (after the restart, if any)
double ref
max(max|b|, max|A x0|)
bool converged
trueRes <= 10 rtol ref
A Krylov vector (§5.1): the fluid field and, for a conjugate scalar, the solid field (WO-7).
Wall condition per body (§1.1, §8.1 set_scalar_wall), in the caller's PHYSICAL units; the conversion ...
diagnostics.scalar_budget (§8.2), physical; collective under MPI.
diagnostics.scalar_facets (§8.2): this rank's facets, physical; per facet 3 values for the centroid a...
The ONE predicate for the cut-cell scalar's advective face flux (doc/scalar_ibm_design....
The map between the solver's INDEX coordinates and the coordinate system the analytic scene is writte...
double w[3]
w_a = 1 / h_a'^2 (<= 1)
double org[3]
physical lower corner of the GLOBAL inner grid
double areaToPhys() const
double speedToInt() const
double h[3]
cell size per axis = extent / cells
double hp[3]
h_a' = h_a / hRef (>= 1, exactly 1 on the finest axis)
double resistToInt() const
double vol
V' = h_x' h_y' h_z' (cell volume in hRef^3)
Per-cell flags of the record pass, on the INNER grid (x fastest).
The geometry part of diagnostics.scalar_census (§8.2).
The whole record: per-cell fields on the extended block (kappa and unknown haloed; the apertures comp...
long version
the Solver's geometry version this record was built for
long n
facets on this rank
The per-body material of a conjugate scalar, internal units (§4.2): conj 1 on a conjugate body; lam =...
G.x at the centre of extended-block cell j (physical; cellCentres' formula, unwrapped).
Device copy of the per-body wall table, resolved (instance override or default) and converted to inte...