peclet-amr 0.2.0
Block-local-Morton AMR octree + collocated cut-cell Navier–Stokes solver (header-only C++20, Kokkos + MPI)
Loading...
Searching...
No Matches
ghost_projection.hpp
Go to the documentation of this file.
1// core — directional ghost-cell projection overlay on the AMR octree (collocated).
2//
3// *** PRODUCTION CANDIDATE (re-framed 2026-08-24; quarantined 2026-08-19..24) ***
4// flow's attractor campaign (flow/doc/collocated_invisible_subspace.md) reversed the 08-19
5// retirement calculus: the aperture projection possesses an attractor FAMILY of steady states
6// (its solid-centered pressure DOFs are constraint multipliers the gauge-exact gradient never
7// reads — support inconsistency, Prop. 2 of the note) plus a rotational-update instability at
8// fine resolution, both of which this fluid-only scheme is measured NOT to have (family-free,
9// no stabilizer at any dt, protocol-independent, clean-ladder convergent with a ~+0.2% bias).
10// The 08-19 COST statement stands (aperture MG-PCG is cheaper per step); the scheme choice is
11// now robustness/uniqueness. Default remains aperture until the suite-wide flip decision;
12// select with setGhostProjection(true) — the (2, 2) closure pair ONLY (the (1, 2) mixed form
13// is march-unstable above ~2000 spheres, flow/doc/ghost_hardening_findings_A.md).
14//
15// The AMR port of flow's collocated set_ghost_projection (flow/src/ghost_projection.hpp §9 of
16// flow/doc/collocated_second_order_open_problem.md): the aperture projection's two measured O(1)
17// cut-cell defects (tests/study_amr_ghost_apriori.cpp — the gauge-dependent O(1/h) ABC gradient
18// and the O(1) truncation of the pinned ½/½ aperture constraint) are removed by
19// * closing every solid/sliver face of the ½/½ face-AVERAGED cell field with the momentum
20// IBM's 1-D wall-anchored closure (peclet::core::scheme — the SAME pure per-face functions
21// flow uses), which after the substitution uf -= grad(phi) makes the pressure operator
22// A = L_bin + Delta: the symmetric BINARY-openness FV Laplacian (face open iff the face
23// sample and both adjacent centers are fluid — rides the UNCHANGED openness-MG rails as
24// the preconditioner) plus a compact nonsymmetric per-row overlay (this header), both
25// row-rescaled by rho = min(1, min_f D_f);
26// * the directional ghost cell-gradient (AmrFlow::setGhostGradient, already the step-1 hybrid)
27// for the -grad(p^n) predictor and the cell correction.
28//
29// THE STRUCTURAL INVARIANT that makes the octree port cheap: cut cells live in a uniformly-FINEST
30// band (the AmrCutCell same-level contract), so every overlay row's ±2-cell closure reach stays
31// inside a locally uniform region — no closure ever crosses a 2:1 level boundary. buildGhostOverlay
32// ENFORCES this (throws if a non-clean row's reach touches a different-level leaf): widen the
33// refineToSdf band rather than weaken the invariant. Level boundaries then carry only the smooth
34// binary operator, which the openness MG already handles conservatively.
35//
36// Sign conventions (differ from flow's kernels — DERIVED, do not pattern-match): the suite AMR
37// operator is the NEGATIVE-definite Laplacian L solved as L phi = div(u*). The ghost system is
38// [rho·(L_bin + Delta)] phi = rho·D_g(u*), Delta_i = invh² Σ_k sgn_k [w1(X(mn)-X(mn-1)) +
39// w2(X(mf)-X(mf-1))]
40// (flow's gpApplyDelta carries the OPPOSITE sign because its binary operator is -L). The
41// divergence delta keeps flow's orientation verbatim: D_g adds sgn·(w1·U(mn) + w2·U(mf)) per
42// closure face (EXPLICIT: sgn·U(mg)) with U(m) = ½(u_a(cell m-1) + u_a(cell m)), scaled invh.
43// Decoupled rows (no phi coupling at all) are zeroed in both matrix and RHS (phi = 0).
44//
45// Host-only-safe: the overlay build + host delta appliers compile without Kokkos (the oracle
46// path); the device mirror + kernels are guarded on KOKKOS_INLINE_FUNCTION (include this header
47// AFTER a Kokkos-carrying header in device TUs).
48#ifndef PECLET_AMR_GHOST_PROJECTION_HPP
49#define PECLET_AMR_GHOST_PROJECTION_HPP
50
51#include <array>
52#include <cstdint>
53#include <stdexcept>
54#include <vector>
55
57#include "peclet/amr/common.hpp"
59#include "peclet/core/common/types.hpp"
60#include "peclet/core/scheme/ghost_closure.hpp"
61
62namespace peclet::amr {
63
68 Index n = 0;
69 std::vector<Index> cell;
70 std::vector<float> rescale;
71 std::vector<int8_t> coupled;
72 std::vector<int8_t> state;
73 std::vector<float> th;
74 std::vector<float> w_bc, w_n1, w_n2;
75 std::vector<float> wm_n1, wm_n2;
76 std::vector<Index> nbr;
77 std::vector<double> invh;
78};
79
80namespace detail {
81// Reference adapter so scheme::gpFillRow (which writes ov.field(slot) = …) targets the host SoA.
84 Index& cell(int s) const { return g.cell[static_cast<std::size_t>(s)]; }
85 float& rescale(int s) const { return g.rescale[static_cast<std::size_t>(s)]; }
86 int8_t& coupled(int s) const { return g.coupled[static_cast<std::size_t>(s)]; }
87 int8_t& state(int s) const { return g.state[static_cast<std::size_t>(s)]; }
88 float& th(int s) const { return g.th[static_cast<std::size_t>(s)]; }
89 float& w_bc(int s) const { return g.w_bc[static_cast<std::size_t>(s)]; }
90 float& w_n1(int s) const { return g.w_n1[static_cast<std::size_t>(s)]; }
91 float& w_n2(int s) const { return g.w_n2[static_cast<std::size_t>(s)]; }
92 float& wm_n1(int s) const { return g.wm_n1[static_cast<std::size_t>(s)]; }
93 float& wm_n2(int s) const { return g.wm_n2[static_cast<std::size_t>(s)]; }
94};
95} // namespace detail
96
108template <unsigned Bits>
111 const std::vector<double>& sdfC, int matrixOrder,
112 int rhsOrder, bool* bandViolation = nullptr) {
113 GhostOverlay ov;
114 const Index n = t.numLeaves();
115 auto sf = [&](Index j) {
116 return j >= 0 ? static_cast<float>(sdfC[static_cast<std::size_t>(j)]) : -1.0f;
117 };
118 // scratch single row appended per accepted cell
119 for (Index i = 0; i < n; ++i) {
120 if (!(sdfC[static_cast<std::size_t>(i)] > 0.0))
121 continue; // solid-centered: decoupled row (phi = 0), not in the overlay
122 Index chain[3][5];
123 bool clean = true;
124 for (int a = 0; a < 3; ++a) {
125 chain[a][2] = i;
126 chain[a][3] = pres.periodicNeighbor(i, a, +1);
127 chain[a][1] = pres.periodicNeighbor(i, a, -1);
128 const float c1 = sf(chain[a][1]), c3 = sf(chain[a][3]), c2 = sf(i);
129 clean =
130 clean && c1 >= 0.0f && c3 >= 0.0f && 0.5f * (c1 + c2) >= 0.0f && 0.5f * (c2 + c3) >= 0.0f;
131 }
132 if (clean)
133 continue;
134 // Non-clean ⇒ cut band ⇒ the finest-band contract must hold: the whole ±2 reach same-level.
135 const unsigned Li = t.level(i);
136 bool bad = false;
137 for (int a = 0; a < 3; ++a) {
138 chain[a][4] = chain[a][3] >= 0 ? pres.periodicNeighbor(chain[a][3], a, +1) : chain[a][3];
139 chain[a][0] = chain[a][1] >= 0 ? pres.periodicNeighbor(chain[a][1], a, -1) : chain[a][1];
140 for (int q = 0; q < 5; ++q)
141 if (chain[a][q] < 0 || pres.levelOf(chain[a][q]) != Li)
142 bad = true;
143 }
144 if (bad) {
145 if (!bandViolation)
146 throw std::runtime_error(
147 "amr ghost projection: an overlay row's ±2 closure reach crosses a 2:1 level "
148 "boundary — the cut band is too thin; widen the refineToSdf band margin");
149 *bandViolation = true;
150 continue;
151 }
152 float F[3][4], Cq[3][5];
153 for (int a = 0; a < 3; ++a) {
154 for (int q = 0; q < 5; ++q)
155 Cq[a][q] = sf(chain[a][q]);
156 for (int m = 0; m < 4; ++m) // face i+m-1 = mean of centers q=m-1, q=m (slots m, m+1)
157 F[a][m] = 0.5f * (Cq[a][m] + Cq[a][m + 1]);
158 }
159 const int slot = static_cast<int>(ov.n);
160 ov.cell.resize(static_cast<std::size_t>(slot) + 1);
161 ov.rescale.resize(static_cast<std::size_t>(slot) + 1);
162 ov.coupled.resize(static_cast<std::size_t>(slot) + 1);
163 ov.state.resize(static_cast<std::size_t>(slot + 1) * 6);
164 ov.th.resize(static_cast<std::size_t>(slot + 1) * 6);
165 ov.w_bc.resize(static_cast<std::size_t>(slot + 1) * 6);
166 ov.w_n1.resize(static_cast<std::size_t>(slot + 1) * 6);
167 ov.w_n2.resize(static_cast<std::size_t>(slot + 1) * 6);
168 ov.wm_n1.resize(static_cast<std::size_t>(slot + 1) * 6);
169 ov.wm_n2.resize(static_cast<std::size_t>(slot + 1) * 6);
171 if (!scheme::gpFillRow(ref, slot, i, F, Cq, matrixOrder, rhsOrder)) {
172 // pre-check said non-clean but every face classified COUPLED (cannot normally happen —
173 // conservative float edge); drop the provisional row.
174 ov.cell.resize(static_cast<std::size_t>(slot));
175 ov.rescale.resize(static_cast<std::size_t>(slot));
176 ov.coupled.resize(static_cast<std::size_t>(slot));
177 ov.state.resize(static_cast<std::size_t>(slot) * 6);
178 ov.th.resize(static_cast<std::size_t>(slot) * 6);
179 ov.w_bc.resize(static_cast<std::size_t>(slot) * 6);
180 ov.w_n1.resize(static_cast<std::size_t>(slot) * 6);
181 ov.w_n2.resize(static_cast<std::size_t>(slot) * 6);
182 ov.wm_n1.resize(static_cast<std::size_t>(slot) * 6);
183 ov.wm_n2.resize(static_cast<std::size_t>(slot) * 6);
184 continue;
185 }
186 for (int a = 0; a < 3; ++a)
187 for (int q = 0; q < 5; ++q)
188 ov.nbr.push_back(chain[a][q]);
189 for (int a = 0; a < 3; ++a) // Phase 3: per axis
190 ov.invh.push_back(1.0 / pres.cellWidth(i, a));
191 ++ov.n;
192 }
193 return ov;
194}
195
204template <class SdfFn>
205inline auto makeBinaryOpenFn(SdfFn sdfFn, const Vec<3>& h0) {
206 return [sdfFn, h0](const Vec<3>& fc, int axis) -> double {
207 Vec<3> pm = fc, pp = fc;
208 pm[axis] -= 0.5 * h0[axis];
209 pp[axis] += 0.5 * h0[axis];
210 const double sm = sdfFn(pm), sp = sdfFn(pp);
211 const float fm = static_cast<float>(sm), fp = static_cast<float>(sp);
212 return (sm > 0.0 && sp > 0.0 && 0.5f * (fm + fp) >= 0.0f) ? 1.0 : 0.0;
213 };
214}
215
217template <class SdfFn>
218inline auto makeBinaryOpenFn(SdfFn sdfFn, double h0) {
219 return makeBinaryOpenFn(std::forward<SdfFn>(sdfFn), detail::filledVec<3>(h0));
220}
221
231template <unsigned Bits>
232inline std::vector<char> findPocketCells(const BlockOctree<3, Bits>& t,
234 const std::vector<double>& sdfC) {
235 const Index n = t.numLeaves();
236 std::vector<char> pocket(static_cast<std::size_t>(n), 0);
237 std::vector<int> comp(static_cast<std::size_t>(n), -1);
238 auto fluid = [&](Index i) { return sdfC[static_cast<std::size_t>(i)] > 0.0; };
239 int nc = 0;
240 std::vector<Index> stack, compSize;
241 for (Index s = 0; s < n; ++s) {
242 if (!fluid(s) || comp[static_cast<std::size_t>(s)] >= 0)
243 continue;
244 const int id = nc++;
245 compSize.push_back(0);
246 stack.assign(1, s);
247 comp[static_cast<std::size_t>(s)] = id;
248 while (!stack.empty()) {
249 const Index i = stack.back();
250 stack.pop_back();
251 ++compSize[static_cast<std::size_t>(id)];
252 pres.forEachFaceNeighbor(i, [&](Index j, Real, int, double alpha) {
253 if (alpha > 0.5 && fluid(j) && comp[static_cast<std::size_t>(j)] < 0) {
254 comp[static_cast<std::size_t>(j)] = id;
255 stack.push_back(j);
256 }
257 });
258 }
259 }
260 if (nc <= 1)
261 return pocket;
262 int mainId = 0;
263 for (int c = 1; c < nc; ++c)
264 if (compSize[static_cast<std::size_t>(c)] > compSize[static_cast<std::size_t>(mainId)])
265 mainId = c;
266 Index nPocket = 0;
267 for (Index i = 0; i < n; ++i)
268 if (fluid(i) && comp[static_cast<std::size_t>(i)] != mainId) {
269 pocket[static_cast<std::size_t>(i)] = 1;
270 ++nPocket;
271 }
272 std::fprintf(stderr,
273 "[peclet.amr] ghost projection: binary coupled-face graph fragments into %d "
274 "components — decoupling %lld pocket cells outside the main component\n",
275 nc, static_cast<long long>(nPocket));
276 return pocket;
277}
278
279// ---- host (oracle) delta appliers --------------------------------------------------------------
280
283inline void ghostApplyDeltaHost(const GhostOverlay& ov, const std::vector<double>& x,
284 std::vector<double>& y) {
285 for (Index r = 0; r < ov.n; ++r) {
286 const std::size_t rr = static_cast<std::size_t>(r);
287 const Index c = ov.cell[rr];
288 if (!ov.coupled[rr]) {
289 y[static_cast<std::size_t>(c)] = 0.0;
290 continue;
291 }
292 auto X = [&](int a, int q) {
293 return x[static_cast<std::size_t>(
294 ov.nbr[rr * 15 + static_cast<std::size_t>(a) * 5 + static_cast<std::size_t>(q + 2)])];
295 };
296 // Phase 3 (`docs/amr_anisotropic.md` §5, work order A1): the row carries `1/cellWidth` PER
297 // AXIS. The delta is a sum over the six FACES, each belonging to axis `k/2`, so the axis weight
298 // multiplies each face's term. It is spelled as `ih^2 * sum_k (term_k * r_a^2)` with
299 // `ih = invh[3r]` and `r_a = invh[3r+a]/ih` — every `r_a` is a double divided by itself on a
300 // cubic octree, hence EXACTLY 1.0, `term*1.0 == term`, and the whole expression collapses to
301 // the pre-Phase-3 `ih*ih*delta` bit for bit (Rule B).
302 const double ih = ov.invh[rr * 3];
303 const double r2[3] = {1.0, (ov.invh[rr * 3 + 1] / ih) * (ov.invh[rr * 3 + 1] / ih),
304 (ov.invh[rr * 3 + 2] / ih) * (ov.invh[rr * 3 + 2] / ih)};
305 double delta = 0.0;
306 for (int k = 0; k < 6; ++k) {
307 const int8_t st = ov.state[rr * 6 + static_cast<std::size_t>(k)];
308 if (st != scheme::GP_QUAD && st != scheme::GP_LIN)
309 continue;
310 const int a = k / 2;
311 const int sgn = (k & 1) ? -1 : 1; // odd k = minus side
312 const int mn = (k & 1) ? 1 : 0; // near-face relative index
313 const int mf = (k & 1) ? 2 : -1; // far-face relative index
314 const double w1 = ov.wm_n1[rr * 6 + static_cast<std::size_t>(k)];
315 const double w2 = ov.wm_n2[rr * 6 + static_cast<std::size_t>(k)];
316 delta += r2[a] * (sgn * w1 * (X(a, mn) - X(a, mn - 1))); // +axis gradient — AMR L sign
317 if (st == scheme::GP_QUAD && w2 != 0.0)
318 delta += r2[a] * (sgn * w2 * (X(a, mf) - X(a, mf - 1)));
319 }
320 y[static_cast<std::size_t>(c)] =
321 ov.rescale[rr] * (y[static_cast<std::size_t>(c)] + ih * ih * delta);
322 }
323}
324
327inline void ghostDivergDeltaHost(const GhostOverlay& ov,
328 const std::array<std::vector<double>, 3>& u,
329 std::vector<double>& d) {
330 for (Index r = 0; r < ov.n; ++r) {
331 const std::size_t rr = static_cast<std::size_t>(r);
332 const Index c = ov.cell[rr];
333 if (!ov.coupled[rr]) {
334 d[static_cast<std::size_t>(c)] = 0.0;
335 continue;
336 }
337 auto U = [&](int a, int m) { // face-averaged value at face index i+m along axis a
338 const Index cm = ov.nbr[rr * 15 + static_cast<std::size_t>(a) * 5 +
339 static_cast<std::size_t>(m + 1)]; // q = m-1
340 const Index cp = ov.nbr[rr * 15 + static_cast<std::size_t>(a) * 5 +
341 static_cast<std::size_t>(m + 2)]; // q = m
342 return 0.5 * (u[static_cast<std::size_t>(a)][static_cast<std::size_t>(cm)] +
343 u[static_cast<std::size_t>(a)][static_cast<std::size_t>(cp)]);
344 };
345 // Phase 3: `ih * sum_a r_a * dd_a`, the divergence twin of the ratio form above.
346 const double ihd = ov.invh[rr * 3];
347 const double r1[3] = {1.0, ov.invh[rr * 3 + 1] / ihd, ov.invh[rr * 3 + 2] / ihd};
348 double dd = 0.0;
349 for (int k = 0; k < 6; ++k) {
350 const int8_t st = ov.state[rr * 6 + static_cast<std::size_t>(k)];
351 if (st == scheme::GP_COUPLED)
352 continue;
353 const int a = k / 2;
354 const int sgn = (k & 1) ? -1 : 1;
355 const int mg = (k & 1) ? 0 : 1; // the closed face's own index
356 const int mn = (k & 1) ? 1 : 0;
357 const int mf = (k & 1) ? 2 : -1;
358 if (st == scheme::GP_EXPLICIT) {
359 dd += r1[a] * (sgn * U(a, mg)); // sliver without crossing: explicit u* flux
360 continue;
361 }
362 if (st == scheme::GP_BC_ONLY)
363 continue; // u_bc = 0
364 double val = ov.w_n1[rr * 6 + static_cast<std::size_t>(k)] * U(a, mn);
365 if (st == scheme::GP_QUAD)
366 val += ov.w_n2[rr * 6 + static_cast<std::size_t>(k)] * U(a, mf);
367 dd += r1[a] * (sgn * val);
368 }
369 d[static_cast<std::size_t>(c)] = ov.rescale[rr] * (d[static_cast<std::size_t>(c)] + ihd * dd);
370 }
371}
372
373// ---- device mirror + kernels (Kokkos TUs only; include after a Kokkos-carrying header) ---------
374#ifdef KOKKOS_INLINE_FUNCTION
375
385
388 d.n = h.n;
389 if (h.n == 0)
390 return d;
391 d.cell = toDevice(h.cell, "gp_cell");
392 d.nbr = toDevice(h.nbr, "gp_nbr");
393 d.rescale = toDevice(h.rescale, "gp_rescale");
394 d.coupled = toDevice(h.coupled, "gp_coupled");
395 d.state = toDevice(h.state, "gp_state");
396 d.w_n1 = toDevice(h.w_n1, "gp_wn1");
397 d.w_n2 = toDevice(h.w_n2, "gp_wn2");
398 d.wm_n1 = toDevice(h.wm_n1, "gp_wmn1");
399 d.wm_n2 = toDevice(h.wm_n2, "gp_wmn2");
400 d.invh = toDevice(h.invh, "gp_invh");
401 return d;
402}
403
406 if (ov.n == 0)
407 return;
408 auto cell = ov.cell;
409 auto nbr = ov.nbr;
410 auto resc = ov.rescale;
411 auto cpl = ov.coupled;
412 auto st = ov.state;
413 auto wm1 = ov.wm_n1;
414 auto wm2 = ov.wm_n2;
415 auto invh = ov.invh;
416 Kokkos::parallel_for(
417 "amr::gp_apply_delta", ov.n, KOKKOS_LAMBDA(const Index r) {
418 const Index c = cell(r);
419 if (!cpl(r)) {
420 y(c) = 0.0;
421 return;
422 }
423 // Phase 3: the ratio form of ghostApplyDeltaHost (per-axis weights, exactly 1.0 cubic).
424 const double ih = invh(r * 3);
425 const double rx = invh(r * 3 + 1) / ih, ry = invh(r * 3 + 2) / ih;
426 const double r2[3] = {1.0, rx * rx, ry * ry};
427 double delta = 0.0;
428 for (int k = 0; k < 6; ++k) {
429 const int8_t s = st(r * 6 + k);
430 if (s != scheme::GP_QUAD && s != scheme::GP_LIN)
431 continue;
432 const int a = k / 2;
433 const int sgn = (k & 1) ? -1 : 1;
434 const int mn = (k & 1) ? 1 : 0;
435 const int mf = (k & 1) ? 2 : -1;
436 const double w1 = wm1(r * 6 + k), w2 = wm2(r * 6 + k);
437 delta += r2[a] *
438 (sgn * w1 * (x(nbr(r * 15 + a * 5 + mn + 2)) - x(nbr(r * 15 + a * 5 + mn + 1))));
439 if (s == scheme::GP_QUAD && w2 != 0.0)
440 delta += r2[a] * (sgn * w2 *
441 (x(nbr(r * 15 + a * 5 + mf + 2)) - x(nbr(r * 15 + a * 5 + mf + 1))));
442 }
443 y(c) = resc(r) * (y(c) + ih * ih * delta);
444 });
445}
446
450 if (ov.n == 0)
451 return;
452 auto cell = ov.cell;
453 auto nbr = ov.nbr;
454 auto resc = ov.rescale;
455 auto cpl = ov.coupled;
456 auto st = ov.state;
457 auto w1v = ov.w_n1;
458 auto w2v = ov.w_n2;
459 auto invh = ov.invh;
460 Kokkos::parallel_for(
461 "amr::gp_diverg_delta", ov.n, KOKKOS_LAMBDA(const Index r) {
462 const Index c = cell(r);
463 if (!cpl(r)) {
464 d(c) = 0.0;
465 return;
466 }
467 auto U = [&](int a, int m) {
468 const Index cm = nbr(r * 15 + a * 5 + m + 1);
469 const Index cp = nbr(r * 15 + a * 5 + m + 2);
470 const double vm = (a == 0) ? u0(cm) : (a == 1) ? u1(cm) : u2(cm);
471 const double vp = (a == 0) ? u0(cp) : (a == 1) ? u1(cp) : u2(cp);
472 return 0.5 * (vm + vp);
473 };
474 // Phase 3: the ratio form of ghostDivergDeltaHost.
475 const double ih = invh(r * 3);
476 const double r1[3] = {1.0, invh(r * 3 + 1) / ih, invh(r * 3 + 2) / ih};
477 double dd = 0.0;
478 for (int k = 0; k < 6; ++k) {
479 const int8_t s = st(r * 6 + k);
480 if (s == scheme::GP_COUPLED)
481 continue;
482 const int a = k / 2;
483 const int sgn = (k & 1) ? -1 : 1;
484 const int mg = (k & 1) ? 0 : 1;
485 const int mn = (k & 1) ? 1 : 0;
486 const int mf = (k & 1) ? 2 : -1;
487 if (s == scheme::GP_EXPLICIT) {
488 dd += r1[a] * (sgn * U(a, mg));
489 continue;
490 }
491 if (s == scheme::GP_BC_ONLY)
492 continue;
493 double val = w1v(r * 6 + k) * U(a, mn);
494 if (s == scheme::GP_QUAD)
495 val += w2v(r * 6 + k) * U(a, mf);
496 dd += r1[a] * (sgn * val);
497 }
498 d(c) = resc(r) * (d(c) + ih * dd);
499 });
500}
501
502#endif // KOKKOS_INLINE_FUNCTION
503
504} // namespace peclet::amr
505
506#endif // PECLET_AMR_GHOST_PROJECTION_HPP
Cell-centered FV Poisson operator on one (periodic) block octree.
Definition poisson.hpp:43
Per-block adaptive octree over block-local Morton codes.
void ghostApplyDeltaHost(const GhostOverlay &ov, const std::vector< double > &x, std::vector< double > &y)
Matrix overlay: y currently holds the BINARY L matvec; overwrite the overlay rows with y = rho·(y + i...
void ghostApplyDelta(const GhostOverlayDev &ov, View< const double > x, View< double > y)
Device matrix overlay (== ghostApplyDeltaHost). Distinct rows per thread: no atomics.
std::vector< char > findPocketCells(const BlockOctree< 3, Bits > &t, const AmrPoisson< 3, Bits > &pres, const std::vector< double > &sdfC)
Fragmentation guard (the AMR port of flow's host-BFS pocket guard): the BINARY coupled-face graph (a ...
void ghostDivergDeltaHost(const GhostOverlay &ov, const std::array< std::vector< double >, 3 > &u, std::vector< double > &d)
Divergence overlay: d currently holds the BINARY divergence (physical); overwrite overlay rows with d...
auto makeBinaryOpenFn(SdfFn sdfFn, const Vec< 3 > &h0)
Binary openness callable factory for the MG surrogate: a face is open iff both adjacent centers (prob...
GhostOverlayDev uploadGhostOverlay(const GhostOverlay &h)
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:51
void ghostDivergDelta(const GhostOverlayDev &ov, View< const double > u0, View< const double > u1, View< const double > u2, View< double > d)
Device divergence overlay (== ghostDivergDeltaHost).
GhostOverlay buildGhostOverlay(const BlockOctree< 3, Bits > &t, const AmrPoisson< 3, Bits > &pres, const std::vector< double > &sdfC, int matrixOrder, int rhsOrder, bool *bandViolation=nullptr)
Build the overlay from the octree + the cell-centered SDF samples (AmrCutCell::sdfCRaw — EXTENDED ove...
Device mirror of GhostOverlay (uploaded once per setSolid).
Host ghost-projection overlay: one row per non-clean fluid leaf (== cut cell: some ±1 center sample s...
std::vector< float > wm_n2
[n*6] matrix (implicit phi) weights (matrixOrder)
std::vector< int8_t > coupled
[n] 1 if the row has any phi coupling at all
std::vector< Index > cell
[n] leaf index
std::vector< float > th
[n*6] (diagnostics)
std::vector< float > wm_n1
std::vector< int8_t > state
[n*6]
std::vector< float > w_bc
std::vector< Index > nbr
[n*15] ±2 neighbour chain per axis
std::vector< float > w_n2
[n*6] RHS/diagnostic closure weights (rhsOrder)
std::vector< float > rescale
[n] rho = min(1, min_f D_f) of the MATRIX weights
std::vector< float > w_n1
std::vector< double > invh
[n*3] 1/cellWidth PER AXIS of the row (Phase 3)