core
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 for (int d = 0; d < 3; ++d)
91 fineExt_[d] = static_cast<Coord>(t.brick()[d] * (Index(1) << t.lmax()));
92 }
93
94 Index numLeaves() const { return t_->numLeaves(); }
95 bool isFluid(Index i) const { return fluid_[static_cast<std::size_t>(i)]; }
99 bool isCut(Index i) const { return cut_[static_cast<std::size_t>(i)] != 0; }
101 Index neighborOf(Index i, int k) const { return nb_[static_cast<std::size_t>(i) * 6 + k]; }
102 double kappa(Index i) const { return kappa_[static_cast<std::size_t>(i)]; }
103 double rhsScale(Index i) const { return rscale_[static_cast<std::size_t>(i)]; }
104
105 // ---- read-only views of the built geometry, for the device assembler (momentum_assembly.hpp)
106 // to stage to the device and reproduce build()/assembleOperator there. Mirrors how AmrPoisson
107 // exposes its openness to the device FV assembler.
108 const AmrPoisson<3, Bits>& lap() const {
109 return lap_;
110 }
111 const std::vector<double>& sdfCRaw() const {
112 return sdfC_;
113 }
114 const std::vector<Index>& nbRaw() const { return nb_; }
115 const std::vector<char>& fluidRaw() const { return fluid_; }
116 const std::vector<char>& cutRaw() const { return cut_; }
117 const std::vector<double>& acRaw() const { return AC_; }
118 const std::vector<double>& offRaw() const { return off_; }
119 const std::vector<double>& rscaleRaw() const { return rscale_; }
120 double idiag() const { return idiag_; }
121 double mu() const { return mu_; }
122 double beta() const { return mu_ / (h0_ * h0_); }
123 bool hasAdv() const { return hasAdv_; }
124 const std::vector<double>& advDiagRaw() const { return advDiag_; }
125 const std::vector<double>& advCoefRaw() const { return advCoef_; }
126 const std::vector<Index>& advStartRaw() const { return advStart_; }
127 const std::vector<Index>& advNbrRaw() const { return advNbr_; }
128
132 template <class SdfFn>
133 void build(SdfFn&& sdfFn, double idiag = 0.0, double beta = 1.0, int nsub = 4) {
134 const Index n = numLeaves();
135 // C/F-aware Laplacian provider for regular (non-cut) fluid cells: an AmrPoisson
136 // with NO openness (α=1) -> the plain ∇² with 2:1-interface coeff(si,sj). The
137 // cut cells (finest, same-level) keep the ξ overlay below.
138 lap_.init(*t_, h0_);
139 idiag_ = idiag;
140 mu_ = beta * h0_ * h0_; // physical μ (operator A = idiag·I − μ∇²)
141 sdfC_.assign(static_cast<std::size_t>(n), 0.0);
142 kappa_.assign(static_cast<std::size_t>(n), 0.0);
143 fluid_.assign(static_cast<std::size_t>(n), false);
144 cut_.assign(static_cast<std::size_t>(n), 0);
145 hasAdv_ = false; // advection FOU is rebuilt per step via buildAdvectionFou
146 AC_.assign(static_cast<std::size_t>(n), 1.0);
147 rscale_.assign(static_cast<std::size_t>(n), 1.0);
148 inhom_.assign(static_cast<std::size_t>(n), 0.0);
149 off_.assign(static_cast<std::size_t>(n) * 6, 0.0);
150 nb_.assign(static_cast<std::size_t>(n) * 6, -1);
151
152 // Pass 1: cell-centre SDF, κ (subsampled), fluid flag, neighbour indices.
153 for (Index i = 0; i < n; ++i) {
154 Vec<3> c = cellCenter(i);
155 double sc = sdfFn(c);
156 sdfC_[static_cast<std::size_t>(i)] = sc;
157 fluid_[static_cast<std::size_t>(i)] = sc > 0.0;
158 kappa_[static_cast<std::size_t>(i)] = volumeFraction(i, sdfFn, nsub);
159 for (int k = 0; k < 6; ++k)
160 nb_[static_cast<std::size_t>(i) * 6 + k] = neighbor(i, k);
161 }
162
163 // Pass 2: build per-leaf stencil.
164 const double AC0 = idiag + 6.0 * beta;
165 for (Index i = 0; i < n; ++i) {
166 if (!fluid_[static_cast<std::size_t>(i)]) { // solid: identity row u=0
167 AC_[static_cast<std::size_t>(i)] = 1.0;
168 for (int k = 0; k < 6; ++k)
169 off_[static_cast<std::size_t>(i) * 6 + k] = 0.0;
170 continue;
171 }
172 double sdf_n[6];
173 bool anyGhost = false;
174 for (int k = 0; k < 6; ++k) {
175 Index j = nb_[static_cast<std::size_t>(i) * 6 + k];
176 sdf_n[k] = (j >= 0) ? sdfC_[static_cast<std::size_t>(j)] : -1.0; // missing => solid
177 if (sdf_n[k] < 0.0)
178 anyGhost = true;
179 }
180 cut_[static_cast<std::size_t>(i)] = anyGhost ? 1 : 0;
181 double AC = AC0, off[6];
182 for (int k = 0; k < 6; ++k)
183 off[k] = -beta;
184 double rscale = 1.0, inhomCoef = 0.0;
185 if (anyGhost)
186 buildCutStencil(sdfC_[static_cast<std::size_t>(i)], sdf_n, beta, AC0, AC, off, rscale,
187 inhomCoef);
188 AC_[static_cast<std::size_t>(i)] = AC;
189 for (int k = 0; k < 6; ++k)
190 off_[static_cast<std::size_t>(i) * 6 + k] = off[k];
191 rscale_[static_cast<std::size_t>(i)] = rscale;
192 inhom_[static_cast<std::size_t>(i)] = inhomCoef;
193 }
194 }
195
203 struct Assembled {
204 std::vector<double> diag;
205 std::vector<Index> start;
206 std::vector<Index> nbr;
207 std::vector<double> coef;
208 };
219 const Index n = numLeaves();
220 Assembled A;
221 A.diag.assign(static_cast<std::size_t>(n), 0.0);
222 A.start.assign(static_cast<std::size_t>(n) + 1, 0);
223 std::vector<std::vector<std::pair<Index, double>>> rows(static_cast<std::size_t>(n));
224 for (Index i = 0; i < n; ++i) {
225 const std::size_t s = static_cast<std::size_t>(i);
226 if (!fluid_[s]) { // solid: identity row (u = u_bc)
227 A.diag[s] = 1.0;
228 continue;
229 }
230 if (cut_[s]) { // ξ-overlay stencil (same-level neighbours)
231 A.diag[s] = AC_[s];
232 for (int k = 0; k < 6; ++k) {
233 double a = off_[s * 6 + k];
234 if (a == 0.0)
235 continue;
236 Index j = nb_[s * 6 + k];
237 if (j >= 0)
238 rows[s].emplace_back(j, a);
239 }
240 } else { // regular fluid: idiag·I − μ∇² with C/F-aware face coupling
241 const double invV = 1.0 / lap_.cellVolume(i);
242 double dsum = 0.0;
243 lap_.forEachFaceNeighbor(i, [&](Index j, Real c, int, double a) {
244 rows[s].emplace_back(j, -mu_ * invV * (a * c));
245 dsum += a * c;
246 });
247 A.diag[s] = idiag_ + mu_ * invV * dsum;
248 }
249 if (hasAdv_) { // implicit-FOU advection: diagonal (outflow) + CSR (inflow)
250 const double as = scaleAdvByRscale ? rscale_[s] : 1.0;
251 A.diag[s] += as * advDiag_[s];
252 for (std::size_t p = static_cast<std::size_t>(advStart_[s]);
253 p < static_cast<std::size_t>(advStart_[static_cast<std::size_t>(i) + 1]); ++p)
254 rows[s].emplace_back(advNbr_[p], as * advCoef_[p]);
255 }
256 }
257 for (Index i = 0; i < n; ++i)
258 A.start[static_cast<std::size_t>(i) + 1] =
259 A.start[static_cast<std::size_t>(i)] +
260 static_cast<Index>(rows[static_cast<std::size_t>(i)].size());
261 const Index nnz = A.start[static_cast<std::size_t>(n)];
262 A.nbr.resize(static_cast<std::size_t>(nnz));
263 A.coef.resize(static_cast<std::size_t>(nnz));
264 for (Index i = 0; i < n; ++i) {
265 Index k = A.start[static_cast<std::size_t>(i)];
266 for (auto& e : rows[static_cast<std::size_t>(i)]) {
267 A.nbr[static_cast<std::size_t>(k)] = e.first;
268 A.coef[static_cast<std::size_t>(k)] = e.second;
269 ++k;
270 }
271 }
272 return A;
273 }
274
275 // ---- Runtime operator: the assembled CSR applied with the SHARED face_csr.hpp row kernels — the
276 // exact same arithmetic the device runs (momentum.hpp), executed serially here. assembleOperator
277 // folds the implicit-FOU advection into the single CSR, so the FaceCsrOpT view sets hasAdv=false.
278 // The *Geometric variants above are the independent reference the oracle tests check this
279 // against.
280
284 v.n = numLeaves();
285 v.diag = HostArr<double>(A.diag.data());
286 v.coef = HostArr<double>(A.coef.data());
287 v.start = HostArr<Index>(A.start.data());
288 v.nbr = HostArr<Index>(A.nbr.data());
289 v.hasAdv = false; // advection already folded into the single CSR by assembleOperator
290 return v;
291 }
292
294 void applyOp(const std::vector<double>& u, std::vector<double>& out) const {
295 const Assembled A = assembleOperator();
296 const auto op = hostOp(A);
297 const Index n = numLeaves();
298 out.assign(static_cast<std::size_t>(n), 0.0);
299 const HostArr<double> uacc(u.data());
300 for (Index i = 0; i < n; ++i)
301 out[static_cast<std::size_t>(i)] = faceCsrApplyRow(op, i, uacc);
302 }
303
304 double residual(const std::vector<double>& u, const std::vector<double>& b,
305 std::vector<double>& res) const {
306 const Assembled A = assembleOperator();
307 const auto op = hostOp(A);
308 const Index n = numLeaves();
309 res.assign(static_cast<std::size_t>(n), 0.0);
310 const HostArr<double> uacc(u.data());
311 double s = 0.0;
312 for (Index i = 0; i < n; ++i) {
313 double r = b[static_cast<std::size_t>(i)] - faceCsrApplyRow(op, i, uacc);
314 res[static_cast<std::size_t>(i)] = r;
315 if (fluid_[static_cast<std::size_t>(i)])
316 s += r * r;
317 }
318 return std::sqrt(s);
319 }
320
324 void gaussSeidel(std::vector<double>& u, const std::vector<double>& b, int sweeps) const {
325 const Assembled A = assembleOperator();
326 const auto op = hostOp(A);
327 const Index n = numLeaves();
328 const HostArr<double> uacc(u.data());
329 for (int s = 0; s < sweeps; ++s)
330 for (Index i = 0; i < n; ++i) {
331 double off, d;
332 faceCsrOffDiag(op, i, uacc, off, d);
333 u[static_cast<std::size_t>(i)] = faceCsrPointUpdate(b[static_cast<std::size_t>(i)], off, d,
334 u[static_cast<std::size_t>(i)], 1.0);
335 }
336 }
337
344 void applyOpGeometric(const std::vector<double>& u, std::vector<double>& out) const {
345 const Index n = numLeaves();
346 out.assign(static_cast<std::size_t>(n), 0.0);
347 std::vector<double> Lu;
348 lap_.applyLaplacian(u, Lu); // C/F-aware ∇² (α=1); used only for regular fluid cells
349 for (Index i = 0; i < n; ++i) {
350 const std::size_t s = static_cast<std::size_t>(i);
351 if (!fluid_[s]) {
352 out[s] = u[s];
353 } else if (cut_[s]) {
354 double acc = AC_[s] * u[s];
355 for (int k = 0; k < 6; ++k) {
356 double a = off_[s * 6 + k];
357 if (a == 0.0)
358 continue;
359 Index j = nb_[s * 6 + k];
360 if (j >= 0)
361 acc += a * u[static_cast<std::size_t>(j)];
362 }
363 out[s] = acc;
364 } else {
365 out[s] = idiag_ * u[s] - mu_ * Lu[s]; // regular fluid (C/F-consistent)
366 }
367 if (hasAdv_ && fluid_[s])
368 out[s] += advApply(i, u); // implicit FOU advection
369 }
370 }
371
384 void buildAdvectionFou(const std::array<std::vector<double>, 3>& uadv, double rho,
385 const std::vector<double>& uf, const std::vector<Index>& faceStart,
386 bool useFace) {
387 const Index n = numLeaves();
388 advDiag_.assign(static_cast<std::size_t>(n), 0.0);
389 advStart_.assign(static_cast<std::size_t>(n) + 1, 0);
390 hasAdv_ = true;
391 auto velOutOf = [&](Index i, Index j, int axis, int dir, Index slot) {
392 return useFace ? dir * uf[static_cast<std::size_t>(slot)]
393 : dir * 0.5 *
394 (uadv[axis][static_cast<std::size_t>(i)] +
395 uadv[axis][static_cast<std::size_t>(j)]);
396 };
397 // pass 1: count inflow (off-diagonal) fluid faces per cell.
398 for (Index i = 0; i < n; ++i) {
399 if (!fluid_[static_cast<std::size_t>(i)])
400 continue;
401 int cnt = 0;
402 Index s = useFace ? faceStart[static_cast<std::size_t>(i)] : 0;
403 lap_.forEachFaceFull(i, [&](Index j, int axis, int dir, double, double, double) {
404 if (fluid_[static_cast<std::size_t>(j)] && velOutOf(i, j, axis, dir, s) < 0.0)
405 ++cnt;
406 ++s;
407 });
408 advStart_[static_cast<std::size_t>(i) + 1] = cnt;
409 }
410 for (Index i = 0; i < n; ++i)
411 advStart_[static_cast<std::size_t>(i) + 1] += advStart_[static_cast<std::size_t>(i)];
412 advNbr_.assign(advStart_[static_cast<std::size_t>(n)], -1);
413 advCoef_.assign(advStart_[static_cast<std::size_t>(n)], 0.0);
414 // pass 2: fill diagonal (outflow) + CSR off-diagonals (inflow).
415 for (Index i = 0; i < n; ++i) {
416 if (!fluid_[static_cast<std::size_t>(i)])
417 continue;
418 const double Vi = lap_.cellVolume(i);
419 std::size_t pos = static_cast<std::size_t>(advStart_[static_cast<std::size_t>(i)]);
420 Index s = useFace ? faceStart[static_cast<std::size_t>(i)] : 0;
421 lap_.forEachFaceFull(i, [&](Index j, int axis, int dir, double area, double, double) {
422 const Index slot = s++;
423 if (!fluid_[static_cast<std::size_t>(j)])
424 return;
425 double velOut = velOutOf(i, j, axis, dir, slot);
426 double w = rho * area * velOut / Vi;
427 if (velOut < 0.0) { // inflow → couple to upstream neighbour j (matches pass 1)
428 advNbr_[pos] = j;
429 advCoef_[pos] = w;
430 ++pos;
431 } else {
432 advDiag_[static_cast<std::size_t>(i)] += w; // outflow → diagonal
433 }
434 });
435 }
436 }
437
440 std::vector<double> makeRhs(const std::vector<double>& src, double u_bc) const {
441 const Index n = numLeaves();
442 std::vector<double> b(static_cast<std::size_t>(n), 0.0);
443 for (Index i = 0; i < n; ++i) {
444 if (!fluid_[static_cast<std::size_t>(i)]) {
445 b[static_cast<std::size_t>(i)] = u_bc; // solid held at u_bc
446 continue;
447 }
448 b[static_cast<std::size_t>(i)] =
449 src[static_cast<std::size_t>(i)] * rscale_[static_cast<std::size_t>(i)] +
450 inhom_[static_cast<std::size_t>(i)] * u_bc;
451 }
452 return b;
453 }
454
455 double residualGeometric(const std::vector<double>& u, const std::vector<double>& b,
456 std::vector<double>& res) const {
457 applyOpGeometric(u, res);
458 double s = 0.0;
459 const Index n = numLeaves();
460 for (Index i = 0; i < n; ++i) {
461 double r = b[static_cast<std::size_t>(i)] - res[static_cast<std::size_t>(i)];
462 res[static_cast<std::size_t>(i)] = r;
463 if (fluid_[static_cast<std::size_t>(i)])
464 s += r * r;
465 }
466 return std::sqrt(s);
467 }
468
469 void gaussSeidelGeometric(std::vector<double>& u, const std::vector<double>& b,
470 int sweeps) const {
471 const Index n = numLeaves();
472 for (int s = 0; s < sweeps; ++s)
473 for (Index i = 0; i < n; ++i) {
474 const std::size_t si = static_cast<std::size_t>(i);
475 if (!fluid_[si]) {
476 u[si] = b[si];
477 } else if (cut_[si]) { // ξ-overlay stencil (same-level neighbours)
478 double sum = b[si];
479 for (int k = 0; k < 6; ++k) {
480 double a = off_[si * 6 + k];
481 if (a == 0.0)
482 continue;
483 Index j = nb_[si * 6 + k];
484 if (j >= 0)
485 sum -= a * u[static_cast<std::size_t>(j)];
486 }
487 double d = AC_[si];
488 if (hasAdv_) {
489 sum -= advOffSum(i, u);
490 d += advDiag_[si];
491 }
492 if (d != 0.0)
493 u[si] = sum / d;
494 } else { // regular fluid: idiag·u − μ∇² with C/F-aware face coupling
495 double offsum = 0.0, dsum = 0.0;
496 lap_.forEachFaceNeighbor(i, [&](Index j, Real c, int, double) {
497 offsum += c * u[static_cast<std::size_t>(j)];
498 dsum += c;
499 });
500 double Vi = lap_.cellVolume(i);
501 double diagA = idiag_ + mu_ * dsum / Vi;
502 double sum = b[si] + mu_ * offsum / Vi;
503 if (hasAdv_) {
504 sum -= advOffSum(i, u);
505 diagA += advDiag_[si];
506 }
507 u[si] = sum / diagA;
508 }
509 }
510 }
511
512 public:
516 double fouApply(Index i, const std::vector<double>& field) const {
517 return advDiag_[static_cast<std::size_t>(i)] * field[static_cast<std::size_t>(i)] +
518 advOffSum(i, field);
519 }
520 bool hasAdvection() const { return hasAdv_; }
521
522 private:
523 // Σ_csr coef·field[nbr] — the implicit-FOU advection off-diagonal coupling.
524 double advOffSum(Index i, const std::vector<double>& field) const {
525 double s = 0.0;
526 for (std::size_t p = static_cast<std::size_t>(advStart_[static_cast<std::size_t>(i)]);
527 p < static_cast<std::size_t>(advStart_[static_cast<std::size_t>(i) + 1]); ++p)
528 s += advCoef_[p] * field[static_cast<std::size_t>(advNbr_[p])];
529 return s;
530 }
531 double advApply(Index i, const std::vector<double>& u) const { return fouApply(i, u); }
532
533 public:
534 // Port of ibmFillEntry<0> + ibmModifyStencil for one cut cell (Dirichlet). Public + MORTON_HD so
535 // the device assembler (momentum_assembly.hpp) runs the SAME per-cell stencil build on device.
536 MORTON_HD static void buildCutStencil(double sdf_c, const double sdf_n[6], double beta,
537 double AC0, double& ACout, double off[6], double& rscaleOut,
538 double& inhomOut) {
539 bool ghost[6];
540 double xi[6], D[6];
541 for (int k = 0; k < 6; ++k) {
542 if (sdf_n[k] < 0.0) {
543 ghost[k] = true;
544 double th = sdf_c / (sdf_c - sdf_n[k]);
545 th = th < 1e-4 ? 1e-4 : (th > 1.0 ? 1.0 : th);
546 xi[k] = th;
547 D[k] = cc::poly_D(th);
548 } else {
549 ghost[k] = false;
550 xi[k] = 1.0;
551 D[k] = 1e9;
552 }
553 }
554 bool sand[3] = {ghost[0] && ghost[1], ghost[2] && ghost[3], ghost[4] && ghost[5]};
555 double Dsand[3] = {0, 0, 0};
556 for (int a = 0; a < 3; ++a)
557 if (sand[a])
558 Dsand[a] = cc::poly_D_sandwich(xi[2 * a + 1], xi[2 * a]);
559 double minAbs = 1e30, descale = 1.0;
560 auto upd = [&](double v) {
561 if (cc::poly_abs(v) < minAbs) {
563 descale = v;
564 }
565 };
566 for (int a = 0; a < 3; ++a) {
567 if (sand[a])
568 upd(Dsand[a]);
569 else {
570 if (ghost[2 * a])
571 upd(D[2 * a]);
572 if (ghost[2 * a + 1])
573 upd(D[2 * a + 1]);
574 }
575 }
576 double K[6] = {0}, Mf[6] = {1, 1, 1, 1, 1, 1}, X[6] = {0}, Nbc[6] = {0},
577 R[6] = {1, 1, 1, 1, 1, 1};
578 for (int a = 0; a < 3; ++a) {
579 int km = 2 * a + 1, kp = 2 * a;
580 double Daxis = sand[a] ? Dsand[a] : (ghost[kp] ? D[kp] : (ghost[km] ? D[km] : descale));
581 double r = descale / Daxis;
582 if (cc::poly_abs(Daxis) < 1e-9)
583 r = 1.0;
584 R[kp] = R[km] = r;
585 if (sand[a]) {
586 K[kp] = cc::poly_N_c_sandwich(xi[km], xi[kp]) * r;
587 K[km] = cc::poly_N_c_sandwich(xi[kp], xi[km]) * r;
590 Mf[kp] = Mf[km] = 0.0;
591 } else {
592 for (int side = 0; side < 2; ++side) {
593 int kk = side == 0 ? kp : km;
594 if (ghost[kk]) {
595 K[kk] = cc::poly_Nc(xi[kk]) * r;
596 X[kk] = cc::poly_N_nb(xi[kk]) * r;
597 Nbc[kk] = cc::poly_Nbc(xi[kk]) * r;
598 Mf[kk] = 0.0;
599 } else {
600 K[kk] = 0.0;
601 Mf[kk] = 1.0;
602 X[kk] = 0.0;
603 Nbc[kk] = 0.0;
604 }
605 }
606 }
607 }
608 // ibmModifyStencil (orig off-diagonal = -beta for every direction). OPP is a function-local
609 // constexpr (not the static member) so the runtime index mod[OPP[k]] is device-safe under CUDA.
610 constexpr int OPP_[6] = {1, 0, 3, 2, 5, 4};
611 double aC = AC0 * descale, mod[6] = {0, 0, 0, 0, 0, 0}, inhom = 0.0;
612 for (int k = 0; k < 6; ++k) {
613 double vnb = -beta;
614 aC += vnb * K[k];
615 inhom += Nbc[k] * vnb;
616 mod[k] += vnb * (descale * Mf[k] - 1.0);
617 mod[OPP_[k]] += vnb * X[k];
618 }
619 ACout = aC;
620 for (int k = 0; k < 6; ++k)
621 off[k] = -beta + mod[k];
623 inhomOut = inhom;
624 }
625
626 private:
627 Vec<3> cellCenter(Index i) const {
628 auto b = t_->bounds(i);
629 double s = static_cast<double>(Index(1) << t_->level(i));
630 Vec<3> c{};
631 for (int d = 0; d < 3; ++d)
632 c[d] = origin_[d] + (static_cast<double>(b[0][d]) + 0.5 * s) * h0_;
633 return c;
634 }
635
636 template <class SdfFn>
637 double volumeFraction(Index i, SdfFn&& sdfFn, int nsub) const {
638 auto b = t_->bounds(i);
639 double s = static_cast<double>(Index(1) << t_->level(i));
640 double w = s * h0_;
641 int inside = 0, total = nsub * nsub * nsub;
642 for (int a = 0; a < nsub; ++a)
643 for (int bb = 0; bb < nsub; ++bb)
644 for (int cc2 = 0; cc2 < nsub; ++cc2) {
645 Vec<3> p{origin_[0] + static_cast<double>(b[0][0]) * h0_ + (a + 0.5) / nsub * w,
646 origin_[1] + static_cast<double>(b[0][1]) * h0_ + (bb + 0.5) / nsub * w,
647 origin_[2] + static_cast<double>(b[0][2]) * h0_ + (cc2 + 0.5) / nsub * w};
648 if (sdfFn(p) > 0.0)
649 ++inside;
650 }
651 return static_cast<double>(inside) / total;
652 }
653
654 Index neighbor(Index i, int k) const {
655 int axis = k / 2, dir = (k % 2 == 0) ? +1 : -1;
656 auto b = t_->bounds(i);
657 const auto& lo = b[0];
658 Coord si = Coord(Coord(1) << t_->level(i));
659 long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
660 : static_cast<long>(lo[axis]) - 1;
661 long e = static_cast<long>(fineExt_[axis]);
662 std::array<Coord, 3> p = lo;
663 p[axis] = static_cast<Coord>(((pc % e) + e) % e);
664 return t_->find(M::encode(p).code());
665 }
666
667 const Octree* t_ = nullptr;
668 Real h0_ = 1.0;
669 Vec<3> origin_{};
670 std::array<Coord, 3> fineExt_{};
671 std::vector<double> sdfC_, kappa_, AC_, rscale_, inhom_, off_;
672 std::vector<Index> nb_;
673 std::vector<char> fluid_, cut_;
674 AmrPoisson<3, Bits> lap_; // C/F-aware ∇² provider for regular fluid cells (α=1)
675 double idiag_ = 0.0, mu_ = 1.0;
676 std::vector<double> advDiag_, advCoef_; // implicit-FOU advection (rebuilt per step)
677 std::vector<Index> advStart_, advNbr_; // CSR off-diagonals (C/F-conservative)
678 bool hasAdv_ = false;
679};
680
681} // namespace peclet::core::amr
682
683#endif // PECLET_CORE_HAVE_MORTON
684#endif // PECLET_CORE_AMR_CUT_CELL_HPP
#define MORTON_HD
double kappa(Index i) const
Definition cut_cell.hpp:102
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:516
const std::vector< Index > & nbRaw() const
n·6 periodic face-neighbour indices
Definition cut_cell.hpp:114
const std::vector< double > & advCoefRaw() const
Definition cut_cell.hpp:125
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:294
void gaussSeidelGeometric(std::vector< double > &u, const std::vector< double > &b, int sweeps) const
Definition cut_cell.hpp:469
double rhsScale(Index i) const
Definition cut_cell.hpp:103
static constexpr int Dim
Definition cut_cell.hpp:77
const std::vector< double > & acRaw() const
Definition cut_cell.hpp:117
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:119
double residualGeometric(const std::vector< double > &u, const std::vector< double > &b, std::vector< double > &res) const
Definition cut_cell.hpp:455
const std::vector< double > & sdfCRaw() const
per-cell SDF sample (build Pass 1)
Definition cut_cell.hpp:111
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:282
const std::vector< double > & advDiagRaw() const
Definition cut_cell.hpp:124
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:324
double residual(const std::vector< double > &u, const std::vector< double > &b, std::vector< double > &res) const
Definition cut_cell.hpp:304
const std::vector< char > & cutRaw() const
Definition cut_cell.hpp:116
Assembled assembleOperator(bool scaleAdvByRscale=false) const
scaleAdvByRscale (default false ⇒ reproduces applyOp/gaussSeidel exactly, for the matvec test): when ...
Definition cut_cell.hpp:218
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:384
const std::vector< Index > & advNbrRaw() const
Definition cut_cell.hpp:127
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:440
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:344
bool isFluid(Index i) const
Definition cut_cell.hpp:95
BlockOctree< 3, Bits > Octree
Definition cut_cell.hpp:78
double beta() const
buildCutStencil's β (= mu_/h0²)
Definition cut_cell.hpp:122
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:536
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:101
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:99
const std::vector< Index > & advStartRaw() const
Definition cut_cell.hpp:126
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:133
const std::vector< char > & fluidRaw() const
Definition cut_cell.hpp:115
const AmrPoisson< 3, Bits > & lap() const
α=1 C/F ∇² geometry for regular cells
Definition cut_cell.hpp:108
const std::vector< double > & offRaw() const
n·6 ξ-overlay off-diagonals
Definition cut_cell.hpp:118
static constexpr int OPP[6]
Definition cut_cell.hpp:84
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:168
void init(const Octree &t, Real h0)
Definition poisson.hpp:53
void applyLaplacian(const std::vector< double > &u, std::vector< double > &out) const
out = L u (periodic FV Laplacian).
Definition poisson.hpp:413
void forEachFaceFull(Index i, Fn &&fn) const
Like forEachFaceNeighbor but exposes geometry for a consistent FV divergence/gradient: fn(neighbour,...
Definition poisson.hpp:220
Real cellVolume(Index i) const
Definition poisson.hpp:156
morton::Morton< Dim, Bits > M
typename M::coord_type Coord
unsigned level(Index i) const
Index find(Code p) const
Leaf containing Morton code p, or -1. Host wrapper over amrLocate.
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
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
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.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:203
std::vector< Index > start
CSR row offsets, size n+1.
Definition cut_cell.hpp:205
std::vector< double > diag
size n
Definition cut_cell.hpp:204
std::vector< double > coef
off-diagonal coefficient, size nnz
Definition cut_cell.hpp:207
std::vector< Index > nbr
neighbour leaf per off-diagonal, size nnz
Definition cut_cell.hpp:206