8#ifndef PECLET_FLOW_FLOW_IBM_GEOMETRY_HPP
9#define PECLET_FLOW_FLOW_IBM_GEOMETRY_HPP
20 const std::size_t n = (std::size_t)nx_ * ny_ * nz_;
22 throw std::runtime_error(
"set_solid: expected nx*ny*nz values");
25 Kokkos::View<const double*, Kokkos::HostSpace, Kokkos::MemoryTraits<Kokkos::Unmanaged>>;
32 std::vector<double>
scaled(n);
33 for (std::size_t i = 0; i < n; ++i)
44 const bool useEx = hasExactCross_ && !Grid::collocated;
46 const mreal lam = (
mreal)(wallSlip_ ? slipLambda_ : 0.0);
47 if (lam > 0.0f && slipSkipDev_.data() ==
nullptr)
48 slipSkipDev_ = Kokkos::View<int, CCMem>(
"peclet::flow::slipSkip");
49 for (
int c = 0; c < 3; ++c) {
53 CCConst(sdf_), e_,
G, off, 0, C[c].ov, C[c].idMap, C[c].counter,
56 lam > 0.0f ? slipSkipDev_ : Kokkos::View<int, CCMem>(),
61 Kokkos::deep_copy(
sk, slipSkipDev_);
62 slipSandwich_[(std::size_t)c] =
sk;
64 slipSandwich_[(std::size_t)c] = 0;
75 Kokkos::deep_copy(C[c].u, 0.0);
77 if constexpr (Grid::collocated)
87 Kokkos::deep_copy(uf_, 0.0);
88 Kokkos::deep_copy(vf_, 0.0);
89 Kokkos::deep_copy(wf_, 0.0);
90 faceFieldValid_ =
false;
98 B3 e{e_.
x, e_.
y, e_.
z};
99 for (
int face = 0; face < 6; ++face) {
100 if (!touchesGlobalFace(face))
102 const int a = face / 2, s = face % 2;
105 else if (bc_[face] != 0)
122 if (cutcellPressure_) {
133 geometryBuilt_ =
true;
138 if constexpr (Grid::collocated) {
146 if (colSchemeAuto_) {
147 const bool ok = !(porous_ || varRho_ || hasBc_ || useChebyshev_ || hasExactCross_ ||
148 hasOpenOverride_ || fluidOnlyMode_ != 0);
150 ghostProjection_ =
true;
156 if (ghostProjection_)
158 ghostProjection_ =
false;
161 "peclet::flow SolverColocated: AUTO scheme fell back to gauge-exact "
162 "(configuration unsupported by the ghost projection v1). Select explicitly "
163 "with set_collocated_scheme to silence this notice.\n");
171#ifdef PECLET_FLOW_MPI
179 const std::size_t nInner = (std::size_t)nx_ * ny_ * nz_;
182 Kokkos::parallel_reduce(
183 "peclet::flow::has_solid", Kokkos::RangePolicy<CCExec>(0, nInner),
187#ifdef PECLET_FLOW_MPI
194#ifdef PECLET_FLOW_MPI
200 const int ex = e_.
x, ey = e_.
y,
nx = nx_,
ny = ny_,
nz = nz_, g =
G;
203 Kokkos::parallel_for(
206 sdf((
long)(x + g) + (
long)(y + g) * ex + (
long)(z + g) * (
long)ex * ey) =
207 dinC((std::size_t)x + (std::size_t)y *
nx + (std::size_t)z * (std::size_t)
nx *
ny);
217 const int ex = e_.
x, ey = e_.
y, ez = e_.
z,
nx = nx_,
ny = ny_,
nz = nz_, g =
G;
219 Kokkos::parallel_for(
224 sdf((
long)x + (
long)y * ex + (
long)z * (
long)ex * ey) =
225 din((std::size_t)
ix + (std::size_t)
iy *
nx + (std::size_t)
iz * (std::size_t)
nx *
ny);
256 if (hasBc_ && !Grid::collocated)
290 if (vmgExplicit_ || vmgAutoCells_ == 0)
292 const bool eligible = !varProps_ && !varRho_ && !hasDrag_ && !porous_ && !Grid::collocated &&
293 (!hasBc_ || hasSolid_ || !implicitFou_);
299 1.0 + 4.0 * dt_ * mu_ * (u_.
w[0] + u_.
w[1] + u_.
w[2]) / (rho_ > 0.0 ? rho_ : 1.0);
300 int minExt = std::min({e_.
x - 2 *
G, e_.
y - 2 *
G, e_.
z - 2 *
G});
304#ifdef PECLET_FLOW_MPI
314 useVelocityMg_ =
eligible && kappa >= vmgAutoMinCond_ &&
minExt >= vmgAutoMinExtent_ &&
316 if (useVelocityMg_) {
328 if (useVelocityMg_ &&
334#ifdef PECLET_FLOW_MPI
342 vmg_.
init(nx_, ny_, nz_, vmgLevels_);
345 if (!hasBc_ || hasSolid_) {
346 vmgTheta_ =
CCField(
"vmgTheta", n_);
347 vmgClean_ =
CCField(
"vmgClean", n_);
360 if (hasOpenOverride_) {
363 const std::vector<double>* src[3] = {&oxOverride_, &oyOverride_, &ozOverride_};
364 CCField dst[3] = {ox_, oy_, oz_};
365 for (
int f = 0; f < 3; ++f) {
366 CCField din(
"peclet::flow::openOv_d", (std::size_t)nx_ * ny_ * nz_);
369 Kokkos::View<
const double*, Kokkos::HostSpace, Kokkos::MemoryTraits<Kokkos::Unmanaged>>(
370 src[f]->
data(), src[f]->size()));
372 const int ex = e_.
x, ey = e_.
y, ez = e_.
z,
nx = nx_,
ny = ny_,
nz = nz_, g =
G;
374 Kokkos::parallel_for(
379 o((
long)x + (
long)y * ex + (
long)z * (
long)ex * ey) =
din(
380 (std::size_t)
ix + (std::size_t)
iy *
nx + (std::size_t)
iz * (std::size_t)
nx *
ny);
385 if constexpr (Grid::collocated) {
388 if (faceInterp_ >= 5 && faceInterp_ <= 7) {
395 const std::size_t
nn = n_;
396 for (
int c = 0; c < 3; ++c) {
398 Kokkos::parallel_for(
399 "peclet::flow::embed_solid_mask", Kokkos::RangePolicy<CCExec>(0,
nn),
400 KOKKOS_LAMBDA(std::size_t i) { m(i) = cs(i) < 1e-6 ? 1.0 : 0.0; });
404 if (fluidOnlyMode_ == 1) {
417 for (
int a = 0; a < 3; ++a) {
419 const long sa = (a == 0) ? 1 : (a == 1) ? (
long)e.x : (
long)e.x * e.y;
420 Kokkos::parallel_for(
421 "peclet::flow::fluid_only_openness",
424 const long i = (
long)x + (
long)y * e.x + (
long)z * (
long)e.x * e.y;
425 if (
sd(i) < 0.0 ||
sd(i -
sa) < 0.0)
430#ifdef PECLET_FLOW_MPI
443 for (
int a = 0; a < 3; ++a)
444 if (bc_[2 * a + 1] != 0 && touchesGlobalFace(2 * a + 1))
451 B3 e2{e_.
x, e_.
y, e_.
z};
453 for (
int a = 0; a < 3; ++a)
454 for (
int s = 0; s < 2; ++s) {
455 const int t = bc_[2 * a + s];
456 const bool open = (t == 3) || (t == 2 && (bcProf_[2 * a + s].extent(0) > 0 ||
457 std::fabs(bcVel_[2 * a + s][a]) > 1e-12));
458 if (t != 0 && !open && touchesGlobalFace(2 * a + s))
480 const bool gp = ghostProjection_ && oxb_.extent(0) > 0;
481 CCField dst[3] = {ox1_, oy1_, oz1_};
482 CCField src[3] = {
gp ? oxb_ : ox_,
gp ? oyb_ : oy_,
gp ? ozb_ : oz_};
483 const int de[3] = {e1_.
x, e1_.
y, e1_.
z},
se[3] = {e_.
x, e_.
y, e_.
z};
484 const long dst3[3] = {1, (
long)e1_.
x, (
long)e1_.
x * e1_.
y};
485 const long sst3[3] = {1, (
long)e_.
x, (
long)e_.
x * e_.
y};
486 for (
int a = 0; a < 3; ++a) {
487 if (bc_[2 * a + 1] != 3 || !touchesGlobalFace(2 * a + 1))
489 const int b = (a + 1) % 3, c = (a + 2) % 3;
493 const int nb =
de[b] - 2, nc =
de[c] - 2;
496 Kokkos::parallel_for(
519 const int dims[3] = {e_.
x, e_.
y, e_.
z};
520 const long st[3] = {1, (
long)e_.
x, (
long)e_.
x * e_.
y};
523 for (
int f = 0; f < 6; ++f) {
525 own[f] = touchesGlobalFace(f) ? 1 : 0;
527 long sealedOn[6] = {0, 0, 0, 0, 0, 0};
528 for (
int a = 0; a < 3; ++a)
529 for (
int s = 0; s < 2; ++s) {
530 if (
bct[2 * a + s] != 2 || !
own[2 * a + s])
532 const int b = (a + 1) % 3, c = (a + 2) % 3;
533 const long sa = st[a],
sb = st[b], sc = st[c];
534 const long base = (
long)((s == 0) ?
G :
dims[a] -
G - 1) *
sa;
536 const int nb =
dims[b] - 2 *
G, nc =
dims[c] - 2 *
G;
539 const long ss[3] = {st[0], st[1], st[2]};
540 const int cA = a,
cB = b,
cC = c,
cS = s;
542 Kokkos::parallel_reduce(
553 for (
int a2 = 0; a2 < 3; ++a2)
554 for (
int s2 = 0; s2 < 2; ++s2) {
555 const bool global =
own[2 * a2 + s2] != 0 &&
556 ((s2 == 0) ? (
idx[a2] ==
G) : (
idx[a2] == dd[a2] -
G - 1));
557 const int t =
bct[2 * a2 + s2];
558 if (
global && (t == 1 || t == 2 || t == 4))
560 alpha += oc[a2](i + ((s2 == 0) ? 0 : ss[a2]));
568#ifdef PECLET_FLOW_MPI
572 for (
int f = 0; f < 6; ++f)
576 for (
int f = 0; f < 6; ++f) {
579 static const char*
kFace[6] = {
"-x",
"+x",
"-y",
"+y",
"-z",
"+z"};
580 throw std::runtime_error(
581 std::string(
"set_solid: the immersed solid seals ") + std::to_string(
sealedOn[f]) +
582 " fluid cell(s) against the " +
kFace[f] +
583 " INFLOW face off from the rest of the domain: their pressure row is entirely closed "
584 "yet the inflow still feeds them, which no pressure field can satisfy (the pressure "
585 "solve would run to its iteration cap with max|div| stuck at the inflow velocity). "
586 "Either pull the solid clear of that face, refine the grid so the pocket connects, or "
587 "make the face a wall. See doc/cutcell_openbc_convergence.md.");
593 if (fluidOnlyMode_ == 2) {
598 if (porous_ || varRho_ || hasBc_ || ghostProjection_ || distributed_ || !Grid::collocated)
599 throw std::runtime_error(
600 "set_fluid_only_constraint(2): v1 is single-rank periodic collocated only");
604 const C3 e1 = e1_, e2 = e_;
605 for (
int a = 0; a < 3; ++a) {
607 const long sa2 = (a == 0) ? 1 : (a == 1) ? (
long)e2.x : (
long)e2.x * e2.y;
608 Kokkos::parallel_for(
614 o1((
long)(x + 1) + (
long)(y + 1) *
e1.x + (
long)(z + 1) * (
long)
e1.x *
e1.y) = 0.0;
618 starCounter_ = Kokkos::View<int, CCMem>(
"star_counter");
619 const C3 nn{nx_, ny_, nz_};
634 auto sdfInner_ = [&]() ->
const std::vector<double>& {
636 const std::size_t
nI = (std::size_t)nx_ * ny_ * nz_;
639 Kokkos::View<
double*, Kokkos::HostSpace, Kokkos::MemoryTraits<Kokkos::Unmanaged>>(
645 if (ghostProjection_) {
650 if (porous_ || varRho_ || hasBc_)
651 throw std::runtime_error(
652 "ghost projection: incompatible with porous/variable-rho/domain-BC (v1)");
653 const std::size_t nInner = (std::size_t)nx_ * ny_ * nz_;
655 gpIdMap_ = Kokkos::View<int*, CCMem>(
"gp_idmap", nInner);
656 gpCounter_ = Kokkos::View<int, CCMem>(
"gp_counter");
679 std::vector<double>
work;
680 int fx = nx_,
fy = ny_,
fz = nz_;
682#ifdef PECLET_FLOW_MPI
694 for (
int r = 0; r <
nRanks; ++r) {
695 const auto b =
dec_->block(r);
696 cnts[r] = (int)(b.size[0] * b.size[1] * b.size[2]);
700 std::vector<double>
flat((std::size_t)acc);
704 for (
int r = 0; r <
nRanks; ++r) {
705 const auto b =
dec_->block(r);
706 const double* src =
flat.data() +
disp[r];
707 for (
int z = 0; z < (int)b.size[2]; ++z)
708 for (
int y = 0; y < (int)b.size[1]; ++y)
709 for (
int x = 0; x < (int)b.size[0]; ++x)
710 work[(std::size_t)(x + b.origin[0]) + (std::size_t)(y + b.origin[1]) *
fx +
711 (std::size_t)(z + b.origin[2]) * (std::size_t)
fx *
fy] =
712 src[(std::size_t)x + (std::size_t)y * b.size[0] +
713 (std::size_t)z * (std::size_t)b.size[0] * b.size[1]];
718 const std::size_t
nTot =
work.size();
720 auto id = [&](
int x,
int y,
int z) {
721 return (std::size_t)((x +
nx) %
nx) + (std::size_t)((y +
ny) %
ny) *
nx +
722 (std::size_t)((z +
nz) %
nz) * (std::size_t)
nx *
ny;
724 std::vector<int> comp(
nTot, -1);
725 std::vector<std::size_t>
stack;
731 std::size_t size = 0;
734 while (!
stack.empty()) {
735 const std::size_t c =
stack.back();
738 const int x = (int)(c %
nx), y = (int)((c /
nx) %
ny),
739 z = (
int)(c / ((std::size_t)
nx *
ny));
740 const int nb[6][3] = {{x - 1, y, z}, {x + 1, y, z}, {x, y - 1, z},
741 {x, y + 1, z}, {x, y, z - 1}, {x, y, z + 1}};
743 const std::size_t j = id(q[0], q[1], q[2]);
744 if (comp[j] >= 0 ||
work[j] < 0.0)
747 if (0.5 * (
work[c] +
work[j]) < 0.0)
762 for (std::size_t i = 0; i <
nTot; ++i)
764 work[i] = -(std::abs(
work[i]) * 1.001 + 1e-30);
769 "peclet::flow ghost projection: %d fluid components; decoupled %zu pocket "
770 "cells outside the main component (%zu of %zu fluid cells)\n",
773#ifdef PECLET_FLOW_MPI
777 for (
int z = 0; z < nz_; ++z)
778 for (
int y = 0; y < ny_; ++y)
779 for (
int x = 0; x < nx_; ++x)
780 sdfGpHost[(std::size_t)x + (std::size_t)y * nx_ +
781 (std::size_t)z * (std::size_t)nx_ * ny_] =
782 work[(std::size_t)(x + b.origin[0]) + (std::size_t)(y + b.origin[1]) *
fx +
783 (std::size_t)(z + b.origin[2]) * (std::size_t)
fx *
fy];
788 sdfGp_ =
CCField(
"peclet::flow::sdfGp", n_);
791#ifdef PECLET_FLOW_MPI
795 auto h = Kokkos::create_mirror_view(sdfGp_);
796 Kokkos::deep_copy(h, sdfGp_);
797 for (
int z = 0; z < nz_; ++z)
798 for (
int y = 0; y < ny_; ++y)
799 for (
int x = 0; x < nx_; ++x)
800 h((
long)(x +
G) + (
long)(y +
G) * e_.
x + (
long)(z +
G) * (
long)e_.
x * e_.
y) =
801 sdfGpHost[(std::size_t)x + (std::size_t)y * nx_ +
802 (std::size_t)z * (std::size_t)nx_ * ny_];
803 Kokkos::deep_copy(sdfGp_, h);
808 CCField din(
"peclet::flow::sdfGpInner_d", nInner);
811 Kokkos::View<
const double*, Kokkos::HostSpace, Kokkos::MemoryTraits<Kokkos::Unmanaged>>(
814 const int ex = e_.
x, ey = e_.
y, ez = e_.
z,
nx = nx_,
ny = ny_,
nz = nz_, g =
G;
815 Kokkos::parallel_for(
820 sdfGp((
long)x + (
long)y * ex + (
long)z * (
long)ex * ey) =
din(
821 (std::size_t)
ix + (std::size_t)
iy *
nx + (std::size_t)
iz * (std::size_t)
nx *
ny);
827 gpMatrixOrder_, gpRhsOrder_,
834#ifdef PECLET_FLOW_MPI
858#ifdef PECLET_FLOW_MPI
865 mg_.
init(nx_, ny_, nz_,
883 Kokkos::deep_copy(phi_, 0.0);
884 Kokkos::deep_copy(P_, 0.0);
889 return geometryBuilt_;
895 throw std::runtime_error(
897 ": call BEFORE the geometry (set_solid / set_pressure_geometry / set_solid_from_scene) "
898 "-- this setting is folded into the operators when the geometry is built, so a later "
899 "call would be silently ignored");
void setOutflowCoefficient(bool on)
void setMetric(const double hp[3])
Per-axis cell spacings h_a' = h_a / hRef of the physical domain (doc/anisotropic_metric....
void setOpenness(CCConst ox, CCConst oy, CCConst oz, double idx2, double idy2, double idz2)
void setBoundaryConditions(const int bc[6])
void setRepartition(bool on)
Repartition stages for a weighted level-0 decomposition (see repartition_); before initMpi.
void setAgglomerationMode(int mode)
void init(int nx, int ny, int nz, int nLevels)
void setSolidUploadSdf(CCField din)
void setSolidInitPressureMg()
void extendSdfDomainGhosts(CCField f)
Repair a cell-centred geometry field's ghost band on every rank-owned NON-PERIODIC domain face,...
void setSolid(const std::vector< double > &sdfInner, bool cutcellPressure)
Host entry point: upload the inner SDF once and delegate.
void setSolidSelectScheme()
void bridgeOutflowFacePlanes()
Bridge the HIGH-side boundary-face openness plane of every rank-owned outflow axis from the g=2 block...
void copyInner(CCField dst, C3 de, int dg, CCConst src, C3 se, int sg)
void requireNoGeometry(const char *who) const
void exchangeExtRaw(CCField f)
Raw ghost fill of an extended-block field: the rank halo exchange under MPI, the periodic wrap single...
void setSolidGhostProjectionOverlay(CCField din)
void buildVelocityOverlays(bool resetU)
Build the three per-component Robust-Scaled cut-cell overlays + solid masks from the CURRENT sdf_ (ex...
void setSolidBuildOverlaysAndStencils()
void setPressureGeometry(const std::vector< double > &sdfInner)
void setSolidDevice(CCField din, bool cutcellPressure)
Device entry point (Layer 2): the inner SDF is ALREADY on device, so geometry never round-trips throu...
bool geometryBuilt() const
void setSolidBuildOpenness()
void setSolidStarOverlay()
void setSolidVelocityMgAuto()
void checkSealedInflowCells()
Reject geometry that seals fluid cells against a prescribed INFLOW face off from the rest of the doma...
void invalidateScalarCutGeometry()
void setBC(const int bc[6])
void init(int nx, int ny, int nz, int nLevels)
void setMetric(const double w[3], const double hp[3])
Per-axis metric of the physical domain (doc/anisotropic_metric.md §1.1): the weights w_a = 1/h_a'^2,...
void buildFaceCentroidDist(CCField xcx, CCField xcy, CCField xcz, CCConst sdf, C3 e)
void ibmSolidMask(CCField mask, CCConst sdf, C3 ext, Off3 off)
void gpBinaryOpenness(CCField ox, CCField oy, CCField oz, CCConst sdf, C3 ext)
Binary openness for the symmetric MG surrogate, on the extended-block layout of buildOpenness: o(face...
StarOverlay starMakeOverlay(long n)
void buildCellFraction(CCField cs, CCConst sdf, C3 e, int g)
void bcZeroOpenness(BField oa, B3 ext, int g, int a, int s)
int buildStarOverlay(CCConst sdf, CCConst ox, CCConst oy, CCConst oz, C3 ext, int g, C3 nn, const StarOverlay &ov, Kokkos::View< int, CCMem > counter)
Count + fill the star overlay from the cell-centered sdf and the ORIGINAL (unfiltered) apertures on t...
int gpDebugLevel()
0 = off (default). Read once per call; cheap enough, and keeps the flag hot-swappable in tests.
void buildOpennessHighFace(CCField oa, CCConst sdf, C3 ext, int g, int a, double dx, double dy, double dz, int order=1)
int buildGpOverlay(CCConst sdf, C3 ext, int g, C3 nn, const GpOverlayReal< Real > &ov, Kokkos::View< int *, CCMem > idMap, Kokkos::View< int, CCMem > counter, int matrixOrder=2, int rhsOrder=2, CCConst tx=CCConst(), CCConst ty=CCConst(), CCConst tz=CCConst(), bool useGhost=false)
Build the overlay over the inner grid nn from the cell-centered sdf on the extended block (ext,...
void bcMirrorGhost(BField f, B3 ext, int g, int a, int s)
Kokkos::View< double *, CCMem > CCField
Kokkos::DefaultExecutionSpace CCExec
void gpDebugReport(const GpOverlayReal< Real > &ov, int nRows, C3 nn, Kokkos::View< int *, CCMem > idMap, int rank=0)
Census + optional per-row dump of the built overlay.
void buildOpenness(CCField ox, CCField oy, CCField oz, CCConst sdf, C3 ext, double dx, double dy, double dz, int order=1)
void bcNeumannGhost(BField f, B3 ext, int g, int a, int s)
Kokkos::View< const double *, CCMem > CCConst
double w[3]
w_a = 1 / h_a'^2 (<= 1)
double hp[3]
h_a' = h_a / hRef (>= 1, exactly 1 on the finest axis)
One entry per eliminated solid-centered cell: packed INNER flat index + the apertures of its (up to 6...