core 0.5.0
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
ghost_projection_sampled.hpp
Go to the documentation of this file.
1// core — SAMPLED ghost-projection overlay: cut cells at MULTIPLE octree levels (host prototype).
2//
3// The D1 machinery of docs/amr_mixed_level_cut_band_plan.md, host-only (the Phase-1 oracle;
4// the device port follows the plan's rungs after the Snellius gate): the ghost overlay's ±2
5// same-level chain entries become SAMPLE SLOTS — precomputed linear functionals of nearby
6// fluid leaves. A same-level entry is the identity (today's behaviour, bit-identical on a
7// uniform band); an entry across a 2:1 level boundary is a VIRTUAL SAMPLE at the uniform-grid
8// position, reconstructed by DEGREE-2 least squares over fluid leaf point values (the M2
9// verdict, tests/study_amr_seam_sample_order.cpp: degree-1 is an O(1) non-decaying matrix
10// perturbation at 53% of the physical scale; degree-2 decays ~O(h) to 0.4%). Fallback cascade
11// (fluid-only, M1-measured 0.1–0.5%): LS2 -> LS1 -> covering-leaf identity.
12//
13// CLASSIFICATION CONSISTENCY (the invariant that prevents double-counted fluxes): a face is
14// closed in the overlay iff it is closed in the binary operator. Both derive from the SAME
15// primitive — a (sub)face is open iff BOTH adjacent ACTUAL leaf centers are fluid and the
16// float mean of their center samples is >= 0 (`sampledFaceOpen`; use `makeBinaryOpenFnMixed`
17// for AmrPoisson::buildOpenness / AmrMultigrid::setOpenness). A row's own-face state takes
18// its SIGN from this canonical rule (any-sub-face-open for a finer-across face — the closed
19// sub-faces of a mixed face are then Neumann-zero, a counted Design-A-like local defect) and
20// its MAGNITUDE (theta, closure weights) from the row's virtual uniform-position samples (the
21// plan's D2 route, level-independent — identical to the classic builder on a uniform band).
22//
23// Everything is a pure function of (octree, sdf): deterministic under adaptivity (D6).
24// Host-only-safe: no Kokkos section — the device mirror is Phase-1 work.
25#ifndef PECLET_CORE_AMR_GHOST_PROJECTION_SAMPLED_HPP
26#define PECLET_CORE_AMR_GHOST_PROJECTION_SAMPLED_HPP
27
28#ifdef PECLET_CORE_HAVE_MORTON
29
30#include <array>
31#include <cmath>
32#include <cstdio>
33#include <cstdint>
34#include <vector>
35
41
42namespace peclet::core::amr {
43
49 std::vector<Index> sampStart;
50 std::vector<Index> sampIdx;
51 std::vector<double> sampW;
52 std::vector<int8_t> sampFluid;
53 std::vector<Index> rowOf;
54 // build census (printed by the builder):
55 long nIdentity = 0, nLS2 = 0, nLS1 = 0, nDegraded = 0, nSolidSlot = 0, nMixedFace = 0,
57};
58
59namespace detail {
60
62inline bool gpsSolveDense(int n, double* A, double* b) {
63 for (int k = 0; k < n; ++k) {
64 int piv = k;
65 for (int r = k + 1; r < n; ++r)
66 if (std::fabs(A[r * n + k]) > std::fabs(A[piv * n + k]))
67 piv = r;
68 if (std::fabs(A[piv * n + k]) < 1e-14)
69 return false;
70 if (piv != k) {
71 for (int c = k; c < n; ++c) {
72 const double tmp = A[k * n + c];
73 A[k * n + c] = A[piv * n + c];
74 A[piv * n + c] = tmp;
75 }
76 const double tb = b[k];
77 b[k] = b[piv];
78 b[piv] = tb;
79 }
80 for (int r = k + 1; r < n; ++r) {
81 const double f = A[r * n + k] / A[k * n + k];
82 for (int c = k; c < n; ++c)
83 A[r * n + c] -= f * A[k * n + c];
84 b[r] -= f * b[k];
85 }
86 }
87 for (int k = n - 1; k >= 0; --k) {
88 for (int c = k + 1; c < n; ++c)
89 b[k] -= A[k * n + c] * b[c];
90 b[k] /= A[k * n + k];
91 }
92 return true;
93}
94
95inline void gpsMonomials(const double d[3], int deg, double* m, int& nm) {
96 m[0] = 1.0;
97 m[1] = d[0];
98 m[2] = d[1];
99 m[3] = d[2];
100 nm = 4;
101 if (deg >= 2) {
102 m[4] = d[0] * d[0];
103 m[5] = d[1] * d[1];
104 m[6] = d[2] * d[2];
105 m[7] = d[0] * d[1];
106 m[8] = d[0] * d[2];
107 m[9] = d[1] * d[2];
108 nm = 10;
109 }
110}
111
112} // namespace detail
113
119template <unsigned Bits, class SdfFn>
121 SdfFn sdfFn, double h0, Vec<3> origin) {
122 return [&t, &pres, sdfFn, h0, origin](const Vec<3>& fc, int axis) -> double {
123 auto centerSample = [&](int side) -> std::pair<bool, float> {
124 Vec<3> probe = fc;
125 probe[axis] += side * 0.25 * h0; // strictly inside the adjacent leaf
126 std::array<long, 3> q{};
127 for (int d = 0; d < 3; ++d)
128 q[d] = static_cast<long>(std::floor((probe[d] - origin[d]) / h0));
129 const Index j = pres.probeSlot(q).first;
130 if (j < 0)
131 return {false, -1.0f};
132 auto b = t.bounds(j);
133 const double s = static_cast<double>(Index(1) << t.level(j));
134 Vec<3> c{};
135 for (int d = 0; d < 3; ++d)
136 c[d] = origin[d] + (static_cast<double>(b[0][d]) + 0.5 * s) * h0;
137 const double sd = sdfFn(c);
138 return {sd > 0.0, static_cast<float>(sd)};
139 };
140 const auto [flM, sM] = centerSample(-1);
141 const auto [flP, sP] = centerSample(+1);
142 return (flM && flP && 0.5f * (sM + sP) >= 0.0f) ? 1.0 : 0.0;
143 };
144}
145
149template <unsigned Bits, class SdfFn>
152 int matrixOrder, int rhsOrder,
153 Vec<3> origin = Vec<3>{}) {
154 GhostOverlaySampled ov;
155 const Index n = t.numLeaves();
156 const double h0 = pres.cellWidth(0) / static_cast<double>(Index(1) << t.level(0));
157
158 // Leaf centers + fluid flags.
159 std::vector<Vec<3>> cen(static_cast<std::size_t>(n));
160 std::vector<char> fluid(static_cast<std::size_t>(n));
161 for (Index i = 0; i < n; ++i) {
162 auto b = t.bounds(i);
163 const double s = static_cast<double>(Index(1) << t.level(i));
164 Vec<3> c{};
165 for (int d = 0; d < 3; ++d)
166 c[d] = origin[d] + (static_cast<double>(b[0][d]) + 0.5 * s) * h0;
167 cen[static_cast<std::size_t>(i)] = c;
168 fluid[static_cast<std::size_t>(i)] = sdf(c) > 0.0 ? 1 : 0;
169 }
170
171 // Hash bins over leaf centers for LS cloud gathering (bin 4*h0, periodic unit... the domain
172 // spans fineExt*h0 from origin; bin in fine units to stay geometry-agnostic).
173 const double hb = 4.0 * h0;
174 long nbx = 0;
175 {
176 // domain extent from the octree via a probe of the wrap in probeSlot is not exposed;
177 // derive from the max leaf bound at level 0 units.
178 long ext = 0;
179 for (Index i = 0; i < n; ++i) {
180 auto b = t.bounds(i);
181 for (int d = 0; d < 3; ++d)
182 ext = std::max(ext, static_cast<long>(b[1][d]));
183 }
184 nbx = std::max<long>(1, ext / 4); // ext fine cells / 4 per bin
185 }
186 const double domain = static_cast<double>(nbx) * hb; // world extent (cubic domains)
187 std::vector<std::vector<Index>> bins(static_cast<std::size_t>(nbx * nbx * nbx));
188 for (Index i = 0; i < n; ++i) {
189 const Vec<3>& c = cen[static_cast<std::size_t>(i)];
190 long bx = static_cast<long>((c[0] - origin[0]) / hb) % nbx;
191 long by = static_cast<long>((c[1] - origin[1]) / hb) % nbx;
192 long bz = static_cast<long>((c[2] - origin[2]) / hb) % nbx;
193 bins[static_cast<std::size_t>((bz * nbx + by) * nbx + bx)].push_back(i);
194 }
195
196 // LS functional at world position p, degree deg, radius rho, scale H: returns the (idx, w)
197 // list. Weight vector w_j = mono(d_j) . M^{-1} e0 with M the normal matrix.
198 auto lsFunctional = [&](const Vec<3>& p, double rho, double H, int deg,
199 std::vector<Index>& idx, std::vector<double>& w) -> bool {
200 idx.clear();
201 w.clear();
202 std::vector<Index> pts;
203 const long lo[3] = {static_cast<long>(std::floor((p[0] - origin[0] - rho) / hb)),
204 static_cast<long>(std::floor((p[1] - origin[1] - rho) / hb)),
205 static_cast<long>(std::floor((p[2] - origin[2] - rho) / hb))};
206 const long hi[3] = {static_cast<long>(std::floor((p[0] - origin[0] + rho) / hb)),
207 static_cast<long>(std::floor((p[1] - origin[1] + rho) / hb)),
208 static_cast<long>(std::floor((p[2] - origin[2] + rho) / hb))};
209 for (long bx = lo[0]; bx <= hi[0]; ++bx)
210 for (long by = lo[1]; by <= hi[1]; ++by)
211 for (long bz = lo[2]; bz <= hi[2]; ++bz) {
212 const long wx = (bx % nbx + nbx) % nbx, wy = (by % nbx + nbx) % nbx,
213 wz = (bz % nbx + nbx) % nbx;
214 for (Index j : bins[static_cast<std::size_t>((wz * nbx + wy) * nbx + wx)]) {
215 if (!fluid[static_cast<std::size_t>(j)])
216 continue;
217 double r2 = 0;
218 for (int d = 0; d < 3; ++d) {
219 double del = cen[static_cast<std::size_t>(j)][d] - p[d];
220 if (del > 0.5 * domain)
221 del -= domain;
222 if (del < -0.5 * domain)
223 del += domain;
224 r2 += del * del;
225 }
226 if (r2 <= rho * rho)
227 pts.push_back(j);
228 }
229 }
230 const int need = deg >= 2 ? 12 : 5;
231 if (static_cast<long>(pts.size()) < need)
232 return false;
233 double A[100] = {}, e0[10] = {};
234 int nm = 0;
235 double mono[10];
236 for (Index j : pts) {
237 double d[3];
238 for (int dd = 0; dd < 3; ++dd) {
239 double del = cen[static_cast<std::size_t>(j)][dd] - p[dd];
240 if (del > 0.5 * domain)
241 del -= domain;
242 if (del < -0.5 * domain)
243 del += domain;
244 d[dd] = del / H;
245 }
247 for (int r = 0; r < nm; ++r)
248 for (int c = 0; c < nm; ++c)
249 A[r * nm + c] += mono[r] * mono[c];
250 }
251 e0[0] = 1.0;
252 if (!detail::gpsSolveDense(nm, A, e0))
253 return false;
254 for (Index j : pts) {
255 double d[3];
256 for (int dd = 0; dd < 3; ++dd) {
257 double del = cen[static_cast<std::size_t>(j)][dd] - p[dd];
258 if (del > 0.5 * domain)
259 del -= domain;
260 if (del < -0.5 * domain)
261 del += domain;
262 d[dd] = del / H;
263 }
265 double wj = 0;
266 for (int k = 0; k < nm; ++k)
267 wj += mono[k] * e0[k];
268 idx.push_back(j);
269 w.push_back(wj);
270 }
271 return true;
272 };
273
274 ov.rowOf.assign(static_cast<std::size_t>(n), -1);
275 ov.sampStart.assign(1, 0);
276 std::vector<Index> sIdx;
277 std::vector<double> sW;
278
279 for (Index i = 0; i < n; ++i) {
280 if (!fluid[static_cast<std::size_t>(i)])
281 continue;
282 const unsigned Li = t.level(i);
283 const double h = pres.cellWidth(i);
284 const Vec<3>& c = cen[static_cast<std::size_t>(i)];
285
286 // Virtual uniform-position float samples (theta / closure magnitudes — the D2 route).
287 float Cq[3][5], F[3][4];
288 for (int a = 0; a < 3; ++a) {
289 for (int q = -2; q <= 2; ++q) {
290 Vec<3> p = c;
291 p[a] += static_cast<double>(q) * h;
292 Cq[a][q + 2] = static_cast<float>(sdf(p));
293 }
294 for (int m = 0; m < 4; ++m)
295 F[a][m] = 0.5f * (Cq[a][m] + Cq[a][m + 1]);
296 }
297 // Canonical own-face openness (any-sub-face-open across finer neighbours): force the SIGN
298 // of the own-face F entries to the canonical rule so overlay-closed <=> binary-closed.
299 bool anyOpen[6] = {}; // slot k = 2*axis + (0 plus, 1 minus)
300 int nSub[6] = {};
301 const float si0 = static_cast<float>(sdf(c));
302 pres.forEachFaceFull(i, [&](Index j, int axis, int dir, double, double, double) {
303 const int face = 2 * axis + (dir > 0 ? 0 : 1);
304 ++nSub[face];
305 if (j >= 0 && fluid[static_cast<std::size_t>(j)]) {
306 const float sj = static_cast<float>(sdf(cen[static_cast<std::size_t>(j)]));
307 if (0.5f * (si0 + sj) >= 0.0f)
308 anyOpen[face] = true;
309 }
310 });
311 for (int k = 0; k < 6; ++k) {
312 const int a = k / 2;
313 const int m = (k & 1) ? 1 : 2; // minus own face = F[a][1], plus own face = F[a][2]
314 const bool virtOpen =
315 F[a][m] >= 0.0f && Cq[a][(k & 1) ? 1 : 3] > 0.0f && Cq[a][2] > 0.0f;
316 if (virtOpen != anyOpen[k]) {
317 // Marginal face where the virtual and canonical classifications disagree: force the
318 // canonical sign (overlay-closed <=> binary-closed), keep the (small) magnitude.
319 ++ov.nSignForced;
320 const float mag = std::fabs(F[a][m]) > 0.0f ? std::fabs(F[a][m]) : 1e-6f;
321 F[a][m] = anyOpen[k] ? mag : -mag;
322 }
323 if (nSub[k] > 1 && !anyOpen[k])
324 ++ov.nMixedFace; // coarse face fully closed against finer neighbours (all subs closed)
325 }
326 // Non-clean test (canonical own faces + virtual +/-1 centers), as the classic builder.
327 bool clean = true;
328 for (int a = 0; a < 3; ++a)
329 clean = clean && Cq[a][1] >= 0.0f && Cq[a][3] >= 0.0f && F[a][1] >= 0.0f && F[a][2] >= 0.0f;
330 if (clean)
331 continue;
332
333 const int slot = static_cast<int>(ov.base.n);
334 auto resize = [&](int nRows) {
335 ov.base.cell.resize(static_cast<std::size_t>(nRows));
336 ov.base.rescale.resize(static_cast<std::size_t>(nRows));
337 ov.base.coupled.resize(static_cast<std::size_t>(nRows));
338 ov.base.state.resize(static_cast<std::size_t>(nRows) * 6);
339 ov.base.th.resize(static_cast<std::size_t>(nRows) * 6);
340 ov.base.w_bc.resize(static_cast<std::size_t>(nRows) * 6);
341 ov.base.w_n1.resize(static_cast<std::size_t>(nRows) * 6);
342 ov.base.w_n2.resize(static_cast<std::size_t>(nRows) * 6);
343 ov.base.wm_n1.resize(static_cast<std::size_t>(nRows) * 6);
344 ov.base.wm_n2.resize(static_cast<std::size_t>(nRows) * 6);
345 };
346 resize(slot + 1);
347 detail::GhostOverlayRef ref{ov.base};
348 if (!scheme::gpFillRow(ref, slot, i, F, Cq, matrixOrder, rhsOrder)) {
349 resize(slot);
350 continue;
351 }
352
353 // Sample functionals for the 15 chain slots.
354 for (int a = 0; a < 3; ++a)
355 for (int q = -2; q <= 2; ++q) {
356 Vec<3> p = c;
357 p[a] += static_cast<double>(q) * h;
358 // Recover the covering leaf: floor the world position to fine units (a level-L cell
359 // center is lo + 0.5*2^L in fine units, so the floor lands inside the cell).
360 std::array<long, 3> lc{};
361 for (int d = 0; d < 3; ++d)
362 lc[d] = static_cast<long>(std::floor((p[d] - origin[d]) / h0));
363 const Index j = pres.probeSlot(lc).first;
364 const bool fluidP = sdf(p) > 0.0;
365 ov.sampFluid.push_back(fluidP ? 1 : 0);
366 if (q == 0 || (j >= 0 && t.level(j) == Li)) {
367 // own cell or same-level cover: identity (the classic chain; bit-identical band).
368 if (j >= 0 && fluid[static_cast<std::size_t>(j)] == (fluidP ? 1 : 0)) {
369 sIdx.push_back(j);
370 sW.push_back(1.0);
371 ++ov.nIdentity;
372 } else if (j >= 0) {
373 sIdx.push_back(j); // marginal float disagreement: still the covering value
374 sW.push_back(1.0);
375 ++ov.nIdentity;
376 }
377 ov.sampStart.push_back(static_cast<Index>(sIdx.size()));
378 continue;
379 }
380 if (!fluidP) {
381 // solid virtual position: empty functional (reads the masked 0).
382 ++ov.nSolidSlot;
383 ov.sampStart.push_back(static_cast<Index>(sIdx.size()));
384 continue;
385 }
386 const double H = (j >= 0) ? pres.cellWidth(j) : h;
387 const double rho = 2.2 * std::max(h, H);
388 std::vector<Index> idx;
389 std::vector<double> w;
390 if (lsFunctional(p, rho, H, 2, idx, w)) {
391 ++ov.nLS2;
392 } else if (lsFunctional(p, rho, H, 1, idx, w)) {
393 ++ov.nLS1;
394 } else if (j >= 0 && fluid[static_cast<std::size_t>(j)]) {
395 idx.assign(1, j);
396 w.assign(1, 1.0);
397 ++ov.nDegraded;
398 } else {
399 ++ov.nDegraded; // no support at all: reads 0 (fluid-only cascade floor)
400 }
401 for (std::size_t k = 0; k < idx.size(); ++k) {
402 sIdx.push_back(idx[k]);
403 sW.push_back(w[k]);
404 }
405 ov.sampStart.push_back(static_cast<Index>(sIdx.size()));
406 }
407 ov.base.invh.push_back(1.0 / h);
408 ov.rowOf[static_cast<std::size_t>(i)] = ov.base.n;
409 ++ov.base.n;
410 }
411 ov.sampIdx = std::move(sIdx);
412 ov.sampW = std::move(sW);
413 std::fprintf(stderr,
414 "[peclet.core.amr] sampled ghost overlay: %lld rows | slots identity %ld, LS2 %ld, "
415 "LS1 %ld, degraded %ld, solid %ld | sign-forced faces %ld, closed mixed faces %ld\n",
416 static_cast<long long>(ov.base.n), ov.nIdentity, ov.nLS2, ov.nLS1, ov.nDegraded,
417 ov.nSolidSlot, ov.nSignForced, ov.nMixedFace);
418 return ov;
419}
420
422inline double gpsSample(const GhostOverlaySampled& ov, Index r, int a, int q,
423 const std::vector<double>& x) {
424 const std::size_t s = static_cast<std::size_t>(r * 15 + a * 5 + (q + 2));
425 double v = 0.0;
426 for (Index k = ov.sampStart[s]; k < ov.sampStart[s + 1]; ++k)
427 v += ov.sampW[static_cast<std::size_t>(k)] *
428 x[static_cast<std::size_t>(ov.sampIdx[static_cast<std::size_t>(k)])];
429 return v;
430}
431
434inline void ghostApplyDeltaSampledHost(const GhostOverlaySampled& ov, const std::vector<double>& x,
435 std::vector<double>& y) {
436 const GhostOverlay& g = ov.base;
437 for (Index r = 0; r < g.n; ++r) {
438 const std::size_t rr = static_cast<std::size_t>(r);
439 const Index c = g.cell[rr];
440 if (!g.coupled[rr]) {
441 y[static_cast<std::size_t>(c)] = 0.0;
442 continue;
443 }
444 auto X = [&](int a, int q) { return gpsSample(ov, r, a, q, x); };
445 double delta = 0.0;
446 for (int k = 0; k < 6; ++k) {
447 const int8_t st = g.state[rr * 6 + static_cast<std::size_t>(k)];
448 if (st != scheme::GP_QUAD && st != scheme::GP_LIN)
449 continue;
450 const int a = k / 2;
451 const int sgn = (k & 1) ? -1 : 1;
452 const int mn = (k & 1) ? 1 : 0;
453 const int mf = (k & 1) ? 2 : -1;
454 const double w1 = g.wm_n1[rr * 6 + static_cast<std::size_t>(k)];
455 const double w2 = g.wm_n2[rr * 6 + static_cast<std::size_t>(k)];
456 delta += sgn * w1 * (X(a, mn) - X(a, mn - 1));
457 if (st == scheme::GP_QUAD && w2 != 0.0)
458 delta += sgn * w2 * (X(a, mf) - X(a, mf - 1));
459 }
460 const double ih = g.invh[rr];
461 y[static_cast<std::size_t>(c)] = g.rescale[rr] * (y[static_cast<std::size_t>(c)] + ih * ih * delta);
462 }
463}
464
467 const std::array<std::vector<double>, 3>& u,
468 std::vector<double>& d) {
469 const GhostOverlay& g = ov.base;
470 for (Index r = 0; r < g.n; ++r) {
471 const std::size_t rr = static_cast<std::size_t>(r);
472 const Index c = g.cell[rr];
473 if (!g.coupled[rr]) {
474 d[static_cast<std::size_t>(c)] = 0.0;
475 continue;
476 }
477 auto U = [&](int a, int m) { // face-averaged value at face index i+m along axis a
478 return 0.5 * (gpsSample(ov, r, a, m - 1, u[static_cast<std::size_t>(a)]) +
479 gpsSample(ov, r, a, m, u[static_cast<std::size_t>(a)]));
480 };
481 double dd = 0.0;
482 for (int k = 0; k < 6; ++k) {
483 const int8_t st = g.state[rr * 6 + static_cast<std::size_t>(k)];
484 if (st == scheme::GP_COUPLED)
485 continue;
486 const int a = k / 2;
487 const int sgn = (k & 1) ? -1 : 1;
488 const int mg = (k & 1) ? 0 : 1;
489 const int mn = (k & 1) ? 1 : 0;
490 const int mf = (k & 1) ? 2 : -1;
491 if (st == scheme::GP_EXPLICIT) {
492 dd += sgn * U(a, mg);
493 continue;
494 }
495 if (st == scheme::GP_BC_ONLY)
496 continue;
497 double val = g.w_n1[rr * 6 + static_cast<std::size_t>(k)] * U(a, mn);
498 if (st == scheme::GP_QUAD)
499 val += g.w_n2[rr * 6 + static_cast<std::size_t>(k)] * U(a, mf);
500 dd += sgn * val;
501 }
502 d[static_cast<std::size_t>(c)] =
503 g.rescale[rr] * (d[static_cast<std::size_t>(c)] + g.invh[rr] * dd);
504 }
505}
506
512inline double gpsDirGrad(const GhostOverlaySampled& ov, Index r, const std::vector<double>& fld,
513 int c, double invh) {
514 auto fl = [&](int q) {
515 return ov.sampFluid[static_cast<std::size_t>(r * 15 + c * 5 + (q + 2))] != 0;
516 };
517 auto F = [&](int q) { return gpsSample(ov, r, c, q, fld); };
518 const bool ap = fl(+1), am = fl(-1);
519 if (am && ap)
520 return (F(+1) - F(-1)) * 0.5 * invh;
521 if (ap)
522 return fl(+2) ? (-3.0 * F(0) + 4.0 * F(+1) - F(+2)) * 0.5 * invh : (F(+1) - F(0)) * invh;
523 if (am)
524 return fl(-2) ? (3.0 * F(0) - 4.0 * F(-1) + F(-2)) * 0.5 * invh : (F(0) - F(-1)) * invh;
525 return 0.0;
526}
527
528} // namespace peclet::core::amr
529
530#endif // PECLET_CORE_HAVE_MORTON
531#endif // PECLET_CORE_AMR_GHOST_PROJECTION_SAMPLED_HPP
Cell-centered FV Poisson operator on one (periodic) block octree.
Definition poisson.hpp:44
Per-block adaptive octree over block-local Morton codes.
bool gpsSolveDense(int n, double *A, double *b)
Gaussian elimination with partial pivoting (small dense normal equations).
void gpsMonomials(const double d[3], int deg, double *m, int &nm)
auto makeBinaryOpenFnMixed(const BlockOctree< 3, Bits > &t, const AmrPoisson< 3, Bits > &pres, SdfFn sdfFn, double h0, Vec< 3 > origin)
The canonical (sub)face openness for mixed-level cut bands: open iff both adjacent ACTUAL leaf center...
void ghostDivergDeltaSampledHost(const GhostOverlaySampled &ov, const std::array< std::vector< double >, 3 > &u, std::vector< double > &d)
Divergence overlay (sampled): the sampled analog of ghostDivergDeltaHost (u_bc = 0).
void ghostApplyDeltaSampledHost(const GhostOverlaySampled &ov, const std::vector< double > &x, std::vector< double > &y)
Matrix overlay (sampled): y currently holds the BINARY L matvec; overwrite overlay rows with y = rho·...
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
double gpsSample(const GhostOverlaySampled &ov, Index r, int a, int q, const std::vector< double > &x)
Evaluate slot (r, a, q) of a scalar leaf field. Empty functional reads 0.
GhostOverlaySampled buildGhostOverlaySampled(const BlockOctree< 3, Bits > &t, const AmrPoisson< 3, Bits > &pres, SdfFn &&sdf, int matrixOrder, int rhsOrder, Vec< 3 > origin=Vec< 3 >{})
Build the sampled overlay.
double gpsDirGrad(const GhostOverlaySampled &ov, Index r, const std::vector< double > &fld, int c, double invh)
Directional ghost cell-gradient on a sampled row (the mixed-level gradOfDir): the same cascade — cent...
PECLET_CORE_GP_HD bool gpFillRow(const OV &ov, int slot, CellId cellId, const float F[3][4], const float Cq[3][5], int matrixOrder, int rhsOrder, const float *exStd=nullptr, const float *exSliver=nullptr)
Fill one overlay row from the per-axis sample sets.
std::array< Real, Dim > Vec
Multi-dimensional real vector.
Definition types.hpp:26
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15
Sampled overlay: the classic per-row fields (weights, states, rescale — base; base....
std::vector< Index > rowOf
[numLeaves] row index of a leaf, -1 if none
std::vector< int8_t > sampFluid
[base.n*15] virtual position is fluid (gradient cascade)
std::vector< Index > sampStart
[base.n*15 + 1]
Host ghost-projection overlay: one row per non-clean fluid leaf (== cut cell: some ±1 center sample s...
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< int8_t > state
[n*6]
std::vector< float > wm_n2
[n*6] matrix (implicit phi) weights (matrixOrder)
std::vector< double > invh
[n] 1/cellWidth of the row (finest band)
std::vector< Index > cell
[n] leaf index
std::vector< int8_t > coupled
[n] 1 if the row has any phi coupling at all