8#ifndef PECLET_FLOW_FLOW_IBM_SCENE_HPP
9#define PECLET_FLOW_FLOW_IBM_SCENE_HPP
17 namespace g = peclet::core::geom;
18 g::SceneBuilder<double> b =
21 if (b.instances().empty())
22 throw std::runtime_error(
"set_scene: at least one instance required");
23 for (
const auto&
nd : b.nodes())
24 if (
nd.kind == g::kGrid)
25 throw std::runtime_error(
"set_scene: grid leaves are not supported (analytic scenes only)");
28 double GX = nx_,
GY = ny_,
GZ = nz_;
38 peclet::core::Vec3<double>
so{0, 0, 0},
se{
GX,
GY,
GZ};
41 throw std::runtime_error(
42 "set_scene: the physical domain was armed for a different global grid than the solver "
43 "has (pass global_cells = the grid init_mpi gets)");
44 so = peclet::core::Vec3<double>{u_.
org[0], u_.
org[1], u_.
org[2]};
45 se = peclet::core::Vec3<double>{u_.
ext[0], u_.
ext[1], u_.
ext[2]};
47 g::PeriodicBox<double> box{
se.x,
se.y,
se.z, periodic};
48 sceneB_ = std::make_shared<g::SceneBuilder<double>>(std::move(b));
51 scenePeriodic_ = periodic;
59 nInst_ =
static_cast<int>(sceneB_->instances().size());
60 instCen_.assign((std::size_t)nInst_ * 3, 0.0);
61 instLin_.assign((std::size_t)nInst_ * 3, 0.0);
62 instAng_.assign((std::size_t)nInst_ * 3, 0.0);
63 instCenPinned_.assign((std::size_t)nInst_, 0);
64 for (
int i = 0; i < nInst_; ++i) {
65 auto&
in = sceneB_->instanceRef(i);
74 const bool pinned =
in.centerPinned && std::isfinite(
in.center.x) &&
75 std::isfinite(
in.center.y) && std::isfinite(
in.center.z);
76 instCenPinned_[(std::size_t)i] =
pinned ? 1 : 0;
77 const auto c =
pinned ?
in.center :
in.transform.translation;
79 instCen_[3 * (std::size_t)i + 0] = c.x;
80 instCen_[3 * (std::size_t)i + 1] = c.y;
81 instCen_[3 * (std::size_t)i + 2] = c.z;
82 instLin_[3 * (std::size_t)i + 0] =
in.linVel.x;
83 instLin_[3 * (std::size_t)i + 1] =
in.linVel.y;
84 instLin_[3 * (std::size_t)i + 2] =
in.linVel.z;
85 instAng_[3 * (std::size_t)i + 0] =
in.angVel.x;
86 instAng_[3 * (std::size_t)i + 1] =
in.angVel.y;
87 instAng_[3 * (std::size_t)i + 2] =
in.angVel.z;
96 throw std::runtime_error(
"instance_center: instance index out of range");
97 return {instCen_[3 * (std::size_t)i], instCen_[3 * (std::size_t)i + 1],
98 instCen_[3 * (std::size_t)i + 2]};
104 throw std::runtime_error(
"instance_center_pinned: instance index out of range");
105 return instCenPinned_[(std::size_t)i] != 0;
110 const std::array<double, 3>&
ang,
const double* center) {
112 throw std::runtime_error(
"set_instance_motion: call set_scene first");
114 throw std::runtime_error(
"set_instance_motion: instance index out of range");
115 auto&
in = sceneB_->instanceRef(i);
116 for (
int k = 0; k < 3; ++k) {
117 instLin_[3 * (std::size_t)i + k] = lin[k];
118 instAng_[3 * (std::size_t)i + k] =
ang[k];
120 instCen_[3 * (std::size_t)i + k] = center[k];
123 instCenPinned_[(std::size_t)i] = 1;
124 in.linVel = peclet::core::Vec3<double>{lin[0], lin[1], lin[2]};
125 in.angVel = peclet::core::Vec3<double>{
ang[0],
ang[1],
ang[2]};
127 peclet::core::Vec3<double>{instCen_[3 * (std::size_t)i + 0], instCen_[3 * (std::size_t)i + 1],
128 instCen_[3 * (std::size_t)i + 2]};
135 const std::array<double, 4>&
quat) {
137 throw std::runtime_error(
"set_instance_transform: call set_scene first");
139 throw std::runtime_error(
"set_instance_transform: instance index out of range");
140 auto&
in = sceneB_->instanceRef(i);
144 const bool centreTracked = instCenPinned_[(std::size_t)i] == 0;
145 in.transform.translation =
147 in.transform.rotation = peclet::core::Quat<double>{
quat[0],
quat[1],
quat[2],
quat[3]};
149 for (
int k = 0; k < 3; ++k)
151 in.center =
in.transform.translation;
159 throw std::runtime_error(
"rebuild_geometry: call set_scene first");
161 for (
int c = 0; c < 3; ++c) {
163 Kokkos::deep_copy(
uSave[c], C[c].u);
166 Kokkos::deep_copy(
mSave[c], C[c].mask);
170 Kokkos::deep_copy(
pSave, P_);
180 for (
int c = 0; c < 3; ++c)
181 Kokkos::deep_copy(C[c].u,
uSave[c]);
182 Kokkos::deep_copy(P_,
pSave);
193 for (
int c = 0; c < 3; ++c) {
194 if (
mOld[c].extent(0) != n_ || uBc_[c].extent(0) != n_)
198 Kokkos::parallel_for(
199 "peclet::flow::seed_fresh", Kokkos::RangePolicy<CCExec>(
space, 0, (
long)n_),
201 if (
mo(i) > 0.5 &&
mn(i) <= 0.5)
221 throw std::runtime_error(
"refresh_wall_velocity: call set_scene first");
223 throw std::runtime_error(
224 "refresh_wall_velocity: an instance TRANSFORM changed since the last geometry build -- "
225 "this call only refreshes the wall VELOCITY, so the run would continue on stale "
226 "geometry. Call rebuild_geometry() instead.");
258 const std::size_t n = (std::size_t)nx_ * ny_ * nz_;
259 std::vector<int> out(n, -1);
260 if (cutOwner_.extent(0) != n)
262 using HostV = Kokkos::View<int*, Kokkos::HostSpace, Kokkos::MemoryTraits<Kokkos::Unmanaged>>;
263 Kokkos::deep_copy(
HostV(out.data(), n), cutOwner_);
270 throw std::runtime_error(
"set_solid_from_scene: call set_scene first");
271 const std::size_t n = (std::size_t)nx_ * ny_ * nz_;
273 if (cutOwner_.extent(0) != n)
274 cutOwner_ = Kokkos::View<int*, CCMem>(
"peclet::flow::cutOwner", n);
275 auto own = cutOwner_;
276 const auto q = sceneQ_->view();
277 const int nx = nx_,
ny = ny_,
nz = nz_;
281 Kokkos::parallel_for(
284 const peclet::core::Vec3<double> p{
sm.a[0] +
sm.b[0] * (
double)(x + og.x),
290 const std::size_t
idx =
291 (std::size_t)x + (std::size_t)y *
nx + (std::size_t)z * (std::size_t)
nx *
ny;
304 imageOverlapCells_ = 0;
305 if (scenePeriodic_) {
306 namespace g = peclet::core::geom;
307 const auto hv = sceneB_->view();
308 const double lmin = std::fmin(sceneExtent_.x, std::fmin(sceneExtent_.y, sceneExtent_.z));
310 for (
int i = 0; i <
hv.instanceCount; ++i)
311 if (2.0 * g::instanceBound(
hv, i).r >
lmin)
314 g::PeriodicBox<double>
nobox{sceneExtent_.x, sceneExtent_.y, sceneExtent_.z,
false};
315 auto qnp = g::SceneQueryDevice<double, CCMem>::build(*sceneB_, sceneOrigin_, sceneExtent_,
317 const auto qv =
qnp.view();
319 Kokkos::parallel_reduce(
322 const peclet::core::Vec3<double> p{
sm.a[0] +
sm.b[0] * (
double)(x + og.x),
326 const std::size_t
idx =
327 (std::size_t)x + (std::size_t)y *
nx + (std::size_t)z * (std::size_t)
nx *
ny;
328 const double dp =
qv.evalOwner(p,
oi);
329 if ((dp < 0.0) != (
din(
idx) < 0.0))
334 imageOverlapCells_ = cnt;
337 "peclet.flow set_solid_from_scene WARNING: an instance is wider than the "
338 "periodic box, and the UNION of its periodic images changes the solid at "
339 "%ld cells on this rank. If it is a container wall (slab minus cavity), "
340 "keep the slab's half-extent at half the box plus the wall thickness -- "
341 "not more -- or the images refill the cavity. "
342 "periodic_image_overlap_cells() returns this count.\n",
353 const int nI = nInst_;
354 if (
nI <= 0 || cutOwner_.extent(0) != (std::size_t)nx_ * ny_ * nz_)
359 Kokkos::View<long*, CCMem> cnt(
"peclet::flow::movingCut",
nI);
360 auto own = cutOwner_;
362 const int nx = nx_,
ny = ny_,
nz = nz_;
364 for (
int c = 0; c < 3; ++c) {
365 if (C[c].idMap.extent(0) != n_)
367 Kokkos::View<const int*, CCMem>
idm = C[c].idMap;
368 Kokkos::parallel_for(
375 own((std::size_t)x + (std::size_t)y *
nx + (std::size_t)z * (std::size_t)
nx *
ny);
376 if (o >= 0 && o <
nI)
377 Kokkos::atomic_add(&cnt(o), (
long)1);
383 Kokkos::View<long*, CCMem> deg(
"peclet::flow::movingDegenerate",
nI);
386 for (
int c = 0; c < 3; ++c) {
387 const auto po = Grid::offset(c);
388 const double ox =
po.x, oy =
po.y, oz =
po.z;
389 Kokkos::parallel_for(
396 own((std::size_t)x + (std::size_t)y *
nx + (std::size_t)z * (std::size_t)
nx *
ny);
397 if (o >= 0 && o <
nI)
398 Kokkos::atomic_add(°(o), (
long)1);
403 auto h = Kokkos::create_mirror_view(cnt);
404 Kokkos::deep_copy(h, cnt);
405 auto hd = Kokkos::create_mirror_view(deg);
406 Kokkos::deep_copy(
hd, deg);
407 movingCutCells_.assign((std::size_t)
nI, 0);
408 movingDegenerate_.assign((std::size_t)
nI, 0);
409 for (
int i = 0; i <
nI; ++i) {
410 movingCutCells_[(std::size_t)i] = h(i);
411 movingDegenerate_[(std::size_t)i] =
hd(i);
413#ifdef PECLET_FLOW_MPI
416 std::vector<long> g((std::size_t)
nI, 0);
420 movingDegenerate_ = g;
427 for (
int i = 0; i <
nI; ++i) {
429 instLin_[3 * (std::size_t)i] != 0.0 || instLin_[3 * (std::size_t)i + 1] != 0.0 ||
430 instLin_[3 * (std::size_t)i + 2] != 0.0 || instAng_[3 * (std::size_t)i] != 0.0 ||
431 instAng_[3 * (std::size_t)i + 1] != 0.0 || instAng_[3 * (std::size_t)i + 2] != 0.0;
432 if (
moves && movingCutCells_[(std::size_t)i] == 0)
434 "peclet.flow set_solid_from_scene WARNING: instance %d has a velocity but owns "
435 "NO cut row of the momentum operator (sub-cell body?): the wall velocity "
436 "cannot enter and the body behaves as a STATIONARY wall. "
437 "moving_instance_cut_cells() returns the counts.\n",
439 if (
moves && movingDegenerate_[(std::size_t)i] > 0)
441 "peclet.flow set_solid_from_scene WARNING: instance %d has a velocity and its "
442 "surface passes EXACTLY through %ld staggered velocity points (a face on a "
443 "lattice plane). Such points are fluid to the mask and not ghosts to the "
444 "cut-cell fold, so the wall datum never enters there and the face acts as a "
445 "STATIONARY wall. Shift the body off the lattice (any fractional offset). "
446 "moving_instance_degenerate_points() returns the counts.\n",
447 i, movingDegenerate_[(std::size_t)i]);
453 return movingCutCells_;
458 return movingDegenerate_;
463 return imageOverlapCells_;
469 throw std::runtime_error(
"set_exact_crossings_from_scene: call set_scene first");
470 const std::size_t n = (std::size_t)nx_ * ny_ * nz_;
471 const auto q = sceneQ_->view();
472 const int nx = nx_,
ny = ny_,
nz = nz_;
476 for (
int c = 0; c < 3; ++c) {
481 const auto po = Grid::offset(c);
483 for (
int a = 0; a < 3; ++a) {
484 tEx_[c][a] =
CCField(
"tEx", n);
488 Kokkos::parallel_for(
493 const double px =
sm.a[0] +
sm.b[0] * ((
double)(x + og.x) + ox),
494 py =
sm.a[1] +
sm.b[1] * ((
double)(y + og.y) + oy),
495 pz =
sm.a[2] +
sm.b[2] * ((
double)(z + og.z) + oz);
496 const double dx =
aa == 0 ?
sm.b[0] : 0.0, dy =
aa == 1 ?
sm.b[1] : 0.0,
497 dz =
aa == 2 ?
sm.b[2] : 0.0;
498 const auto f = [&](
double s) {
499 return q.eval(peclet::core::Vec3<double>{px + s * dx, py + s * dy, pz + s * dz});
501 const double f0 = f(0.0),
f1 = f(1.0);
502 const std::size_t
idx =
503 (std::size_t)x + (std::size_t)y *
nx + (std::size_t)z * (std::size_t)
nx *
ny;
504 if ((
f0 < 0.0) == (
f1 < 0.0)) {
505 t(
idx) = Kokkos::Experimental::quiet_NaN_v<double>;
508 double lo = 0.0, hi = 1.0,
flo =
f0;
509 for (
int it = 0; it < 52; ++it) {
510 const double mid = 0.5 * (lo + hi);
511 const double fm = f(
mid);
512 if ((
fm < 0.0) == (
flo < 0.0)) {
519 t(
idx) = 0.5 * (lo + hi);
524 hasExactCross_ =
true;
525 sceneCrossings_ =
true;
530 namespace g = peclet::core::geom;
531 g::PeriodicBox<double> box{sceneExtent_.x, sceneExtent_.y, sceneExtent_.z, scenePeriodic_};
532 sceneQ_ = std::make_shared<g::SceneQueryDevice<double, CCMem>>(
533 g::SceneQueryDevice<double, CCMem>::build(*sceneB_, sceneOrigin_, sceneExtent_, box));
539 for (std::size_t k = 0; k < instLin_.size(); ++k)
540 if (instLin_[k] != 0.0 || instAng_[k] != 0.0)
546 const std::size_t m = (std::size_t)nInst_ * 3;
547 if (instCenD_.extent(0) != m) {
548 instCenD_ = Kokkos::View<double*, CCMem>(
"instCen", m);
549 instLinD_ = Kokkos::View<double*, CCMem>(
"instLin", m);
550 instAngD_ = Kokkos::View<double*, CCMem>(
"instAng", m);
555 Kokkos::View<const double*, Kokkos::HostSpace, Kokkos::MemoryTraits<Kokkos::Unmanaged>>;
556 Kokkos::deep_copy(instCenD_,
HostConst(instCen_.data(), m));
557 Kokkos::deep_copy(instLinD_,
HostConst(instLin_.data(), m));
558 Kokkos::deep_copy(instAngD_,
HostConst(instAng_.data(), m));
563 peclet::core::geom::InstanceMotionView<double>
mv;
564 mv.cen = instCenD_.data();
565 mv.lin = instLinD_.data();
566 mv.ang = instAngD_.data();
573 if (!hasScene_ || !hasMotion_) {
580 for (
int c = 0; c < 3; ++c) {
581 if (uBc_[c].extent(0) == n_)
582 Kokkos::deep_copy(uBc_[c], 0.0);
583 if (uwCell_[c].extent(0) == n_)
584 Kokkos::deep_copy(uwCell_[c], 0.0);
588 for (
int c = 0; c < 3; ++c) {
589 if (uBc_[c].extent(0) != n_)
591 if (uwCell_[c].extent(0) != n_)
592 uwCell_[c] =
CCField(
"uwCell", n_);
594 const auto q = sceneQ_->view();
597 const C3 e = e_, og = og_;
602 for (
int c = 0; c < 3; ++c) {
603 const auto po = Grid::offset(c);
604 const double ox =
po.x, oy =
po.y, oz =
po.z;
607 Kokkos::parallel_for(
608 "peclet::flow::wall_velocity_stag",
MD(
space, {0, 0, 0}, {e.x, e.y, e.z}),
610 const long i = (
long)
lx + (
long)
ly * e.x + (
long)
lz * (
long)e.x * e.y;
613 const peclet::core::Vec3<double>
grad{
620 const peclet::core::Vec3<double>
pi{
sx -
G + og.x, sy -
G + og.y, sz -
G + og.z};
621 const peclet::core::Vec3<double>
wi = peclet::core::geom::wallPoint(
pi, s0,
grad);
622 const peclet::core::Vec3<double> p{
sm.a[0] +
sm.b[0] *
pi.x,
sm.a[1] +
sm.b[1] *
pi.y,
623 sm.a[2] +
sm.b[2] *
pi.z};
624 const peclet::core::Vec3<double> w{
sm.a[0] +
sm.b[0] *
wi.x,
sm.a[1] +
sm.b[1] *
wi.y,
625 sm.a[2] +
sm.b[2] *
wi.z};
626 const peclet::core::Vec3<double> v =
627 peclet::core::geom::instanceVelocity(
mv, q.owner(p), w, q.box);
628 out(i) = cc == 0 ? v.x *
sm.velToInt[0]
629 : (cc == 1 ? v.y *
sm.velToInt[1] : v.z *
sm.velToInt[2]);
634 CCField ux = uwCell_[0],
uy = uwCell_[1],
uz = uwCell_[2];
635 Kokkos::parallel_for(
636 "peclet::flow::wall_velocity_cell",
MD(
space, {0, 0, 0}, {e.x, e.y, e.z}),
638 const long i = (
long)
lx + (
long)
ly * e.x + (
long)
lz * (
long)e.x * e.y;
639 const double sx =
lx, sy =
ly, sz =
lz;
641 const peclet::core::Vec3<double>
grad{
645 const peclet::core::Vec3<double>
pi{
sx -
G + og.x, sy -
G + og.y, sz -
G + og.z};
646 const peclet::core::Vec3<double>
wi = peclet::core::geom::wallPoint(
pi, s0,
grad);
647 const peclet::core::Vec3<double> p{
sm.a[0] +
sm.b[0] *
pi.x,
sm.a[1] +
sm.b[1] *
pi.y,
648 sm.a[2] +
sm.b[2] *
pi.z};
649 const peclet::core::Vec3<double> w{
sm.a[0] +
sm.b[0] *
wi.x,
sm.a[1] +
sm.b[1] *
wi.y,
650 sm.a[2] +
sm.b[2] *
wi.z};
651 const peclet::core::Vec3<double> v =
652 peclet::core::geom::instanceVelocity(
mv, q.owner(p), w, q.box);
653 ux(i) = v.x *
sm.velToInt[0];
654 uy(i) = v.y *
sm.velToInt[1];
655 uz(i) = v.z *
sm.velToInt[2];
667 for (
int c = 0; c < 3; ++c) {
675#ifdef PECLET_FLOW_MPI
686 if (!hasScene_ || !hasMotion_ || !wallFluxDiv_ || uwCell_[0].extent(0) != n_)
693 Kokkos::parallel_for(
694 "peclet::flow::wall_flux_div",
MD(
space, {
G,
G,
G}, {e.x -
G, e.y -
G, e.z -
G}),
696 const long sx = 1, sy = e.x, sz = (
long)e.x * e.y;
697 const long i = (
long)x + (
long)y * sy + (
long)z * sz;
698 const double ax = -(
oxv(i +
sx) -
oxv(i));
699 const double ay = -(
oyv(i + sy) -
oyv(i));
700 const double az = -(
ozv(i + sz) -
ozv(i));
701 d(i) +=
wx(i) * ax +
wy(i) * ay +
wz(i) *
az;
void setInstanceTransform(int i, const std::array< double, 3 > &translation, const std::array< double, 4 > &quat)
Move one instance (Layer 3 rung 4).
bool hasMovingInstance() const
True when at least one instance carries a nonzero velocity – i.e.
void fillDomainPeriodic(CCField f)
void refreshWallVelocity()
Re-derive ONLY the wall-velocity fields and the momentum operator that folds them in.
void checkMovingInstancesAreCut()
A MOVING instance whose surface produces no fractional face aperture has no path for its wall velocit...
int sceneInstanceCount() const
std::vector< long > movingInstanceDegeneratePoints() const
Per instance: staggered points where the sampled sdf is exactly zero (see the warning).
bool freshCellSeed() const
void rebuildGeometry()
Re-derive ALL geometry from the current instance transforms (Layer 3 rung 4): rebuild the accelerated...
void setSolidFromScene(bool cutcellPressure)
Sample the scene onto this rank's inner grid and install it as the solid, entirely on device – no nx*...
void exchangeExtRaw(CCField f)
Raw ghost fill of an extended-block field: the rank halo exchange under MPI, the periodic wrap single...
std::vector< int > getCutOwner() const
Per-inner-cell owning instance (Layer 3 rung 1), x-fastest, -1 where no scene has been sampled yet.
void addWallFluxDivergence(CCField d)
long periodicImageOverlapCells() const
Cells on this rank whose solid/fluid sign was set by a periodic IMAGE of an instance wider than the b...
void setWallFluxDivergence(bool on)
Rung 3 on/off.
void setInstanceMotion(int i, const std::array< double, 3 > &lin, const std::array< double, 3 > &ang, const double *center=nullptr)
Rigid-body motion of one scene instance (Layer 3 rung 2).
std::array< double, 3 > instanceCenter(int i) const
The resolved centre of rotation of instance i and whether it is pinned (explicit) or follows the body...
bool wallFluxDivergence() const
std::vector< long > movingInstanceCutCells() const
Per instance: cut rows of the momentum operator (all three components) at inner points this rank owns...
void seedFreshCells(CCField mOld[3])
FRESH CELLS: the points a moving body has just uncovered.
SceneMap sceneMap() const
void setSolidDevice(CCField din, bool cutcellPressure)
Device entry point (Layer 2): the inner SDF is ALREADY on device, so geometry never round-trips throu...
peclet::core::geom::InstanceMotionView< double > motionView() const
void setExactCrossingsFromScene()
EXACT wall crossings straight from the scene, on device, on every rank – the in-solver replacement fo...
void setScene(const std::vector< int > &nodeInts, const std::vector< double > &nodeReals, const std::vector< int > &instInts, const std::vector< double > &instReals, bool periodic=false)
Install an analytic scene from core's flat node/instance encoding (peclet/core/geom/scene_builder....
void setFreshCellSeed(bool on)
Fresh-cell policy for moving geometry.
bool instanceCenterPinned(int i) const
Kokkos::View< double *, CCMem > CCField
Kokkos::DefaultExecutionSpace CCExec
double ccSampleExt(CCConst sdf, C3 ext, double x, double y, double z)
Kokkos::View< const double *, CCMem > CCConst
The map between the solver's INDEX coordinates and the coordinate system the analytic scene is writte...
double org[3]
physical lower corner of the GLOBAL inner grid
bool physical
an extent was given (the scales are armed)
long cells[3]
the GLOBAL cell counts the extent spans
double ext[3]
physical extent of the GLOBAL inner grid