peclet.voro 1.0.4
Device-native moving-particle Voronoi dynamics
Loading...
Searching...
No Matches
repair.hpp
Go to the documentation of this file.
1
44#ifndef PECLET_VORO_REPAIR_HPP
45#define PECLET_VORO_REPAIR_HPP
46
47#include <cmath>
48#include <Kokkos_Core.hpp>
49#include <string>
50
51#include "peclet/core/common/view.hpp"
57
58namespace peclet::voro {
59
63 enum Route { kTwoPass = 0, kDilated = 1, kRebuildGate = 2 };
64 int pass1 = 0;
65 int pass1Raw = 0;
66 int pass2 = 0;
67 int extra = 0;
68 int surgical = 0;
69 int verifyPasses = 0;
70 int wallFlagged = 0;
71 int nearFlagged = 0;
73 bool fellBack = false;
74};
75
78 long buried = 0;
79 long reachExceeded = 0;
80 long empty = 0;
81 long overflow = 0;
82 long incomplete = 0;
84};
85
88template <class Real, int MAXP = kMaxPlanes, int MAXT = kMaxTriangles, bool Weighted = false,
89 class Sdf = NoSdf>
91 using Mem = peclet::core::MemSpace;
92 using Exec = peclet::core::ExecSpace;
93 using PlanePolicy = std::conditional_t<Weighted, Power, Voronoi>; // radical vs bisector planes
94 static constexpr bool kHasSdf = !std::is_same_v<Sdf, NoSdf>;
95 // The certificate/load/re-eval path reads the resident `poke4` plane set by pointer and never
96 // touches the edge adjacency, so it uses a LEAN cell with NO `adj` member (TrackAdj=false) —
97 // ~MAXT*3*4 bytes smaller per thread, which lifts occupancy on the full-N certify (the GPU
98 // repair's hot kernel). Only the cell-BUILDING paths (the gather's CellBuilder, rebuildAdjAll,
99 // surgicalRepair) need `adj` to derive poke4, so they use the TrackAdj=true BuildCell.
103
104 int N = 0;
105 int nProc =
106 0;
109 Real L[3] = {1, 1, 1};
110 Real tol =
111 0;
112 Real skin = 0;
113 int verifyCap = 2;
114
115 // ---- Phase 3: adaptive three-way gate (decided after the free certificate, before any gather)
116 // ----
117 bool useGate = true;
118 bool profile = false;
121 false;
125 double churnThresh =
126 0.55;
129 0.15;
131 int clusterNbhd = 18;
133 // ---- Phase 4: single-face surgical repair (no grid gather; gated, default OFF — see note) ----
134 // NEGATIVE RESULT (measured): on this engine surgical re-clip is BOTH slower and not robustly
135 // exact, so it stays off. (1) Slower: the cold gather clips closest-first with the
136 // security-radius early-out (the cell tightens fast, few live triangles per clip), whereas
137 // re-clipping the stored+partner candidates unsorted keeps the cell large and costs more O(#tri)
138 // horizon scans — the gather wins. (2) Not exact: a surgically-rebuilt cell that GAINS a
139 // neighbour absent from its candidate set stays convex, so the certificate can't see the gain
140 // (§1c) and the verify loop can't catch it; the small per-step error then COMPOUNDS in the
141 // resident store (maxRelV grew to ~0.5 over a sweep). Making it exact needs the maintained
142 // 1-/2-ring candidate set (the ConnectivityArena the plan parks). Kept as the implemented
143 // experiment behind this flag; the production path is the Phase-3 gated two-pass gather.
144 bool surgical =
145 false;
146 static constexpr int kExtraCap = 8;
147 // ---- Lawson local convexity certificate (the cheap O(nt) convexity test) ----
149 true;
156 bool adjFresh =
157 false;
161 bool useLocalNow = false;
162
163 Kokkos::View<int*, Mem> lastStatus;
166 BuildReport r;
167 if (lastStatus.extent(0) == 0)
168 return r;
169 auto h = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, lastStatus);
170 for (int i = 0; i < nProc; ++i) {
171 const int s = h(i);
172 r.buried += (s & kBuried) ? 1 : 0;
173 r.reachExceeded += (s & kReachExceeded) ? 1 : 0;
174 r.empty += (s & kEmpty) ? 1 : 0;
175 r.overflow += (s & kOverflow) ? 1 : 0;
176 r.incomplete += (s & kIncomplete) ? 1 : 0;
177 }
179 return r;
180 }
181
183 Kokkos::View<Real*, Mem> vol; // N : cell volumes (the published geometry scalar)
184 Kokkos::View<Real*, Mem> xRef; // 3N : positions at the last (re)build (Verlet reference)
185 Kokkos::View<Real*, Mem> weight; // N : per-seed power weights (empty for Voronoi); DOFs
186 // alongside positions. Set via setWeights before rebuild/step.
187
189 void setWeights(const Kokkos::View<Real*, Mem>& w) { weight = w; }
190
191 // ---- near-miss (face-gain) completeness ----
192 // The seed-local certificate cannot see a face GAINED from a seed that is not in the stored
193 // topology (its bisector drifts into the cell without either seed moving past the Verlet
194 // skin): measured ~0.1 % of neighbour relations missed per step, ~1e-3 volume error over long
195 // runs. Every (re)build records the candidates whose plane missed the cell by less than
196 // `nearMargin` (a multiple of the skin); the certificate re-tests those planes on the current
197 // positions and flags both cells when one now cuts. Set useNearMiss=false for the old (cheaper,
198 // incomplete) certificate.
199 bool useNearMiss = true;
200 int kNearCap = 32;
201 Real nearMarginFrac = Real(0.5);
202 bool nearFresh = false;
206 Kokkos::View<int*, Mem> near, nearCnt;
207 Kokkos::View<int, Mem> nearCounter;
208
209 // ---- SDF solid (rung A0) ----
210 Sdf sdf;
212 bool wallExact = true;
213 Real wallSkin = 0;
214 Kokkos::View<int, Mem> wallCounter;
215 // scratch
216 Kokkos::View<int*, Mem> mask, mask2, mover, rebuilt, wl1, wl2, wlM;
217 Kokkos::View<int*, Mem> cellFlag; // per linear grid-cell flagged-seed count (dilation)
218 Kokkos::View<int*, Mem> extraNbr, extraCnt; // Phase-4 partner-discovered candidate neighbours
219 Kokkos::View<int, Mem> counter;
220 int gridNcell = 0;
221
226 void alloc(int n, const Real Lbox[3], Real tol_, Real skin_, int sw_ = kSearchWindow,
227 int densityCount_ = -1, int nProc_ = -1) {
228 N = n;
229 nProc = (nProc_ < 0) ? n : nProc_;
230 sw = sw_;
231 densityCount = densityCount_ < 0 ? n : densityCount_;
232 tol = tol_;
233 skin = skin_;
234 for (int k = 0; k < 3; ++k)
235 L[k] = Lbox[k];
236 store.alloc(n);
237 store.allocPoke4(); // resident local-cert plane set (computed by the gather, read each step by
238 // the cert)
239 using Kokkos::view_alloc;
240 using Kokkos::WithoutInitializing;
241 vol = Kokkos::View<Real*, Mem>("mt.vol", n);
242 xRef = Kokkos::View<Real*, Mem>(view_alloc(std::string("mt.xRef"), WithoutInitializing),
243 (size_t)n * 3);
244 mask = Kokkos::View<int*, Mem>("mt.mask", n);
245 mask2 = Kokkos::View<int*, Mem>("mt.mask2", n);
246 mover = Kokkos::View<int*, Mem>("mt.mover", n);
247 rebuilt = Kokkos::View<int*, Mem>("mt.rebuilt", n);
248 wl1 = Kokkos::View<int*, Mem>(view_alloc(std::string("mt.wl1"), WithoutInitializing), n);
249 wl2 = Kokkos::View<int*, Mem>(view_alloc(std::string("mt.wl2"), WithoutInitializing), n);
250 wlM = Kokkos::View<int*, Mem>(view_alloc(std::string("mt.wlM"), WithoutInitializing), n);
251 extraNbr = Kokkos::View<int*, Mem>(view_alloc(std::string("mt.extraNbr"), WithoutInitializing),
252 (size_t)n * kExtraCap);
253 extraCnt = Kokkos::View<int*, Mem>("mt.extraCnt", n);
254 counter = Kokkos::View<int, Mem>("mt.counter");
255 // Per-backend gate threshold: rebuild is cheap + clip-bound on GPU (repair loses past ~50%
256 // flagged), expensive on the CPU paths (repair pays out to ~85%). Hand-set from the
257 // cross-device sweep; the plan's Phase-3 tuning would fit these from logged (signals vs
258 // verify-clean) per backend.
259 constexpr bool host = Kokkos::SpaceAccessibility<Kokkos::HostSpace, Mem>::accessible;
260 churnThresh = host ? 0.70 : 0.50;
261 if (useNearMiss) {
262 near = Kokkos::View<int*, Mem>(view_alloc(std::string("mt.near"), WithoutInitializing),
263 (size_t)n * kNearCap);
264 nearCnt = Kokkos::View<int*, Mem>("mt.nearCnt", n);
265 nearCounter = Kokkos::View<int, Mem>("mt.nearCounter");
266 }
267 if constexpr (kHasSdf) {
268 wall.alloc(n);
269 wallCounter = Kokkos::View<int, Mem>("mt.wallCounter");
270 surgical = false; // the surgical re-clip knows nothing of walls; the gather does
271 }
272 }
273
276 KOKKOS_INLINE_FUNCTION static void dispFromRef(const Kokkos::View<Real*, Mem>& P,
277 const Kokkos::View<Real*, Mem>& XR, int i, Real Lx,
278 Real Ly, Real Lz, Real& dx, Real& dy, Real& dz) {
279 dx = P(3 * i) - XR(3 * i);
280 dy = P(3 * i + 1) - XR(3 * i + 1);
281 dz = P(3 * i + 2) - XR(3 * i + 2);
282 const Real Lxh = Real(0.5) * Lx, Lyh = Real(0.5) * Ly, Lzh = Real(0.5) * Lz;
283 dx = dx > Lxh ? dx - Lx : (dx < -Lxh ? dx + Lx : dx);
284 dy = dy > Lyh ? dy - Ly : (dy < -Lyh ? dy + Ly : dy);
285 dz = dz > Lzh ? dz - Lz : (dz < -Lzh ? dz + Lz : dz);
286 }
287
296 void rebuild(const Kokkos::View<Real*, Mem>& pos, bool eagerAdj = true) {
297 Kokkos::View<long*, Mem> gd;
298 const int nBuild =
299 (nProc == N) ? -1 : nProc; // build only the owned cells; ghosts are candidates
300 // A gate-rebuild (eagerAdj=false) stays in the high-churn regime and skips the near-miss
301 // emission like it skips the adjacency; the lists are rebuilt once churn drops (step()).
302 const bool emitNear = useNearMiss && eagerAdj;
303 auto res = buildTessellation<Real, Weighted, Sdf>(
304 pos, weight, N, L, sw, densityCount, gd, sdf, /*withForceGeom=*/false, nBuild, store.np,
305 store.nt, store.pnbr, store.tri, /*outCand=*/{}, /*outCandCnt=*/{}, /*candCap=*/0,
306 /*wlc=*/nullptr, wall, /*withAreaGrad=*/false, emitNear ? near : Kokkos::View<int*, Mem>{},
307 emitNear ? nearCnt : Kokkos::View<int*, Mem>{}, emitNear ? kNearCap : 0,
308 nearMarginFrac * skin, /*withWallFD=*/false, /*withMoments=*/false, profile);
309 nearFresh = emitNear;
310 lastStatus = res.status;
311 lastOverBufferRebuilds = res.overBufferRebuilds;
312 Kokkos::deep_copy(vol, res.view.cellVolume);
313 Kokkos::deep_copy(xRef, pos);
314 adjFresh = false; // the cold build does not emit adjacency
315 if (eagerAdj)
316 maybeBuildAdj(pos); // the whole topology changed -> repopulate store.poke4 (unless gating)
317 }
318
320 int compact(const Kokkos::View<int*, Mem>& m, const Kokkos::View<int*, Mem>& wl) {
321 Kokkos::deep_copy(counter, 0);
322 auto M = m;
323 auto W = wl;
324 auto c = counter;
325 Kokkos::parallel_for(
326 "mt.compact", Kokkos::RangePolicy<Exec>(0, nProc), KOKKOS_LAMBDA(int i) {
327 if (M(i))
328 W(Kokkos::atomic_fetch_add(&c(), 1)) = i;
329 });
330 int h = 0;
331 Kokkos::deep_copy(h, counter);
332 return h;
333 }
334
343 template <bool Local>
344 void certify(const Kokkos::View<Real*, Mem>& pos, const Kokkos::View<int*, Mem>& outMask,
345 const Kokkos::View<int*, Mem>& outMover, bool useSkin) {
346 Kokkos::deep_copy(outMask, 0);
347 if (useSkin)
348 Kokkos::deep_copy(outMover, 0);
349 // Phase-4: in the Pass-1 certify, also record partner-discovered new neighbours for the
350 // surgical path. The gaining cells of a flip are the violated-plane partners; each pair gains
351 // the other, so scatter every other partner of a flagging cell onto each partner's
352 // extra-candidate list (no dedup — duplicates are harmless no-op clips; overflow past kExtraCap
353 // just truncates and the cell falls back to a full gather at verify). Only in Pass 1 (useSkin),
354 // and only when surgical is enabled.
355 const bool doExtra = surgical && useSkin;
356 if (doExtra)
357 Kokkos::deep_copy(extraCnt, 0);
358 auto EN = extraNbr;
359 auto EC = extraCnt;
360 constexpr int kCap = kExtraCap;
361 auto P = pos;
362 auto XR = xRef;
363 auto Vv = vol;
364 auto M = outMask;
365 auto Mv = outMover;
366 Store st = store;
367 auto Pk4 = store.poke4;
368 const Real Lx = L[0], Ly = L[1], Lz = L[2];
369 const Real Lxh = Real(0.5) * Lx, Lyh = Real(0.5) * Ly, Lzh = Real(0.5) * Lz;
370 const Real tolL = tol, half2 = Real(0.25) * skin * skin;
371 const bool skinOn = useSkin;
372 const int nP_ = nProc;
373 auto Wt =
374 weight; // power weights (empty for Voronoi; captured for reevalGeometry<PlanePolicy>)
375 // near-miss (face-gain) check captures
376 auto NR = near;
377 auto NC = nearCnt;
378 auto NCtr = nearCounter;
379 const int nearCapL = kNearCap; // a local: a member read inside the lambda would capture `this`
380 const bool nearOn = useNearMiss && useSkin && nearFresh; // once per step (Pass-1 certify)
381 if (nearOn)
382 Kokkos::deep_copy(nearCounter, 0);
383 // SDF boundary watch captures (force-captured outside the constexpr-if, nvcc rule)
385 Sdf Sd = sdf;
386 auto WC = wallCounter;
387 const bool wallExactL = wallExact;
388 const Real wallSkin2 = wallSkin * wallSkin;
389 if constexpr (kHasSdf)
390 Kokkos::deep_copy(wallCounter, 0);
391 Kokkos::parallel_for(
392 "mt.certify", Kokkos::RangePolicy<Exec>(0, nProc), KOKKOS_LAMBDA(int i) {
393 CertCell c;
394 st.load(i, c, Lx, Ly, Lz);
395 Real dx, dy, dz;
396 dispFromRef(P, XR, i, Lx, Ly, Lz, dx, dy, dz);
397 const bool wallOk = Wl.load(i, c, dx, dy, dz); // no-op (true) without a wall store
398 // force-capture everything the constexpr boundary watch reads OUTSIDE it (nvcc rule)
399 const Sdf SdC = Sd;
400 int* wcPtr = WC.data();
401 const bool wallExactC = wallExactL;
402 const Real wallSkin2C = wallSkin2;
403 const Real* wPtr = Wt.data(); // force-capture Wt OUTSIDE the constexpr-if (nvcc rule)
404 Real wSelfI = Real(0);
405 if constexpr (Weighted)
406 wSelfI = Wt(i);
407 c.template reevalGeometry<PlanePolicy>(P(3 * i), P(3 * i + 1), P(3 * i + 2), P.data(), Lx,
408 wSelfI, wPtr);
409 Vv(i) = c.volumePerVertex();
410 int partners[32];
411 int nP = 0;
412 bool ok;
413 // Force-capture every lambda capture used in the branches OUTSIDE the constexpr-if (nvcc:
414 // an extended __host__ __device__ lambda cannot FIRST-capture a variable inside a
415 // constexpr-if).
416 const unsigned char* pk4 = &Pk4((size_t)i * MAXT * 4); // poke4 is always allocated
417 const Real tl = tolL;
418 if constexpr (Local)
419 ok = c.isLocallyConvexPartners(pk4, tl, partners, 32, nP);
420 else {
421 (void)pk4;
422 ok = c.isSelfConsistentPartners(tl, partners, 32, nP);
423 }
424 bool mover = false;
425 if (skinOn) {
426 mover = (dx * dx + dy * dy + dz * dz) > half2;
427 if (mover)
428 Mv(i) = 1;
429 }
430 // ---- SDF boundary watch (rung A0): what the seed-seed certificate cannot see ----
431 bool wflag = false;
432 if constexpr (kHasSdf) {
433 const Real seed[3] = {P(3 * i), P(3 * i + 1), P(3 * i + 2)};
434 if (!wallOk) {
435 wflag = true; // crossed one of its own wall planes
436 } else if (Wl.cnt(i) > 0) {
437 if (wallExactC || (dx * dx + dy * dy + dz * dz) > wallSkin2C) {
438 wflag = true; // a moved wall cell: only a fresh clip reproduces the cold build
439 } else { // skin mode: keep the planes unless the seed or a vertex entered the
440 // solid (a sagitta-placed plane sits beyond the surface, so the seed can
441 // enter the solid without crossing its own plane)
442 if (SdC.eval(seed[0], seed[1], seed[2]) <= Real(0))
443 wflag = true;
444 for (int t = 0; t < c.nt && !wflag; ++t)
445 if (c.alive[t] && SdC.eval(seed[0] + c.vx[t], seed[1] + c.vy[t],
446 seed[2] + c.vz[t]) < Real(-1e-8))
447 wflag = true;
448 }
449 } else if (c.nt == 0) {
450 wflag = SdC.eval(seed[0], seed[1], seed[2]) > Real(0); // seed back in the fluid
451 } else {
452 wflag = sdfWouldClip(c, seed, SdC); // the cold build would cut this cell
453 }
454 if (wflag)
455 Kokkos::atomic_inc(wcPtr);
456 }
457 // near-miss check: does any recorded non-neighbour plane now cut the cell?
458 bool nflag = false;
459 if (nearOn) {
460 const int nc = NC(i);
461 if (nc > nearCapL) {
462 nflag = true; // list overflowed at build: re-gather conservatively
463 } else {
464 const Real sx = P(3 * i), sy = P(3 * i + 1), sz = P(3 * i + 2);
465 for (int q = 0; q < nc; ++q) {
466 const int j = NR((size_t)i * nearCapL + q);
467 if (j < 0)
468 continue;
469 Real rx = P(3 * j) - sx, ry = P(3 * j + 1) - sy, rz = P(3 * j + 2) - sz;
470 rx = rx > Lxh ? rx - Lx : (rx < -Lxh ? rx + Lx : rx);
471 ry = ry > Lyh ? ry - Ly : (ry < -Lyh ? ry + Ly : ry);
472 rz = rz > Lzh ? rz - Lz : (rz < -Lzh ? rz + Lz : rz);
473 const Real pv[3] = {rx, ry, rz};
474 Real wN = Real(0);
475 if constexpr (Weighted)
476 wN = Wt(j);
477 const Real off = PlanePolicy::template offsetFromRel<Real>(pv, wSelfI, wN);
478 if (c.planeGap(pv, off) > Real(0)) {
479 nflag = true;
480 if (j < nP_)
481 Kokkos::atomic_exchange(&M(j), 1); // the gain is mutual
482 }
483 }
484 }
485 if (nflag)
486 Kokkos::atomic_inc(&NCtr());
487 }
488 if (!ok || mover || wflag || nflag)
489 M(i) = 1;
490 // only mark partners we MAINTAIN (owned); a ghost partner is the owning rank's
491 // responsibility.
492 for (int q = 0; q < nP; ++q)
493 if (partners[q] < nP_)
494 Kokkos::atomic_exchange(&M(partners[q]), 1);
495 if (doExtra) { // each pair of gaining cells gains the other -> seed the surgical
496 // candidate lists
497 for (int q = 0; q < nP; ++q) {
498 const int cell = partners[q];
499 if (cell < 0 || cell >= nP_)
500 continue;
501 for (int r = 0; r < nP; ++r) {
502 if (r == q)
503 continue;
504 const int nb = partners[r];
505 if (nb < 0)
506 continue;
507 const int slot = Kokkos::atomic_fetch_add(&EC(cell), 1);
508 if (slot < kCap)
509 EN((size_t)cell * kCap + slot) = nb;
510 }
511 }
512 }
513 });
514 }
515
524 template <bool Local>
525 void certifyList(const Kokkos::View<Real*, Mem>& pos, const Kokkos::View<int*, Mem>& list, int n,
526 const Kokkos::View<int*, Mem>& outMask) {
527 Kokkos::deep_copy(outMask, 0);
528 if (n <= 0)
529 return;
530 auto P = pos;
531 auto Vv = vol;
532 auto M = outMask;
533 Store st = store;
534 auto Pk4 = store.poke4;
535 const Real Lx = L[0], Ly = L[1], Lz = L[2];
536 const Real tolL = tol;
537 const int nP_ = nProc;
538 auto Wt = weight;
540 auto XR = xRef;
541 Kokkos::parallel_for(
542 "mt.certifyList", Kokkos::RangePolicy<Exec>(0, n), KOKKOS_LAMBDA(int s) {
543 const int i = list(s);
544 CertCell c;
545 st.load(i, c, Lx, Ly, Lz);
546 {
547 Real dx, dy, dz;
548 dispFromRef(P, XR, i, Lx, Ly, Lz, dx, dy, dz);
549 Wl.load(i, c, dx, dy, dz); // no-op without a wall store
550 }
551 const Real* wPtr = Wt.data(); // force-capture Wt OUTSIDE the constexpr-if (nvcc rule)
552 Real wSelfI = Real(0);
553 if constexpr (Weighted)
554 wSelfI = Wt(i);
555 c.template reevalGeometry<PlanePolicy>(P(3 * i), P(3 * i + 1), P(3 * i + 2), P.data(), Lx,
556 wSelfI, wPtr);
557 Vv(i) = c.volumePerVertex();
558 int partners[32];
559 int nP = 0;
560 bool ok;
561 const unsigned char* pk4 =
562 &Pk4((size_t)i * MAXT * 4); // force-capture poke4 + tol OUTSIDE the
563 const Real tl = tolL; // constexpr-if (nvcc extended-lambda rule)
564 if constexpr (Local)
565 ok = c.isLocallyConvexPartners(pk4, tl, partners, 32, nP);
566 else {
567 (void)pk4;
568 ok = c.isSelfConsistentPartners(tl, partners, 32, nP);
569 }
570 if (!ok)
571 M(i) = 1;
572 for (int q = 0; q < nP; ++q)
573 if (partners[q] < nP_)
574 Kokkos::atomic_exchange(&M(partners[q]), 1);
575 });
576 }
577
583 void rebuildAdjAll(const Kokkos::View<Real*, Mem>& pos) {
584 if (!localCert)
585 return;
586 Store st = store;
587 auto P = pos;
588 auto Pk4 = store.poke4;
589 const Real Lx = L[0], Ly = L[1], Lz = L[2];
590 auto Wt = weight;
592 auto XR = xRef;
593 Kokkos::parallel_for(
594 "mt.rebuildAdjAll", Kokkos::RangePolicy<Exec>(0, nProc), KOKKOS_LAMBDA(int i) {
595 BuildCell c;
596 st.load(i, c, Lx, Ly,
597 Lz); // load brings topology; adj is rebuilt to derive the cert planes
598 {
599 Real dx, dy, dz;
600 dispFromRef(P, XR, i, Lx, Ly, Lz, dx, dy, dz);
601 Wl.load(i, c, dx, dy, dz); // no-op without a wall store
602 }
603 const Real* wPtr = Wt.data(); // force-capture Wt OUTSIDE the constexpr-if (nvcc rule)
604 Real wSelfI = Real(0);
605 if constexpr (Weighted)
606 wSelfI = Wt(i);
607 c.template reevalGeometry<PlanePolicy>(P(3 * i), P(3 * i + 1), P(3 * i + 2), P.data(), Lx,
608 wSelfI, wPtr); // computePoke4 needs vertices
610 c.computePoke4(&Pk4((size_t)i * MAXT *
611 4)); // the only thing this pass produces (topology unchanged)
612 });
613 }
619 void maybeBuildAdj(const Kokkos::View<Real*, Mem>& pos) {
620 if (localCert && !adjFresh) {
621 rebuildAdjAll(pos);
622 adjFresh = true;
623 useLocalNow = true;
624 }
625 }
626
631 void gatherSet(const TessGrid<Real>& grid, const Kokkos::View<int*, Mem>& wl, int n,
632 const Kokkos::View<Real*, Mem>& pos) {
633 if (n <= 0)
634 return;
635 // TrackAdj gather: clip stitches the edge adjacency incrementally; the builder derives the cert
636 // plane set from it (computePoke4, no findSharing) into store.poke4, so the local cert stays
637 // valid for the gathered cells with no separate maintenance pass.
638 subsetGather<Real, Weighted, true, Sdf>(grid, wl, n, store.np, store.nt, store.pnbr, store.tri,
639 vol, store.poke4, sdf, /*withForceGeom=*/false, wall,
642 auto W = wl;
643 auto rb = rebuilt;
644 auto P = pos;
645 auto XR = xRef;
646 Kokkos::parallel_for(
647 "mt.markRebuilt", Kokkos::RangePolicy<Exec>(0, n), KOKKOS_LAMBDA(int s) {
648 const int i = W(s);
649 rb(i) = 1;
650 XR(3 * i) = P(3 * i);
651 XR(3 * i + 1) = P(3 * i + 1);
652 XR(3 * i + 2) = P(3 * i + 2);
653 });
654 }
655
658 void collectNewNbrs(const Kokkos::View<int*, Mem>& wl, int n,
659 const Kokkos::View<int*, Mem>& outMask) {
660 Kokkos::deep_copy(outMask, 0);
661 if (n <= 0)
662 return;
663 Store st = store;
664 auto rb = rebuilt;
665 auto M = outMask;
666 const int nP_ = nProc;
667 Kokkos::parallel_for(
668 "mt.collectNbrs", Kokkos::RangePolicy<Exec>(0, n), KOKKOS_LAMBDA(int s) {
669 const int i = wl(s);
670 const int np = st.np(i), nt = st.nt(i);
671 for (int k = 6; k < np; ++k) {
672 const int j = st.pnbr((size_t)i * MAXP + k);
673 if (j < 0 || j >= nP_ || rb(j))
674 continue; // only expand neighbours we MAINTAIN (owned)
675 int cnt = 0; // face = ≥3 incident live triangles
676 for (int t = 0; t < nt; ++t) {
677 const unsigned w = st.tri((size_t)i * MAXT + t);
678 if (!((w >> 24) & 1u))
679 continue;
680 const int a = (int)(w & 0xffu), b = (int)((w >> 8) & 0xffu),
681 cc = (int)((w >> 16) & 0xffu);
682 if (a == k || b == k || cc == k)
683 ++cnt;
684 }
685 if (cnt >= 3)
686 Kokkos::atomic_exchange(&M(j), 1);
687 }
688 });
689 }
690
699 void surgicalRepair(const Kokkos::View<int*, Mem>& wl, int n,
700 const Kokkos::View<Real*, Mem>& pos) {
701 if (n <= 0)
702 return;
703 auto W = wl;
704 auto P = pos;
705 auto rb = rebuilt;
706 auto XR = xRef;
707 auto Vv = vol;
708 auto EN = extraNbr;
709 auto EC = extraCnt;
710 constexpr int kCap = kExtraCap;
711 Store st = store;
712 auto Pk4 = store.poke4;
713 const Real Lx = L[0], Ly = L[1], Lz = L[2];
714 const Real Lxh = Real(0.5) * Lx, Lyh = Real(0.5) * Ly, Lzh = Real(0.5) * Lz;
715 Kokkos::parallel_for(
716 "mt.surgical", Kokkos::RangePolicy<Exec>(0, n), KOKKOS_LAMBDA(int s) {
717 const int i = W(s);
718 BuildCell c;
719 c.initBox(Lx, Ly, Lz);
720 const Real sx = P(3 * i), sy = P(3 * i + 1), sz = P(3 * i + 2);
721 const int npi = st.np(i);
722 for (int k = 6; k < npi && !c.overflow; ++k) { // stored neighbours, current positions
723 const int j = st.pnbr((size_t)i * MAXP + k);
724 if (j < 0)
725 continue;
726 Real rx = P(3 * j) - sx, ry = P(3 * j + 1) - sy, rz = P(3 * j + 2) - sz;
727 rx = rx > Lxh ? rx - Lx : (rx < -Lxh ? rx + Lx : rx);
728 ry = ry > Lyh ? ry - Ly : (ry < -Lyh ? ry + Ly : ry);
729 rz = rz > Lzh ? rz - Lz : (rz < -Lzh ? rz + Lz : rz);
730 const Real nrm[3] = {rx, ry, rz};
731 c.clip(nrm, Real(0.5) * (rx * rx + ry * ry + rz * rz), j);
732 }
733 const int ne = EC(i) < kCap ? EC(i) : kCap;
734 for (int e = 0; e < ne && !c.overflow; ++e) { // partner-discovered new neighbours
735 const int j = EN((size_t)i * kCap + e);
736 if (j < 0 || j == i)
737 continue;
738 Real rx = P(3 * j) - sx, ry = P(3 * j + 1) - sy, rz = P(3 * j + 2) - sz;
739 rx = rx > Lxh ? rx - Lx : (rx < -Lxh ? rx + Lx : rx);
740 ry = ry > Lyh ? ry - Ly : (ry < -Lyh ? ry + Ly : ry);
741 rz = rz > Lzh ? rz - Lz : (rz < -Lzh ? rz + Lz : rz);
742 const Real nrm[3] = {rx, ry, rz};
743 c.clip(nrm, Real(0.5) * (rx * rx + ry * ry + rz * rz), j);
744 }
745 if (!c.overflow) {
746 st.save(i, c);
747 Vv(i) = c.volumePerVertex();
748 c.computePoke4(
749 &Pk4((size_t)i * MAXT * 4)); // refresh the cert plane set for this re-clipped cell
750 }
751 rb(i) = 1;
752 XR(3 * i) = sx;
753 XR(3 * i + 1) = sy;
754 XR(3 * i + 2) = sz;
755 });
756 }
757
764 bool dilate(const TessGrid<Real>& grid, const Kokkos::View<Real*, Mem>& pos) {
765 const int dimx = grid.dimx, dimy = grid.dimy, dimz = grid.dimz;
766 const int ncell = dimx * dimy * dimz;
767 if ((int)cellFlag.extent(0) < ncell)
768 cellFlag = Kokkos::View<int*, Mem>(
769 Kokkos::view_alloc(std::string("mt.cellFlag"), Kokkos::WithoutInitializing), ncell);
770 Kokkos::deep_copy(cellFlag, 0);
771 const Real icx = grid.icx, icy = grid.icy, icz = grid.icz;
772 auto P = pos;
773 auto M = mask;
774 auto CF = cellFlag;
775 Kokkos::parallel_for(
776 "mt.dilate.count", Kokkos::RangePolicy<Exec>(0, nProc), KOKKOS_LAMBDA(int i) {
777 if (!M(i))
778 return;
779 const int gx = ((int)Kokkos::floor(P(3 * i) * icx) % dimx + dimx) % dimx;
780 const int gy = ((int)Kokkos::floor(P(3 * i + 1) * icy) % dimy + dimy) % dimy;
781 const int gz = ((int)Kokkos::floor(P(3 * i + 2) * icz) % dimz + dimz) % dimz;
782 Kokkos::atomic_inc(&CF(gx + gy * dimx + gz * dimx * dimy));
783 });
784 const int thr = clusterNbhd;
785 long added = 0;
786 Kokkos::parallel_reduce(
787 "mt.dilate.mark", Kokkos::RangePolicy<Exec>(0, nProc),
788 KOKKOS_LAMBDA(int i, long& a) {
789 if (M(i))
790 return;
791 const int gx = ((int)Kokkos::floor(P(3 * i) * icx) % dimx + dimx) % dimx;
792 const int gy = ((int)Kokkos::floor(P(3 * i + 1) * icy) % dimy + dimy) % dimy;
793 const int gz = ((int)Kokkos::floor(P(3 * i + 2) * icz) % dimz + dimz) % dimz;
794 int sum = 0;
795 for (int dz = -1; dz <= 1; ++dz)
796 for (int dy = -1; dy <= 1; ++dy)
797 for (int dx = -1; dx <= 1; ++dx) {
798 const int nx = (gx + dx + dimx) % dimx, ny = (gy + dy + dimy) % dimy,
799 nz = (gz + dz + dimz) % dimz;
800 sum += CF(nx + ny * dimx + nz * dimx * dimy);
801 }
802 if (sum > thr) {
803 M(i) = 1;
804 ++a;
805 }
806 },
807 added);
808 return added > 0;
809 }
810
814 void certifyDispatch(const Kokkos::View<Real*, Mem>& pos, const Kokkos::View<int*, Mem>& outMask,
815 const Kokkos::View<int*, Mem>& outMover, bool useSkin) {
816 if (useLocalNow)
817 certify<true>(pos, outMask, outMover, useSkin);
818 else
819 certify<false>(pos, outMask, outMover, useSkin);
820 }
821 void certifyListDispatch(const Kokkos::View<Real*, Mem>& pos, const Kokkos::View<int*, Mem>& list,
822 int n, const Kokkos::View<int*, Mem>& outMask) {
823 if (useLocalNow)
824 certifyList<true>(pos, list, n, outMask);
825 else
826 certifyList<false>(pos, list, n, outMask);
827 }
828
830 RepairStats step(const Kokkos::View<Real*, Mem>& pos) {
831 RepairStats s;
832 auto grid = buildTessGrid<Real, Weighted>(pos, weight, N, L, sw, densityCount,
833 Kokkos::View<long*, Mem>());
834 Kokkos::deep_copy(rebuilt, 0);
835 // Use the cheap local certificate whenever store.poke4 is valid for the current topology. With
836 // the 4th-face plane included the local cert matches the brute flag set (it is complete, not a
837 // subset), so no per-step displacement gate or brute fallback is needed — large moves and
838 // teleports are handled by the same detect→gather→verify path. (adj/face4 are invalidated only
839 // by a cold rebuild.)
841
842 // Pass 1: detect (flagged ∪ partners ∪ skin-movers). flagged-commons + their violated-plane
843 // partners (the gaining cells) fully cover an isolated flip — no Pass 2 for flips.
844 certifyDispatch(pos, mask, mover, /*useSkin=*/true);
845 int n1 = compact(mask, wl1);
846 s.pass1Raw = n1;
847 if constexpr (kHasSdf)
848 Kokkos::deep_copy(s.wallFlagged, wallCounter);
849 if (useNearMiss)
850 Kokkos::deep_copy(s.nearFlagged, nearCounter);
851
852 // Small-displacement fast path: the certificate found every cell consistent and no skin-mover
853 // (n1 counts flagged ∪ partners ∪ movers). By the §1 completeness argument a stale topology
854 // always flags some cell, so n1==0 ⇒ the stored topology is still Voronoi on the new positions
855 // — the re-eval already refreshed every volume, so publish with NO gather and NO verify pass.
856 // This is the "almost no cells invalidated" regime: the per-step cost collapses to one re-eval
857 // + the grid.
858 if (n1 == 0) {
859 maybeBuildAdj(pos);
860 return s;
861 }
862
863 // Phase-3 adaptive gate (decided here, after the FREE certificate, before any gather):
864 // high global churn -> a full rebuild is cheaper than repairing this many cells;
865 // dense local cluster -> dilate the Pass-1 set by a grid-cell buffer (one regional gather);
866 // else (sparse) -> the two-pass repair below.
867 if (useGate && (double)n1 / (nProc > 0 ? nProc : 1) > churnThresh) {
868 rebuild(pos,
869 /*eagerAdj=*/false); // high-churn regime: stay on brute, don't pay adj maintenance
871 return s;
872 }
873 if (useNearMiss && !nearFresh) {
874 // First low-churn step after gate-rebuilds: the certificate above ran WITHOUT the near-miss
875 // lists (incomplete for face gains), so do not repair on it — pay one emitting rebuild
876 // (exact) and repair from the next step on with fresh lists.
877 rebuild(pos, /*eagerAdj=*/true);
879 return s;
880 }
881 if (useGate && useDilation && n1 > 0 && (double)n1 / nProc < dilateMaxChurn &&
882 dilate(grid, pos)) {
883 n1 = compact(mask, wl1);
885 }
886 s.pass1 = n1;
887 int nM = compact(mover, wlM);
888 // Pass 1 gather. Phase 4: FLIP cells (flagged non-movers) are repaired SURGICALLY (re-clip
889 // known candidates, no grid gather); far-mover insertions keep the full gather (their new
890 // neighbours are in no candidate list). Default (surgical off): the whole Pass-1 set goes
891 // through the full gather.
892 if (surgical && !kHasSdf) {
893 auto Msk = mask;
894 auto Mv = mover;
895 auto M2 = mask2;
896 Kokkos::parallel_for(
897 "mt.surgMask", Kokkos::RangePolicy<Exec>(0, nProc),
898 KOKKOS_LAMBDA(int i) { M2(i) = (Msk(i) && !Mv(i)) ? 1 : 0; });
899 int nS =
900 compact(mask2, wl1); // wl1's Pass-1 list no longer needed -> reuse for the flip list
901 s.surgical = nS;
902 surgicalRepair(wl1, nS, pos); // flips: no gather
903 gatherSet(grid, wlM, nM, pos); // movers: full gather
904 } else {
905 gatherSet(grid, wl1, n1, pos);
906 }
907
908 // Pass 2: ONLY the new face-neighbours of the skin-MOVERS (the insertion side §1c — those
909 // neighbours never flag and are never partners). Expanding every Pass-1 cell would re-clip ~all
910 // neighbours of every flip cluster for nothing; movers are the only cells whose rebuild reveals
911 // an unflagged gainer.
913 int n2 = compact(mask2, wl2);
914 s.pass2 = n2;
915 gatherSet(grid, wl2, n2, pos);
917 pos); // first low-churn step after a rebuild: populate adj so verify + next steps go local
918
919 // Verify (scoped): re-certify only the GATHERED cells (every non-gathered cell is provably
920 // unchanged — see certifyList). Clean ⇒ publish; else gather the still-flagged ∪ partners
921 // (residual coupled flips / tol-degenerate cells) and re-verify, bounded.
922 for (int vp = 0; vp < verifyCap + 1; ++vp) {
923 const int ng = compact(rebuilt, wl1); // all cells gathered so far this step
924 // Verify with the step's certificate: a far/teleport step already forced useLocalNow=false,
925 // so its verify is the COMPLETE brute form (catches far-pokes the gather left on a gathered
926 // cell). A small- displacement local step uses the cheap local verify over the
927 // freshly-gathered (locally convex) set.
928 certifyListDispatch(pos, wl1, ng, mask);
929 int nv = compact(mask, wl2);
930 s.verifyPasses = vp + 1;
931 if (nv == 0)
932 return s; // clean — done
933 if (vp == verifyCap)
934 break; // out of budget — fall back
935 s.extra += nv;
936 gatherSet(grid, wl2, nv, pos);
937 }
938
939 // Still dirty after the verify budget: cold rebuild (always exact). Rare; the data point that
940 // says "for this step, rebuild was cheaper" — the adaptive gate (Phase 3) will pre-empt it up
941 // front.
942 rebuild(pos);
943 s.fellBack = true;
944 return s;
945 }
946};
947
948} // namespace peclet::voro
949
950#endif // PECLET_VORO_REPAIR_HPP
Device-native Voronoi tessellation, geometry and finite-volume physics.
Definition convex_cell.hpp:41
@ kOverflow
Definition tessellator.hpp:50
@ kBuried
Definition tessellator.hpp:64
@ kReachExceeded
Definition tessellator.hpp:65
@ kIncomplete
Definition tessellator.hpp:52
@ kEmpty
Definition tessellator.hpp:51
KOKKOS_INLINE_FUNCTION bool sdfWouldClip(const ConvexCell< Real, MAXP, MAXT, TrackAdj > &c, const Real seed[3], const Sdf &sdf)
Definition sdf.hpp:552
constexpr int kMaxPlanes
Definition params.hpp:18
constexpr int kMaxTriangles
Definition params.hpp:19
constexpr int kSearchWindow
Definition params.hpp:28
A2a validity diagnostics of a cold (re)build (counts over the maintained cells).
Definition repair.hpp:77
long buried
power seeds outside their own cell (emptied — see kBuried)
Definition repair.hpp:78
long empty
cells with no volume (in-solid seeds + buried)
Definition repair.hpp:80
long reachExceeded
cells whose search reach passed half the box (min-image invalid)
Definition repair.hpp:79
long incomplete
cells whose coverage did not close
Definition repair.hpp:82
int overBufferRebuilds
over-buffer misses of the build (TessellatorResult)
Definition repair.hpp:83
long overflow
cells that hit a capacity (planes / triangles / facets)
Definition repair.hpp:81
Definition convex_cell.hpp:140
KOKKOS_INLINE_FUNCTION Real volumePerVertex() const
Definition convex_cell.hpp:1192
KOKKOS_INLINE_FUNCTION Real planeGap(const Real pdir[3], Real d) const
Definition convex_cell.hpp:526
KOKKOS_INLINE_FUNCTION bool isLocallyConvexPartners(const unsigned char *poke4, Real tol, int *outPartners, int maxPartners, int &nPartners) const
Definition convex_cell.hpp:456
KOKKOS_INLINE_FUNCTION void initBox(Real L0, Real L1, Real L2)
Definition convex_cell.hpp:176
bool overflow
set if MAXP/MAXT exceeded -> cell invalid, caller falls back
Definition convex_cell.hpp:157
int nt
triangle high-water mark
Definition convex_cell.hpp:156
KOKKOS_INLINE_FUNCTION void rebuildAdjacency()
Definition convex_cell.hpp:624
Real vy[MAXT]
Definition convex_cell.hpp:154
KOKKOS_INLINE_FUNCTION bool isSelfConsistentPartners(Real tol, int *outPartners, int maxPartners, int &nPartners) const
Definition convex_cell.hpp:345
Real vx[MAXT]
Definition convex_cell.hpp:154
KOKKOS_INLINE_FUNCTION bool clip(const Real pdir[3], Real d, int nbr, Real *gapOut=nullptr)
Definition convex_cell.hpp:688
KOKKOS_INLINE_FUNCTION void computePoke4(unsigned char *out) const
Definition convex_cell.hpp:394
bool alive[MAXT]
triangle live flag
Definition convex_cell.hpp:155
Real vz[MAXT]
cached dual-vertex position per triangle
Definition convex_cell.hpp:154
Definition repair.hpp:90
Kokkos::View< int, Mem > nearCounter
Definition repair.hpp:207
void gatherSet(const TessGrid< Real > &grid, const Kokkos::View< int *, Mem > &wl, int n, const Kokkos::View< Real *, Mem > &pos)
Definition repair.hpp:631
bool surgical
repair flip cells by re-clipping known candidates instead of gathering
Definition repair.hpp:144
bool wallExact
re-gather every wall-clipped cell that moved (== cold build)
Definition repair.hpp:212
Kokkos::View< int *, Mem > rebuilt
Definition repair.hpp:216
peclet::core::MemSpace Mem
Definition repair.hpp:91
Kokkos::View< int, Mem > counter
Definition repair.hpp:219
double dilateMaxChurn
Definition repair.hpp:128
void certify(const Kokkos::View< Real *, Mem > &pos, const Kokkos::View< int *, Mem > &outMask, const Kokkos::View< int *, Mem > &outMover, bool useSkin)
Definition repair.hpp:344
Kokkos::View< Real *, Mem > vol
Definition repair.hpp:183
bool useNearMiss
Definition repair.hpp:199
WallStore< Real > wall
persisted wall planes (allocated by alloc() when kHasSdf)
Definition repair.hpp:211
int nProc
Definition repair.hpp:105
Kokkos::View< int *, Mem > mask
Definition repair.hpp:216
void certifyDispatch(const Kokkos::View< Real *, Mem > &pos, const Kokkos::View< int *, Mem > &outMask, const Kokkos::View< int *, Mem > &outMover, bool useSkin)
Definition repair.hpp:814
Kokkos::View< int *, Mem > mask2
Definition repair.hpp:216
int densityCount
Definition repair.hpp:108
bool localCert
Definition repair.hpp:148
peclet::core::ExecSpace Exec
Definition repair.hpp:92
Kokkos::View< int *, Mem > cellFlag
Definition repair.hpp:217
int lastOverBufferRebuilds
of the last cold (re)build (BuildReport)
Definition repair.hpp:119
void surgicalRepair(const Kokkos::View< int *, Mem > &wl, int n, const Kokkos::View< Real *, Mem > &pos)
Definition repair.hpp:699
int N
total cells in the resident arrays (single-domain: all; MPI: owned+ghost)
Definition repair.hpp:104
Kokkos::View< int, Mem > wallCounter
Definition repair.hpp:214
void rebuild(const Kokkos::View< Real *, Mem > &pos, bool eagerAdj=true)
Definition repair.hpp:296
RepairStats step(const Kokkos::View< Real *, Mem > &pos)
One moving-point update step. Updates store + vol in place from pos.
Definition repair.hpp:830
Kokkos::View< Real *, Mem > xRef
Definition repair.hpp:184
bool profile
the cold build's timing / over-buffer report on stderr
Definition repair.hpp:118
Real L[3]
Definition repair.hpp:109
void certifyList(const Kokkos::View< Real *, Mem > &pos, const Kokkos::View< int *, Mem > &list, int n, const Kokkos::View< int *, Mem > &outMask)
Definition repair.hpp:525
int gridNcell
Definition repair.hpp:220
void alloc(int n, const Real Lbox[3], Real tol_, Real skin_, int sw_=kSearchWindow, int densityCount_=-1, int nProc_=-1)
Definition repair.hpp:226
bool adjFresh
Definition repair.hpp:156
int clusterNbhd
Definition repair.hpp:131
bool dilate(const TessGrid< Real > &grid, const Kokkos::View< Real *, Mem > &pos)
Definition repair.hpp:764
Kokkos::View< int *, Mem > extraCnt
Definition repair.hpp:218
bool useDilation
Definition repair.hpp:120
Real nearMarginFrac
margin = nearMarginFrac · skin
Definition repair.hpp:201
bool nearFresh
Definition repair.hpp:202
Kokkos::View< Real *, Mem > weight
Definition repair.hpp:185
static KOKKOS_INLINE_FUNCTION void dispFromRef(const Kokkos::View< Real *, Mem > &P, const Kokkos::View< Real *, Mem > &XR, int i, Real Lx, Real Ly, Real Lz, Real &dx, Real &dy, Real &dz)
Definition repair.hpp:276
Kokkos::View< int *, Mem > wl1
Definition repair.hpp:216
int verifyCap
max verify extra-passes before falling back to a full rebuild
Definition repair.hpp:113
static constexpr bool kHasSdf
Definition repair.hpp:94
Real skin
Verlet skin width (absolute); a particle moving > skin/2 is a Pass-1 mover.
Definition repair.hpp:112
void maybeBuildAdj(const Kokkos::View< Real *, Mem > &pos)
Definition repair.hpp:619
Kokkos::View< int *, Mem > extraNbr
Definition repair.hpp:218
Kokkos::View< int *, Mem > mover
Definition repair.hpp:216
bool useGate
high-churn → rebuild routing (the "never slower than rebuild" guard)
Definition repair.hpp:117
Kokkos::View< int *, Mem > wlM
Definition repair.hpp:216
Kokkos::View< int *, Mem > lastStatus
Definition repair.hpp:163
int compact(const Kokkos::View< int *, Mem > &m, const Kokkos::View< int *, Mem > &wl)
Compact the nonzero entries of m[0..nProc) into wl, returning the count.
Definition repair.hpp:320
bool useLocalNow
this step's certify uses the local cert (= localCert && adjFresh)
Definition repair.hpp:161
int sw
Definition repair.hpp:108
BuildReport report() const
A2a validity diagnostics of the last cold (re)build (counts over the maintained cells).
Definition repair.hpp:165
Sdf sdf
the geometry provider (replicated on every rank under MPI)
Definition repair.hpp:210
void rebuildAdjAll(const Kokkos::View< Real *, Mem > &pos)
Definition repair.hpp:583
void setWeights(const Kokkos::View< Real *, Mem > &w)
Set the per-seed power weights (Weighted only). Held by reference; the caller keeps it alive.
Definition repair.hpp:189
Kokkos::View< int *, Mem > near
Definition repair.hpp:206
Kokkos::View< int *, Mem > wl2
Definition repair.hpp:216
std::conditional_t< Weighted, Power, Voronoi > PlanePolicy
Definition repair.hpp:93
Kokkos::View< int *, Mem > nearCnt
Definition repair.hpp:206
Real tol
certificate tolerance (absolute distance); ~1e-4·spacing FP64, ~2e-3·spacing FP32
Definition repair.hpp:110
static constexpr int kExtraCap
per-cell capacity for partner-discovered new neighbours
Definition repair.hpp:146
int kNearCap
per-cell list capacity (overflow ⇒ re-gather every step)
Definition repair.hpp:200
double churnThresh
Definition repair.hpp:125
void certifyListDispatch(const Kokkos::View< Real *, Mem > &pos, const Kokkos::View< int *, Mem > &list, int n, const Kokkos::View< int *, Mem > &outMask)
Definition repair.hpp:821
Store store
Definition repair.hpp:182
Real wallSkin
skin mode (wallExact=false): re-gather a wall cell only past this
Definition repair.hpp:213
void collectNewNbrs(const Kokkos::View< int *, Mem > &wl, int n, const Kokkos::View< int *, Mem > &outMask)
Definition repair.hpp:658
Sentinel "no geometry" provider — the default; the clip stage is skipped.
Definition sdf.hpp:39
Per-step repair telemetry.
Definition repair.hpp:61
int wallFlagged
cells flagged by the SDF boundary watch (0 without an SDF)
Definition repair.hpp:70
int extra
cells gathered across the verify extra-passes
Definition repair.hpp:67
int pass1
cells gathered in Pass 1 (flagged ∪ partners ∪ skin-movers, after dilation)
Definition repair.hpp:64
int surgical
Pass-1 cells repaired surgically (Phase 4, no grid gather)
Definition repair.hpp:68
int pass2
cells gathered in Pass 2 (new face-neighbours of the movers)
Definition repair.hpp:66
bool fellBack
true if the cold-build fallback was triggered (repair did not close)
Definition repair.hpp:73
Route route
Definition repair.hpp:72
int pass1Raw
flagged count BEFORE dilation (the gate signal: pass1Raw/nProc = churn)
Definition repair.hpp:65
Route
Which path the Phase-3 adaptive gate took this step.
Definition repair.hpp:63
@ kDilated
Definition repair.hpp:63
@ kTwoPass
Definition repair.hpp:63
@ kRebuildGate
Definition repair.hpp:63
int nearFlagged
cells flagged by the near-miss (face-gain) check
Definition repair.hpp:71
int verifyPasses
number of verify iterations run
Definition repair.hpp:69
Definition tess_grid.hpp:56
int dimz
Definition tess_grid.hpp:71
int dimy
Definition tess_grid.hpp:71
int dimx
Definition tess_grid.hpp:71
Real icz
Definition tess_grid.hpp:70
Real icx
Definition tess_grid.hpp:70
Real icy
Definition tess_grid.hpp:70
Definition topology_store.hpp:39
void alloc(int n)
Definition topology_store.hpp:53
Kokkos::View< int *, MemSpace > np
Definition topology_store.hpp:42
Kokkos::View< unsigned char *, MemSpace > poke4
Definition topology_store.hpp:47
KOKKOS_INLINE_FUNCTION void load(int i, Cell &c, Real L0, Real L1, Real L2) const
Definition topology_store.hpp:93
Kokkos::View< unsigned *, MemSpace > tri
Definition topology_store.hpp:45
KOKKOS_INLINE_FUNCTION void save(int i, const Cell &c) const
Persist cell c at slot i (call after the cell is finalised — clipped + complete).
Definition topology_store.hpp:75
Kokkos::View< int *, MemSpace > nt
Definition topology_store.hpp:43
Kokkos::View< int *, MemSpace > pnbr
Definition topology_store.hpp:44
void allocPoke4()
Definition topology_store.hpp:67
Definition sdf.hpp:352
Kokkos::View< int *, MemSpace > cnt
Definition sdf.hpp:356
KOKKOS_INLINE_FUNCTION bool load(int i, Cell &c, Real dx, Real dy, Real dz) const
Definition sdf.hpp:393