core 0.5.0
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
cut_cell.hpp
Go to the documentation of this file.
1// core — Robust-Scaled cut-cell Dirichlet operator on a BlockOctree.
2//
3// A faithful port of flow's ξ-polynomial sub-cell boundary scheme
4// (flow/src/cut_cell_ibm.hpp: poly_*, ibmFillEntry, ibmModifyStencil) onto the
5// cell-centered octree. Where the openness/aperture scheme (poisson.hpp) imposes a
6// *Neumann* wall (no flux through solid faces), this imposes a *Dirichlet* value
7// u = u_bc on the immersed boundary located at the true sub-cell distance ξ·h
8// (Shortley–Weller): for a cut cell whose neighbour in some direction is solid,
9// the 7-point stencil is modified by the boundary-distance polynomials, with
10// D_rescale row-scaling for the small-cell problem. This is the velocity-diffusion
11// / scalar-Dirichlet half of the cut-cell IBM (the user's "ξ-polynomial sub-cell
12// BC"); the cell fluid-volume fraction κ classifies solid/fluid/cut cells.
13//
14// Cut cells are assumed to have same-level face neighbours (the suite contract:
15// resolve the immersed boundary in a uniformly-finest band, so cut cells never sit
16// on a 2:1 interface — see docs/AMR.md). 3D (flow's 6-direction scheme).
17// Header-only, guarded by PECLET_CORE_HAVE_MORTON. Serial/host first.
18#ifndef PECLET_CORE_AMR_CUT_CELL_HPP
19#define PECLET_CORE_AMR_CUT_CELL_HPP
20
21#ifdef PECLET_CORE_HAVE_MORTON
22
23#include <algorithm>
24#include <array>
25#include <cmath>
26#include <vector>
27
29#include "peclet/core/amr/face_csr.hpp" // shared host+device assembled-operator row kernels
33
34namespace peclet::core::amr {
35
36// ---- boundary-distance polynomials (port of flow cut_cell_ibm.hpp, SCHEME 0,
37// double precision) ----
38namespace cc {
39// MORTON_HD (from face_csr.hpp): KOKKOS_FUNCTION on a Kokkos build, empty otherwise — so
40// buildCutStencil and these polynomials are device-callable under the AMR device assembler
41// (assembly.hpp) yet compile unchanged in the pure-C++ host oracle build. poly_abs replaces
42// std::fabs (host-only under a CUDA device pass); it is bit-identical to std::fabs for every value
43// buildCutStencil feeds it (the only difference, fabs(-0.0)=+0.0 vs −0.0, never occurs and would
44// not change the |·|< comparisons).
45MORTON_HD inline double poly_abs(double x) {
46 return x < 0.0 ? -x : x;
47}
48MORTON_HD inline double poly_D(double xi) {
49 return xi * (1.0 + xi);
50}
51MORTON_HD inline double poly_N_nb(double xi) {
52 return xi * (1.0 - xi);
53}
54MORTON_HD inline double poly_Nc(double xi) {
55 return 2.0 * (xi * xi - 1.0);
56}
57MORTON_HD inline double poly_Nbc(double) {
58 return 2.0;
59}
60MORTON_HD inline double poly_D_sandwich(double xm, double xp) {
61 return xm * xp;
62}
63MORTON_HD inline double poly_N_c_sandwich(double xm, double xp) {
64 return (xm + 1.0) * (xp - 1.0);
65}
66MORTON_HD inline double poly_Nbc_pp_sw(double xm, double xp) {
67 return (xm / (xm + xp)) * (1.0 + xm);
68}
69MORTON_HD inline double poly_Nbc_mp_sw(double xm, double xp) {
70 return (xp / (xm + xp)) * (1.0 - xp);
71}
72} // namespace cc
73
74template <unsigned Bits = 21u>
76 public:
77 static constexpr int Dim = 3;
79 using M = typename Octree::M;
80 using Code = typename Octree::Code;
81 using Coord = typename Octree::Coord;
82
83 // direction k: 0=+x,1=-x,2=+y,3=-y,4=+z,5=-z (flow order); OPP swaps sides.
84 static constexpr int OPP[6] = {1, 0, 3, 2, 5, 4};
85
86 void init(const Octree& t, Real h0, Vec<3> origin = Vec<3>{}) {
87 t_ = &t;
88 h0_ = h0;
89 origin_ = origin;
90 extResolve_ = nullptr; // distributed seam re-installed by the owner after every init
91 ghostLo_.clear();
92 ghostLv_.clear();
93 frameShift_ = {};
94 for (int d = 0; d < 3; ++d)
95 fineExt_[d] = static_cast<Coord>(t.brick()[d] * (Index(1) << t.lmax()));
96 }
97
98 // ---- distributed seam (docs/amr_distributed_flow.md): mirrors AmrPoisson's. build() forwards
99 // all three into the internal lap_ (which it re-inits), so the whole momentum geometry —
100 // regular C/F rows and the ξ-overlay band — resolves cross-block probes through the LeafHalo
101 // registry and evaluates the SDF in the GLOBAL frame (bit-identical samples on every rank).
103 void setResolver(ExtResolver r) { extResolve_ = std::move(r); }
107 void setGhosts(std::vector<std::array<long, 3>> lo, std::vector<unsigned> lv) {
108 ghostLo_ = std::move(lo);
109 ghostLv_ = std::move(lv);
110 }
111 Index numGhosts() const { return static_cast<Index>(ghostLv_.size()); }
112 void setFrameShift(const std::array<long, 3>& s) { frameShift_ = s; }
113
114 Index numLeaves() const { return t_->numLeaves(); }
115 bool isFluid(Index i) const { return fluid_[static_cast<std::size_t>(i)]; }
119 bool isCut(Index i) const { return cut_[static_cast<std::size_t>(i)] != 0; }
121 Index neighborOf(Index i, int k) const { return nb_[static_cast<std::size_t>(i) * 6 + k]; }
122 double kappa(Index i) const { return kappa_[static_cast<std::size_t>(i)]; }
123 double rhsScale(Index i) const { return rscale_[static_cast<std::size_t>(i)]; }
124
125 // ---- read-only views of the built geometry, for the device assembler (momentum_assembly.hpp)
126 // to stage to the device and reproduce build()/assembleOperator there. Mirrors how AmrPoisson
127 // exposes its openness to the device FV assembler.
128 const AmrPoisson<3, Bits>& lap() const {
129 return lap_;
130 }
131 const std::vector<double>& sdfCRaw() const {
132 return sdfC_;
133 }
134 const std::vector<Index>& nbRaw() const { return nb_; }
135 const std::vector<char>& fluidRaw() const { return fluid_; }
136 const std::vector<char>& cutRaw() const { return cut_; }
137 const std::vector<double>& acRaw() const { return AC_; }
138 const std::vector<double>& offRaw() const { return off_; }
139 const std::vector<double>& rscaleRaw() const { return rscale_; }
140 double idiag() const { return idiag_; }
141 double mu() const { return mu_; }
142 double beta() const { return mu_ / (h0_ * h0_); }
143 bool hasAdv() const { return hasAdv_; }
144 const std::vector<double>& advDiagRaw() const { return advDiag_; }
145 const std::vector<double>& advCoefRaw() const { return advCoef_; }
146 const std::vector<Index>& advStartRaw() const { return advStart_; }
147 const std::vector<Index>& advNbrRaw() const { return advNbr_; }
148
152 template <class SdfFn>
153 void build(SdfFn&& sdfFn, double idiag = 0.0, double beta = 1.0, int nsub = 4) {
154 const Index n = numLeaves();
155 const Index ng = numGhosts();
156 // C/F-aware Laplacian provider for regular (non-cut) fluid cells: an AmrPoisson
157 // with NO openness (α=1) -> the plain ∇² with 2:1-interface coeff(si,sj). The
158 // cut cells (finest, same-level) keep the ξ overlay below.
159 lap_.init(*t_, h0_);
160 if (extResolve_) { // distributed: forward the seam into the freshly-init'd lap_
161 lap_.setResolver(extResolve_);
162 lap_.setGhosts(ghostLo_, ghostLv_);
163 lap_.setFrameShift(frameShift_);
164 }
165 idiag_ = idiag;
166 mu_ = beta * h0_ * h0_; // physical μ (operator A = idiag·I − μ∇²)
167 sdfC_.assign(static_cast<std::size_t>(n + ng), 0.0);
168 kappa_.assign(static_cast<std::size_t>(n), 0.0);
169 fluid_.assign(static_cast<std::size_t>(n + ng), false);
170 cut_.assign(static_cast<std::size_t>(n), 0);
171 hasAdv_ = false; // advection FOU is rebuilt per step via buildAdvectionFou
172 AC_.assign(static_cast<std::size_t>(n), 1.0);
173 rscale_.assign(static_cast<std::size_t>(n), 1.0);
174 inhom_.assign(static_cast<std::size_t>(n), 0.0);
175 off_.assign(static_cast<std::size_t>(n) * 6, 0.0);
176 nb_.assign(static_cast<std::size_t>(n) * 6, -1);
177
178 // Pass 1: cell-centre SDF, κ (subsampled), fluid flag, neighbour indices.
179 for (Index i = 0; i < n; ++i) {
180 Vec<3> c = cellCenter(i);
181 double sc = sdfFn(c);
182 sdfC_[static_cast<std::size_t>(i)] = sc;
183 fluid_[static_cast<std::size_t>(i)] = sc > 0.0;
184 kappa_[static_cast<std::size_t>(i)] = volumeFraction(i, sdfFn, nsub);
185 for (int k = 0; k < 6; ++k)
186 nb_[static_cast<std::size_t>(i) * 6 + k] = neighbor(i, k);
187 }
188 // Ghost metadata (distributed): the SAME world SdfFn sampled at the ghost cell centre in
189 // the GLOBAL frame — bit-identical to the owner's Pass-1 sample of that leaf, so the
190 // ξ-classifications (and hence the CSR coefficients) agree across ranks exactly.
191 for (Index g = 0; g < ng; ++g) {
192 const auto& lo = ghostLo_[static_cast<std::size_t>(g)];
193 const double s = static_cast<double>(1L << ghostLv_[static_cast<std::size_t>(g)]);
194 Vec<3> c{};
195 for (int d = 0; d < 3; ++d)
196 c[d] = origin_[d] + (static_cast<double>(lo[d] + frameShift_[d]) + 0.5 * s) * h0_;
197 const double sc = sdfFn(c);
198 sdfC_[static_cast<std::size_t>(n + g)] = sc;
199 fluid_[static_cast<std::size_t>(n + g)] = sc > 0.0;
200 }
201
202 // Pass 2: build per-leaf stencil.
203 const double AC0 = idiag + 6.0 * beta;
204 for (Index i = 0; i < n; ++i) {
205 if (!fluid_[static_cast<std::size_t>(i)]) { // solid: identity row u=0
206 AC_[static_cast<std::size_t>(i)] = 1.0;
207 for (int k = 0; k < 6; ++k)
208 off_[static_cast<std::size_t>(i) * 6 + k] = 0.0;
209 continue;
210 }
211 double sdf_n[6];
212 bool anyGhost = false;
213 for (int k = 0; k < 6; ++k) {
214 Index j = nb_[static_cast<std::size_t>(i) * 6 + k];
215 sdf_n[k] = (j >= 0) ? sdfC_[static_cast<std::size_t>(j)] : -1.0; // missing => solid
216 if (sdf_n[k] < 0.0)
217 anyGhost = true;
218 }
219 cut_[static_cast<std::size_t>(i)] = anyGhost ? 1 : 0;
220 double AC = AC0, off[6];
221 for (int k = 0; k < 6; ++k)
222 off[k] = -beta;
223 double rscale = 1.0, inhomCoef = 0.0;
224 if (anyGhost)
225 buildCutStencil(sdfC_[static_cast<std::size_t>(i)], sdf_n, beta, AC0, AC, off, rscale,
226 inhomCoef);
227 AC_[static_cast<std::size_t>(i)] = AC;
228 for (int k = 0; k < 6; ++k)
229 off_[static_cast<std::size_t>(i) * 6 + k] = off[k];
230 rscale_[static_cast<std::size_t>(i)] = rscale;
231 inhom_[static_cast<std::size_t>(i)] = inhomCoef;
232 }
233 }
234
242 struct Assembled {
243 std::vector<double> diag;
244 std::vector<Index> start;
245 std::vector<Index> nbr;
246 std::vector<double> coef;
247 };
258 const Index n = numLeaves();
259 Assembled A;
260 A.diag.assign(static_cast<std::size_t>(n), 0.0);
261 A.start.assign(static_cast<std::size_t>(n) + 1, 0);
262 std::vector<std::vector<std::pair<Index, double>>> rows(static_cast<std::size_t>(n));
263 for (Index i = 0; i < n; ++i) {
264 const std::size_t s = static_cast<std::size_t>(i);
265 if (!fluid_[s]) { // solid: identity row (u = u_bc)
266 A.diag[s] = 1.0;
267 continue;
268 }
269 if (cut_[s]) { // ξ-overlay stencil (same-level neighbours)
270 A.diag[s] = AC_[s];
271 for (int k = 0; k < 6; ++k) {
272 double a = off_[s * 6 + k];
273 if (a == 0.0)
274 continue;
275 Index j = nb_[s * 6 + k];
276 if (j >= 0)
277 rows[s].emplace_back(j, a);
278 }
279 } else { // regular fluid: idiag·I − μ∇² with C/F-aware face coupling
280 const double invV = 1.0 / lap_.cellVolume(i);
281 double dsum = 0.0;
282 lap_.forEachFaceNeighbor(i, [&](Index j, Real c, int, double a) {
283 rows[s].emplace_back(j, -mu_ * invV * (a * c));
284 dsum += a * c;
285 });
286 A.diag[s] = idiag_ + mu_ * invV * dsum;
287 }
288 if (hasAdv_) { // implicit-FOU advection: diagonal (outflow) + CSR (inflow)
289 const double as = scaleAdvByRscale ? rscale_[s] : 1.0;
290 A.diag[s] += as * advDiag_[s];
291 for (std::size_t p = static_cast<std::size_t>(advStart_[s]);
292 p < static_cast<std::size_t>(advStart_[static_cast<std::size_t>(i) + 1]); ++p)
293 rows[s].emplace_back(advNbr_[p], as * advCoef_[p]);
294 }
295 }
296 for (Index i = 0; i < n; ++i)
297 A.start[static_cast<std::size_t>(i) + 1] =
298 A.start[static_cast<std::size_t>(i)] +
299 static_cast<Index>(rows[static_cast<std::size_t>(i)].size());
300 const Index nnz = A.start[static_cast<std::size_t>(n)];
301 A.nbr.resize(static_cast<std::size_t>(nnz));
302 A.coef.resize(static_cast<std::size_t>(nnz));
303 for (Index i = 0; i < n; ++i) {
304 Index k = A.start[static_cast<std::size_t>(i)];
305 for (auto& e : rows[static_cast<std::size_t>(i)]) {
306 A.nbr[static_cast<std::size_t>(k)] = e.first;
307 A.coef[static_cast<std::size_t>(k)] = e.second;
308 ++k;
309 }
310 }
311 return A;
312 }
313
314 // ---- Runtime operator: the assembled CSR applied with the SHARED face_csr.hpp row kernels — the
315 // exact same arithmetic the device runs (momentum.hpp), executed serially here. assembleOperator
316 // folds the implicit-FOU advection into the single CSR, so the FaceCsrOpT view sets hasAdv=false.
317 // The *Geometric variants above are the independent reference the oracle tests check this
318 // against.
319
323 v.n = numLeaves();
324 v.diag = HostArr<double>(A.diag.data());
325 v.coef = HostArr<double>(A.coef.data());
326 v.start = HostArr<Index>(A.start.data());
327 v.nbr = HostArr<Index>(A.nbr.data());
328 v.hasAdv = false; // advection already folded into the single CSR by assembleOperator
329 return v;
330 }
331
333 void applyOp(const std::vector<double>& u, std::vector<double>& out) const {
334 const Assembled A = assembleOperator();
335 const auto op = hostOp(A);
336 const Index n = numLeaves();
337 out.assign(static_cast<std::size_t>(n), 0.0);
338 const HostArr<double> uacc(u.data());
339 for (Index i = 0; i < n; ++i)
340 out[static_cast<std::size_t>(i)] = faceCsrApplyRow(op, i, uacc);
341 }
342
343 double residual(const std::vector<double>& u, const std::vector<double>& b,
344 std::vector<double>& res) const {
345 const Assembled A = assembleOperator();
346 const auto op = hostOp(A);
347 const Index n = numLeaves();
348 res.assign(static_cast<std::size_t>(n), 0.0);
349 const HostArr<double> uacc(u.data());
350 double s = 0.0;
351 for (Index i = 0; i < n; ++i) {
352 double r = b[static_cast<std::size_t>(i)] - faceCsrApplyRow(op, i, uacc);
353 res[static_cast<std::size_t>(i)] = r;
354 if (fluid_[static_cast<std::size_t>(i)])
355 s += r * r;
356 }
357 return std::sqrt(s);
358 }
359
363 void gaussSeidel(std::vector<double>& u, const std::vector<double>& b, int sweeps) const {
364 const Assembled A = assembleOperator();
365 const auto op = hostOp(A);
366 const Index n = numLeaves();
367 const HostArr<double> uacc(u.data());
368 for (int s = 0; s < sweeps; ++s)
369 for (Index i = 0; i < n; ++i) {
370 double off, d;
371 faceCsrOffDiag(op, i, uacc, off, d);
372 u[static_cast<std::size_t>(i)] = faceCsrPointUpdate(b[static_cast<std::size_t>(i)], off, d,
373 u[static_cast<std::size_t>(i)], 1.0);
374 }
375 }
376
383 void applyOpGeometric(const std::vector<double>& u, std::vector<double>& out) const {
384 const Index n = numLeaves();
385 out.assign(static_cast<std::size_t>(n), 0.0);
386 std::vector<double> Lu;
387 lap_.applyLaplacian(u, Lu); // C/F-aware ∇² (α=1); used only for regular fluid cells
388 for (Index i = 0; i < n; ++i) {
389 const std::size_t s = static_cast<std::size_t>(i);
390 if (!fluid_[s]) {
391 out[s] = u[s];
392 } else if (cut_[s]) {
393 double acc = AC_[s] * u[s];
394 for (int k = 0; k < 6; ++k) {
395 double a = off_[s * 6 + k];
396 if (a == 0.0)
397 continue;
398 Index j = nb_[s * 6 + k];
399 if (j >= 0)
400 acc += a * u[static_cast<std::size_t>(j)];
401 }
402 out[s] = acc;
403 } else {
404 out[s] = idiag_ * u[s] - mu_ * Lu[s]; // regular fluid (C/F-consistent)
405 }
406 if (hasAdv_ && fluid_[s])
407 out[s] += advApply(i, u); // implicit FOU advection
408 }
409 }
410
423 void buildAdvectionFou(const std::array<std::vector<double>, 3>& uadv, double rho,
424 const std::vector<double>& uf, const std::vector<Index>& faceStart,
425 bool useFace) {
426 const Index n = numLeaves();
427 advDiag_.assign(static_cast<std::size_t>(n), 0.0);
428 advStart_.assign(static_cast<std::size_t>(n) + 1, 0);
429 hasAdv_ = true;
430 auto velOutOf = [&](Index i, Index j, int axis, int dir, Index slot) {
431 return useFace ? dir * uf[static_cast<std::size_t>(slot)]
432 : dir * 0.5 *
433 (uadv[axis][static_cast<std::size_t>(i)] +
434 uadv[axis][static_cast<std::size_t>(j)]);
435 };
436 // pass 1: count inflow (off-diagonal) fluid faces per cell.
437 for (Index i = 0; i < n; ++i) {
438 if (!fluid_[static_cast<std::size_t>(i)])
439 continue;
440 int cnt = 0;
441 Index s = useFace ? faceStart[static_cast<std::size_t>(i)] : 0;
442 lap_.forEachFaceFull(i, [&](Index j, int axis, int dir, double, double, double) {
443 if (fluid_[static_cast<std::size_t>(j)] && velOutOf(i, j, axis, dir, s) < 0.0)
444 ++cnt;
445 ++s;
446 });
447 advStart_[static_cast<std::size_t>(i) + 1] = cnt;
448 }
449 for (Index i = 0; i < n; ++i)
450 advStart_[static_cast<std::size_t>(i) + 1] += advStart_[static_cast<std::size_t>(i)];
451 advNbr_.assign(advStart_[static_cast<std::size_t>(n)], -1);
452 advCoef_.assign(advStart_[static_cast<std::size_t>(n)], 0.0);
453 // pass 2: fill diagonal (outflow) + CSR off-diagonals (inflow).
454 for (Index i = 0; i < n; ++i) {
455 if (!fluid_[static_cast<std::size_t>(i)])
456 continue;
457 const double Vi = lap_.cellVolume(i);
458 std::size_t pos = static_cast<std::size_t>(advStart_[static_cast<std::size_t>(i)]);
459 Index s = useFace ? faceStart[static_cast<std::size_t>(i)] : 0;
460 lap_.forEachFaceFull(i, [&](Index j, int axis, int dir, double area, double, double) {
461 const Index slot = s++;
462 if (!fluid_[static_cast<std::size_t>(j)])
463 return;
464 double velOut = velOutOf(i, j, axis, dir, slot);
465 double w = rho * area * velOut / Vi;
466 if (velOut < 0.0) { // inflow → couple to upstream neighbour j (matches pass 1)
467 advNbr_[pos] = j;
468 advCoef_[pos] = w;
469 ++pos;
470 } else {
471 advDiag_[static_cast<std::size_t>(i)] += w; // outflow → diagonal
472 }
473 });
474 }
475 }
476
479 std::vector<double> makeRhs(const std::vector<double>& src, double u_bc) const {
480 const Index n = numLeaves();
481 std::vector<double> b(static_cast<std::size_t>(n), 0.0);
482 for (Index i = 0; i < n; ++i) {
483 if (!fluid_[static_cast<std::size_t>(i)]) {
484 b[static_cast<std::size_t>(i)] = u_bc; // solid held at u_bc
485 continue;
486 }
487 b[static_cast<std::size_t>(i)] =
488 src[static_cast<std::size_t>(i)] * rscale_[static_cast<std::size_t>(i)] +
489 inhom_[static_cast<std::size_t>(i)] * u_bc;
490 }
491 return b;
492 }
493
494 double residualGeometric(const std::vector<double>& u, const std::vector<double>& b,
495 std::vector<double>& res) const {
496 applyOpGeometric(u, res);
497 double s = 0.0;
498 const Index n = numLeaves();
499 for (Index i = 0; i < n; ++i) {
500 double r = b[static_cast<std::size_t>(i)] - res[static_cast<std::size_t>(i)];
501 res[static_cast<std::size_t>(i)] = r;
502 if (fluid_[static_cast<std::size_t>(i)])
503 s += r * r;
504 }
505 return std::sqrt(s);
506 }
507
508 void gaussSeidelGeometric(std::vector<double>& u, const std::vector<double>& b,
509 int sweeps) const {
510 const Index n = numLeaves();
511 for (int s = 0; s < sweeps; ++s)
512 for (Index i = 0; i < n; ++i) {
513 const std::size_t si = static_cast<std::size_t>(i);
514 if (!fluid_[si]) {
515 u[si] = b[si];
516 } else if (cut_[si]) { // ξ-overlay stencil (same-level neighbours)
517 double sum = b[si];
518 for (int k = 0; k < 6; ++k) {
519 double a = off_[si * 6 + k];
520 if (a == 0.0)
521 continue;
522 Index j = nb_[si * 6 + k];
523 if (j >= 0)
524 sum -= a * u[static_cast<std::size_t>(j)];
525 }
526 double d = AC_[si];
527 if (hasAdv_) {
528 sum -= advOffSum(i, u);
529 d += advDiag_[si];
530 }
531 if (d != 0.0)
532 u[si] = sum / d;
533 } else { // regular fluid: idiag·u − μ∇² with C/F-aware face coupling
534 double offsum = 0.0, dsum = 0.0;
535 lap_.forEachFaceNeighbor(i, [&](Index j, Real c, int, double) {
536 offsum += c * u[static_cast<std::size_t>(j)];
537 dsum += c;
538 });
539 double Vi = lap_.cellVolume(i);
540 double diagA = idiag_ + mu_ * dsum / Vi;
541 double sum = b[si] + mu_ * offsum / Vi;
542 if (hasAdv_) {
543 sum -= advOffSum(i, u);
544 diagA += advDiag_[si];
545 }
546 u[si] = sum / diagA;
547 }
548 }
549 }
550
551 public:
555 double fouApply(Index i, const std::vector<double>& field) const {
556 return advDiag_[static_cast<std::size_t>(i)] * field[static_cast<std::size_t>(i)] +
557 advOffSum(i, field);
558 }
559 bool hasAdvection() const { return hasAdv_; }
560
561 private:
562 // Σ_csr coef·field[nbr] — the implicit-FOU advection off-diagonal coupling.
563 double advOffSum(Index i, const std::vector<double>& field) const {
564 double s = 0.0;
565 for (std::size_t p = static_cast<std::size_t>(advStart_[static_cast<std::size_t>(i)]);
566 p < static_cast<std::size_t>(advStart_[static_cast<std::size_t>(i) + 1]); ++p)
567 s += advCoef_[p] * field[static_cast<std::size_t>(advNbr_[p])];
568 return s;
569 }
570 double advApply(Index i, const std::vector<double>& u) const { return fouApply(i, u); }
571
572 public:
573 // Port of ibmFillEntry<0> + ibmModifyStencil for one cut cell (Dirichlet). Public + MORTON_HD so
574 // the device assembler (momentum_assembly.hpp) runs the SAME per-cell stencil build on device.
575 MORTON_HD static void buildCutStencil(double sdf_c, const double sdf_n[6], double beta,
576 double AC0, double& ACout, double off[6], double& rscaleOut,
577 double& inhomOut) {
578 bool ghost[6];
579 double xi[6], D[6];
580 for (int k = 0; k < 6; ++k) {
581 if (sdf_n[k] < 0.0) {
582 ghost[k] = true;
583 double th = sdf_c / (sdf_c - sdf_n[k]);
584 th = th < 1e-4 ? 1e-4 : (th > 1.0 ? 1.0 : th);
585 xi[k] = th;
586 D[k] = cc::poly_D(th);
587 } else {
588 ghost[k] = false;
589 xi[k] = 1.0;
590 D[k] = 1e9;
591 }
592 }
593 bool sand[3] = {ghost[0] && ghost[1], ghost[2] && ghost[3], ghost[4] && ghost[5]};
594 double Dsand[3] = {0, 0, 0};
595 for (int a = 0; a < 3; ++a)
596 if (sand[a])
597 Dsand[a] = cc::poly_D_sandwich(xi[2 * a + 1], xi[2 * a]);
598 double minAbs = 1e30, descale = 1.0;
599 auto upd = [&](double v) {
600 if (cc::poly_abs(v) < minAbs) {
602 descale = v;
603 }
604 };
605 for (int a = 0; a < 3; ++a) {
606 if (sand[a])
607 upd(Dsand[a]);
608 else {
609 if (ghost[2 * a])
610 upd(D[2 * a]);
611 if (ghost[2 * a + 1])
612 upd(D[2 * a + 1]);
613 }
614 }
615 double K[6] = {0}, Mf[6] = {1, 1, 1, 1, 1, 1}, X[6] = {0}, Nbc[6] = {0},
616 R[6] = {1, 1, 1, 1, 1, 1};
617 for (int a = 0; a < 3; ++a) {
618 int km = 2 * a + 1, kp = 2 * a;
619 double Daxis = sand[a] ? Dsand[a] : (ghost[kp] ? D[kp] : (ghost[km] ? D[km] : descale));
620 double r = descale / Daxis;
621 if (cc::poly_abs(Daxis) < 1e-9)
622 r = 1.0;
623 R[kp] = R[km] = r;
624 if (sand[a]) {
625 K[kp] = cc::poly_N_c_sandwich(xi[km], xi[kp]) * r;
626 K[km] = cc::poly_N_c_sandwich(xi[kp], xi[km]) * r;
629 Mf[kp] = Mf[km] = 0.0;
630 } else {
631 for (int side = 0; side < 2; ++side) {
632 int kk = side == 0 ? kp : km;
633 if (ghost[kk]) {
634 K[kk] = cc::poly_Nc(xi[kk]) * r;
635 X[kk] = cc::poly_N_nb(xi[kk]) * r;
636 Nbc[kk] = cc::poly_Nbc(xi[kk]) * r;
637 Mf[kk] = 0.0;
638 } else {
639 K[kk] = 0.0;
640 Mf[kk] = 1.0;
641 X[kk] = 0.0;
642 Nbc[kk] = 0.0;
643 }
644 }
645 }
646 }
647 // ibmModifyStencil (orig off-diagonal = -beta for every direction). OPP is a function-local
648 // constexpr (not the static member) so the runtime index mod[OPP[k]] is device-safe under CUDA.
649 constexpr int OPP_[6] = {1, 0, 3, 2, 5, 4};
650 double aC = AC0 * descale, mod[6] = {0, 0, 0, 0, 0, 0}, inhom = 0.0;
651 for (int k = 0; k < 6; ++k) {
652 double vnb = -beta;
653 aC += vnb * K[k];
654 inhom += Nbc[k] * vnb;
655 mod[k] += vnb * (descale * Mf[k] - 1.0);
656 mod[OPP_[k]] += vnb * X[k];
657 }
658 ACout = aC;
659 for (int k = 0; k < 6; ++k)
660 off[k] = -beta + mod[k];
662 inhomOut = inhom;
663 }
664
665 private:
666 Vec<3> cellCenter(Index i) const {
667 auto b = t_->bounds(i);
668 double s = static_cast<double>(Index(1) << t_->level(i));
669 Vec<3> c{};
670 for (int d = 0; d < 3; ++d)
671 c[d] = origin_[d] +
672 (static_cast<double>(static_cast<long>(b[0][d]) + frameShift_[d]) + 0.5 * s) * h0_;
673 return c;
674 }
675
676 template <class SdfFn>
677 double volumeFraction(Index i, SdfFn&& sdfFn, int nsub) const {
678 auto b = t_->bounds(i);
679 double s = static_cast<double>(Index(1) << t_->level(i));
680 double w = s * h0_;
681 int inside = 0, total = nsub * nsub * nsub;
682 Vec<3> base{};
683 for (int d = 0; d < 3; ++d)
684 base[d] =
685 origin_[d] + static_cast<double>(static_cast<long>(b[0][d]) + frameShift_[d]) * h0_;
686 for (int a = 0; a < nsub; ++a)
687 for (int bb = 0; bb < nsub; ++bb)
688 for (int cc2 = 0; cc2 < nsub; ++cc2) {
689 Vec<3> p{base[0] + (a + 0.5) / nsub * w, base[1] + (bb + 0.5) / nsub * w,
690 base[2] + (cc2 + 0.5) / nsub * w};
691 if (sdfFn(p) > 0.0)
692 ++inside;
693 }
694 return static_cast<double>(inside) / total;
695 }
696
700 Index neighbor(Index i, int k) const {
701 return lap_.periodicNeighbor(i, k / 2, (k % 2 == 0) ? +1 : -1);
702 }
703
704 const Octree* t_ = nullptr;
705 Real h0_ = 1.0;
706 Vec<3> origin_{};
707 std::array<Coord, 3> fineExt_{};
708 ExtResolver extResolve_; // distributed seam (forwarded into lap_ by build)
709 std::vector<std::array<long, 3>> ghostLo_; // ghost slot → block-local lo (longs)
710 std::vector<unsigned> ghostLv_; // ghost slot → covering-leaf level
711 std::array<long, 3> frameShift_{}; // block global fine origin (0 single-rank)
712 std::vector<double> sdfC_, kappa_, AC_, rscale_, inhom_, off_;
713 std::vector<Index> nb_;
714 std::vector<char> fluid_, cut_;
715 AmrPoisson<3, Bits> lap_; // C/F-aware ∇² provider for regular fluid cells (α=1)
716 double idiag_ = 0.0, mu_ = 1.0;
717 std::vector<double> advDiag_, advCoef_; // implicit-FOU advection (rebuilt per step)
718 std::vector<Index> advStart_, advNbr_; // CSR off-diagonals (C/F-conservative)
719 bool hasAdv_ = false;
720};
721
722} // namespace peclet::core::amr
723
724#endif // PECLET_CORE_HAVE_MORTON
725#endif // PECLET_CORE_AMR_CUT_CELL_HPP
#define MORTON_HD
double kappa(Index i) const
Definition cut_cell.hpp:122
double fouApply(Index i, const std::vector< double > &field) const
The implicit-FOU advection operator applied to field at leaf i: advDiag·field_i + Σ_csr coef·field_nb...
Definition cut_cell.hpp:555
const std::vector< Index > & nbRaw() const
n·6 periodic face-neighbour indices
Definition cut_cell.hpp:134
const std::vector< double > & advCoefRaw() const
Definition cut_cell.hpp:145
void applyOp(const std::vector< double > &u, std::vector< double > &out) const
out = A u, via the shared kernel over the assembled CSR (== device applyMom arithmetic).
Definition cut_cell.hpp:333
void setResolver(ExtResolver r)
Definition cut_cell.hpp:103
void gaussSeidelGeometric(std::vector< double > &u, const std::vector< double > &b, int sweeps) const
Definition cut_cell.hpp:508
double rhsScale(Index i) const
Definition cut_cell.hpp:123
static constexpr int Dim
Definition cut_cell.hpp:77
const std::vector< double > & acRaw() const
Definition cut_cell.hpp:137
void init(const Octree &t, Real h0, Vec< 3 > origin=Vec< 3 >{})
Definition cut_cell.hpp:86
const std::vector< double > & rscaleRaw() const
Definition cut_cell.hpp:139
double residualGeometric(const std::vector< double > &u, const std::vector< double > &b, std::vector< double > &res) const
Definition cut_cell.hpp:494
const std::vector< double > & sdfCRaw() const
per-cell SDF sample (build Pass 1)
Definition cut_cell.hpp:131
FaceCsrOpT< HostArr< double >, HostArr< Index > > hostOp(const Assembled &A) const
View a host Assembled as a backend-agnostic FaceCsrOpT for the shared row kernels.
Definition cut_cell.hpp:321
const std::vector< double > & advDiagRaw() const
Definition cut_cell.hpp:144
void gaussSeidel(std::vector< double > &u, const std::vector< double > &b, int sweeps) const
sweeps true serial Gauss–Seidel sweeps (ω=1, in place) over the assembled CSR using the shared point-...
Definition cut_cell.hpp:363
double residual(const std::vector< double > &u, const std::vector< double > &b, std::vector< double > &res) const
Definition cut_cell.hpp:343
const std::vector< char > & cutRaw() const
Definition cut_cell.hpp:136
typename AmrPoisson< 3, Bits >::ExtResolver ExtResolver
Definition cut_cell.hpp:102
Assembled assembleOperator(bool scaleAdvByRscale=false) const
scaleAdvByRscale (default false ⇒ reproduces applyOp/gaussSeidel exactly, for the matvec test): when ...
Definition cut_cell.hpp:257
void buildAdvectionFou(const std::array< std::vector< double >, 3 > &uadv, double rho, const std::vector< double > &uf, const std::vector< Index > &faceStart, bool useFace)
Build the implicit first-order-upwind advection operator from a (lagged) advecting velocity field uad...
Definition cut_cell.hpp:423
const std::vector< Index > & advNbrRaw() const
Definition cut_cell.hpp:147
typename Octree::Coord Coord
Definition cut_cell.hpp:81
std::vector< double > makeRhs(const std::vector< double > &src, double u_bc) const
Effective RHS for source src (≈ -h^2 f at cell centres) and wall value u_bc: row-scaled by D_rescale ...
Definition cut_cell.hpp:479
void applyOpGeometric(const std::vector< double > &u, std::vector< double > &out) const
Geometric operator apply (walks the octree live) — the INDEPENDENT reference encoding,...
Definition cut_cell.hpp:383
bool isFluid(Index i) const
Definition cut_cell.hpp:115
BlockOctree< 3, Bits > Octree
Definition cut_cell.hpp:78
double beta() const
buildCutStencil's β (= mu_/h0²)
Definition cut_cell.hpp:142
static MORTON_HD void buildCutStencil(double sdf_c, const double sdf_n[6], double beta, double AC0, double &ACout, double off[6], double &rscaleOut, double &inhomOut)
Definition cut_cell.hpp:575
Index neighborOf(Index i, int k) const
Periodic face neighbour of leaf i in direction k (0=+x,1=-x,2=+y,3=-y,4=+z,5=-z).
Definition cut_cell.hpp:121
bool isCut(Index i) const
True for a cut cell: a fluid cell with at least one solid face neighbour (the row-scaled ξ-overlay ba...
Definition cut_cell.hpp:119
void setGhosts(std::vector< std::array< long, 3 > > lo, std::vector< unsigned > lv)
Ghost slots [n, n+nGhost): block-local lo (longs) + covering-leaf level.
Definition cut_cell.hpp:107
const std::vector< Index > & advStartRaw() const
Definition cut_cell.hpp:146
typename Octree::Code Code
Definition cut_cell.hpp:80
void build(SdfFn &&sdfFn, double idiag=0.0, double beta=1.0, int nsub=4)
Build the cut-cell stencils from an SDF callable sdfFn(worldPoint) (>0 fluid, <0 solid).
Definition cut_cell.hpp:153
const std::vector< char > & fluidRaw() const
Definition cut_cell.hpp:135
const AmrPoisson< 3, Bits > & lap() const
α=1 C/F ∇² geometry for regular cells
Definition cut_cell.hpp:128
void setFrameShift(const std::array< long, 3 > &s)
Definition cut_cell.hpp:112
const std::vector< double > & offRaw() const
n·6 ξ-overlay off-diagonals
Definition cut_cell.hpp:138
static constexpr int OPP[6]
Definition cut_cell.hpp:84
Cell-centered FV Poisson operator on one (periodic) block octree.
Definition poisson.hpp:44
void setGhosts(std::vector< std::array< long, Dim > > lo, std::vector< unsigned > lv)
Declare the ghost slots [n, n+nGhost): block-local lo corner (longs — ghosts lie outside the block) a...
Definition poisson.hpp:82
void forEachFaceNeighbor(Index i, Fn &&fn) const
Visit each face neighbour of leaf i: fn(neighbourSlot, coeff, axis, alpha) where coeff = A_f / d_f (p...
Definition poisson.hpp:258
std::function< Index(const std::array< long, Dim > &)> ExtResolver
Route probes that exit the block through an external resolver (the LeafHalo registry): fn(blockLocalP...
Definition poisson.hpp:76
void setFrameShift(const std::array< long, Dim > &s)
Distributed frame shift: this block's global fine origin.
Definition poisson.hpp:92
void init(const Octree &t, Real h0)
Definition poisson.hpp:54
Index periodicNeighbor(Index i, int axis, int dir) const
Periodic face neighbour leaf (covering the cell just across the face).
Definition poisson.hpp:358
void setResolver(ExtResolver r)
Definition poisson.hpp:77
void applyLaplacian(const std::vector< double > &u, std::vector< double > &out) const
out = L u (periodic FV Laplacian).
Definition poisson.hpp:506
void forEachFaceFull(Index i, Fn &&fn) const
Like forEachFaceNeighbor but exposes geometry for a consistent FV divergence/gradient: fn(neighbour,...
Definition poisson.hpp:313
Real cellVolume(Index i) const
Definition poisson.hpp:246
Per-block adaptive octree over block-local Morton codes.
morton::Morton< Dim, Bits > M
typename M::coord_type Coord
unsigned level(Index i) const
typename M::code_type Code
std::array< std::array< Coord, Dim >, 2 > bounds(Index i) const
Inclusive integer bounds [lo, hi] of leaf i in fine units.
MORTON_HD double poly_Nc(double xi)
Definition cut_cell.hpp:54
MORTON_HD double poly_N_nb(double xi)
Definition cut_cell.hpp:51
MORTON_HD double poly_abs(double x)
Definition cut_cell.hpp:45
MORTON_HD double poly_D_sandwich(double xm, double xp)
Definition cut_cell.hpp:60
MORTON_HD double poly_N_c_sandwich(double xm, double xp)
Definition cut_cell.hpp:63
MORTON_HD double poly_D(double xi)
Definition cut_cell.hpp:48
MORTON_HD double poly_Nbc_mp_sw(double xm, double xp)
Definition cut_cell.hpp:69
MORTON_HD double poly_Nbc(double)
Definition cut_cell.hpp:57
MORTON_HD double poly_Nbc_pp_sw(double xm, double xp)
Definition cut_cell.hpp:66
MORTON_HD void faceCsrOffDiag(const Op &op, Index i, const U &u, double &off, double &d)
Off-diagonal sum and the (advection-inclusive) diagonal for the point smoothers: out off = Σ coef·u[n...
Definition face_csr.hpp:78
std::vector< std::array< double, Dim > > transferFieldGradients(const BlockOctree< Dim, Bits > &oldT, const std::vector< double > &oldF)
Per-old-leaf minmod prolongation gradients (per fine-coordinate unit) — transferField's stencil,...
Definition adapt.hpp:53
MORTON_HD double faceCsrPointUpdate(double b_i, double off, double d, double uOld, double omega)
The damped point update used by both Jacobi and (multicolour) Gauss–Seidel: returns the new u_i given...
Definition face_csr.hpp:93
MORTON_HD double faceCsrApplyRow(const Op &op, Index i, const U &u)
(A u)_i — one assembled-operator row.
Definition face_csr.hpp:62
std::array< Real, Dim > Vec
Multi-dimensional real vector.
Definition types.hpp:26
double Real
Default host floating type. Device kernels may use float; conversions happen at the boundary.
Definition types.hpp:18
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15
The assembled linear operator A as a per-cell diagonal + face CSR: (A u)_i = diag[i]·u_i + Σ_{k∈[star...
Definition cut_cell.hpp:242
std::vector< Index > start
CSR row offsets, size n+1.
Definition cut_cell.hpp:244
std::vector< double > diag
size n
Definition cut_cell.hpp:243
std::vector< double > coef
off-diagonal coefficient, size nnz
Definition cut_cell.hpp:246
std::vector< Index > nbr
neighbour leaf per off-diagonal, size nnz
Definition cut_cell.hpp:245
A backend-agnostic view of an assembled face-CSR operator.
Definition face_csr.hpp:51
A uniform accessor over a raw host array, giving it the operator()(i) that Kokkos::View has,...
Definition face_csr.hpp:40