pnm 1.0.3
peclet.pnm — pore-network extraction from SDF geometry (Kokkos)
Loading...
Searching...
No Matches
pore_kernels.hpp
Go to the documentation of this file.
1
31#ifndef PECLET_PNM_PORE_KERNELS_HPP
32#define PECLET_PNM_PORE_KERNELS_HPP
33
34#include <algorithm>
35#include <array>
36#include <cstddef>
37#include <cstdint>
38#include <Kokkos_Core.hpp>
39#include <utility>
40#include <vector>
41
42namespace pnm {
43
44struct Pore {
45 float x, y, z, radius;
46};
47struct I3 {
48 int x, y, z;
49};
50
51using Exec = Kokkos::DefaultExecutionSpace;
52using Mem = Exec::memory_space;
54using Index = std::int64_t;
55
58template <class T>
59inline void uploadVec(const std::vector<T>& h, const Kokkos::View<T*, Mem>& d) {
60 if (h.empty())
61 return;
62 Kokkos::deep_copy(
63 d, Kokkos::View<const T*, Kokkos::HostSpace, Kokkos::MemoryTraits<Kokkos::Unmanaged>>(
64 h.data(), h.size()));
65}
66
69template <class V>
70inline std::vector<typename V::value_type> downloadN(const V& d, std::size_t count) {
71 std::vector<typename V::value_type> out(count);
72 if (count)
73 Kokkos::deep_copy(Kokkos::View<typename V::value_type*, Kokkos::HostSpace,
74 Kokkos::MemoryTraits<Kokkos::Unmanaged>>(out.data(), count),
75 Kokkos::subview(d, Kokkos::make_pair(std::size_t(0), count)));
76 return out;
77}
78
80template <class V>
81inline typename V::value_type readScalar(const V& d) {
82 auto h = Kokkos::create_mirror_view(d);
83 Kokkos::deep_copy(h, d);
84 return h();
85}
86
87KOKKOS_INLINE_FUNCTION int get_idx(int x, int y, int z, I3 res) {
88 x = (x % res.x + res.x) % res.x;
89 y = (y % res.y + res.y) % res.y;
90 z = (z % res.z + res.z) % res.z;
91 return z * res.y * res.x + y * res.x + x;
92}
93
94KOKKOS_INLINE_FUNCTION int wrapc(int a, int n) {
95 const int r = a % n;
96 return r < 0 ? r + n : r;
97}
98
101struct GridGeo {
102 int nx, ny, nz;
103 int g; // 0
104
105 static GridGeo of(I3 res) { return GridGeo{res.x, res.y, res.z, 0}; }
106 KOKKOS_INLINE_FUNCTION Index cell(int lx, int ly, int lz) const {
107 return (Index(wrapc(lz, nz)) * ny + wrapc(ly, ny)) * nx + wrapc(lx, nx);
108 }
109 KOKKOS_INLINE_FUNCTION Index gid(int lx, int ly, int lz) const { return cell(lx, ly, lz); }
110 KOKKOS_INLINE_FUNCTION void gcoord(int lx, int ly, int lz, int& gx, int& gy, int& gz) const {
111 gx = wrapc(lx, nx);
112 gy = wrapc(ly, ny);
113 gz = wrapc(lz, nz);
114 }
115 KOKKOS_INLINE_FUNCTION bool inOwned(int, int, int) const { return true; }
116 KOKKOS_INLINE_FUNCTION Index owned(int ix, int iy, int iz) const { return cell(ix, iy, iz); }
117 KOKKOS_INLINE_FUNCTION void ownedCoords(Index o, int& ix, int& iy, int& iz) const {
118 ix = int(o % nx);
119 iy = int((o / nx) % ny);
120 iz = int(o / (Index(nx) * ny));
121 }
122 KOKKOS_INLINE_FUNCTION Index localOf(Index gid) const { return gid; }
123 KOKKOS_INLINE_FUNCTION Index cellAtGlobal(int gx, int gy, int gz) const {
124 return cell(gx, gy, gz);
125 }
126 std::size_t numOwned() const { return std::size_t(nx) * ny * nz; }
127};
128
131struct BlockGeo {
132 int ex, ey, ez; // extended sizes (inner + 2g)
133 int ox, oy, oz; // global origin of the extended box (originInclGhost; may be negative)
134 int nx, ny, nz; // inner (owned) sizes
135 int gnx, gny, gnz; // global dims
136 int g; // ghost width
137
138 static KOKKOS_INLINE_FUNCTION int wrapc(int a, int n) { return pnm::wrapc(a, n); }
139 KOKKOS_INLINE_FUNCTION Index lidx(int lx, int ly, int lz) const {
140 return (Index(lz) * ey + ly) * ex + lx;
141 }
142 KOKKOS_INLINE_FUNCTION Index cell(int lx, int ly, int lz) const { return lidx(lx, ly, lz); }
144 KOKKOS_INLINE_FUNCTION Index gidAt(int lx, int ly, int lz) const {
145 const int gx = wrapc(ox + lx, gnx), gy = wrapc(oy + ly, gny), gz = wrapc(oz + lz, gnz);
146 return (Index(gz) * gny + gy) * gnx + gx;
147 }
148 KOKKOS_INLINE_FUNCTION Index gid(int lx, int ly, int lz) const { return gidAt(lx, ly, lz); }
149 KOKKOS_INLINE_FUNCTION void gcoord(int lx, int ly, int lz, int& gx, int& gy, int& gz) const {
150 gx = wrapc(ox + lx, gnx);
151 gy = wrapc(oy + ly, gny);
152 gz = wrapc(oz + lz, gnz);
153 }
154 KOKKOS_INLINE_FUNCTION bool inOwned(int ix, int iy, int iz) const {
155 return ix >= 0 && ix < nx && iy >= 0 && iy < ny && iz >= 0 && iz < nz;
156 }
157 KOKKOS_INLINE_FUNCTION Index owned(int ix, int iy, int iz) const {
158 return (Index(iz) * ny + iy) * nx + ix;
159 }
160 KOKKOS_INLINE_FUNCTION void ownedCoords(Index o, int& ix, int& iy, int& iz) const {
161 ix = int(o % nx);
162 iy = int((o / nx) % ny);
163 iz = int(o / (Index(nx) * ny));
164 }
168 KOKKOS_INLINE_FUNCTION Index localOf(Index gid) const {
169 const int gx = int(gid % gnx);
170 const Index t = gid / gnx;
171 const int gy = int(t % gny), gz = int(t / gny);
172 const int lx = wrapc(gx - ox, gnx);
173 if (lx >= ex)
174 return Index(-1);
175 const int ly = wrapc(gy - oy, gny);
176 if (ly >= ey)
177 return Index(-1);
178 const int lz = wrapc(gz - oz, gnz);
179 if (lz >= ez)
180 return Index(-1);
181 return lidx(lx, ly, lz);
182 }
183 KOKKOS_INLINE_FUNCTION Index cellAtGlobal(int gx, int gy, int gz) const {
184 return localOf((Index(wrapc(gz, gnz)) * gny + wrapc(gy, gny)) * gnx + wrapc(gx, gnx));
185 }
187 KOKKOS_INLINE_FUNCTION bool onSurface(int ix, int iy, int iz) const {
188 return !(ix > 0 && ix < nx - 1 && iy > 0 && iy < ny - 1 && iz > 0 && iz < nz - 1);
189 }
190 std::size_t numOwned() const { return std::size_t(nx) * ny * nz; }
191 std::size_t numExt() const { return std::size_t(ex) * ey * ez; }
192};
193
194namespace kernels {
195
196using MD3 = Kokkos::MDRangePolicy<Exec, Kokkos::Rank<3>>;
197using R1 = Kokkos::RangePolicy<Exec>;
198
200template <class Geo>
201inline MD3 ownedRange(const Exec& space, const Geo& geo) {
202 return MD3(space, {0, 0, 0}, {geo.nx, geo.ny, geo.nz});
203}
204
206constexpr Index kSent = 0x7ffffffffffffffeLL;
207
208// ---------------------------------------------------------------------------------------------
209// device stencil primitives
210// ---------------------------------------------------------------------------------------------
211
213template <class Geo, class SdfView>
214KOKKOS_INLINE_FUNCTION bool isPeak(const Geo& g, const SdfView& sdf, int lx, int ly, int lz,
215 float cv, Index cgid) {
216 for (int dz = -1; dz <= 1; ++dz)
217 for (int dy = -1; dy <= 1; ++dy)
218 for (int dx = -1; dx <= 1; ++dx) {
219 if (dx == 0 && dy == 0 && dz == 0)
220 continue;
221 const float nv = sdf(g.cell(lx + dx, ly + dy, lz + dz));
222 const Index ngid = g.gid(lx + dx, ly + dy, lz + dz);
223 if (nv > cv || (nv == cv && ngid > cgid))
224 return false;
225 }
226 return true;
227}
228
231template <class Geo, class SdfView>
232KOKKOS_INLINE_FUNCTION Pore poreAt(const Geo& g, const SdfView& sdf, int lx, int ly, int lz,
233 float ox, float oy, float oz, float sx, float sy, float sz) {
234 float sw = 0.0f, px = 0.0f, py = 0.0f, pz = 0.0f;
235 for (int dz = -1; dz <= 1; ++dz)
236 for (int dy = -1; dy <= 1; ++dy)
237 for (int dx = -1; dx <= 1; ++dx) {
238 const float v = sdf(g.cell(lx + dx, ly + dy, lz + dz));
239 float w = v > 0.0f ? v : 0.0f;
240 w = w * w;
241 sw += w;
242 px += dx * w;
243 py += dy * w;
244 pz += dz * w;
245 }
246 float fx = 0, fy = 0, fz = 0;
247 if (sw > 1e-6f) {
248 fx = px / sw;
249 fy = py / sw;
250 fz = pz / sw;
251 }
252 int gx, gy, gz;
253 g.gcoord(lx, ly, lz, gx, gy, gz);
254 return Pore{ox + (gx + fx) * sx, oy + (gy + fy) * sy, oz + (gz + fz) * sz,
255 sdf(g.cell(lx, ly, lz))};
256}
257
261template <class Geo, class SdfView>
262KOKKOS_INLINE_FUNCTION Index steepest(const Geo& g, const SdfView& sdf, int lx, int ly, int lz,
263 Index cgid, bool ascent) {
264 Index best = cgid;
265 float bv = sdf(g.cell(lx, ly, lz));
266 for (int dz = -1; dz <= 1; ++dz)
267 for (int dy = -1; dy <= 1; ++dy)
268 for (int dx = -1; dx <= 1; ++dx) {
269 if (dx == 0 && dy == 0 && dz == 0)
270 continue;
271 const Index ni = g.gid(lx + dx, ly + dy, lz + dz);
272 const float nv = sdf(g.cell(lx + dx, ly + dy, lz + dz));
273 if (ascent) {
274 if (nv > bv) {
275 bv = nv;
276 best = ni;
277 } else if (nv == bv && ni > best)
278 best = ni;
279 } else {
280 if (nv < bv) {
281 bv = nv;
282 best = ni;
283 } else if (nv == bv && ni > best)
284 best = ni;
285 }
286 }
287 return best;
288}
289
291template <class Geo, class FieldView>
292KOKKOS_INLINE_FUNCTION double trilinear(const Geo& g, const FieldView& p, const double gp[3]) {
293 int b[3];
294 double f[3];
295 for (int a = 0; a < 3; ++a) {
296 const double fl = Kokkos::floor(gp[a]);
297 b[a] = static_cast<int>(fl);
298 f[a] = gp[a] - fl;
299 }
300 double acc = 0.0;
301 for (int dz = 0; dz < 2; ++dz)
302 for (int dy = 0; dy < 2; ++dy)
303 for (int dx = 0; dx < 2; ++dx) {
304 const double wt =
305 (dx ? f[0] : 1.0 - f[0]) * (dy ? f[1] : 1.0 - f[1]) * (dz ? f[2] : 1.0 - f[2]);
306 acc += wt * p(g.cellAtGlobal(b[0] + dx, b[1] + dy, b[2] + dz));
307 }
308 return acc;
309}
310
315KOKKOS_INLINE_FUNCTION bool facesAdjacent(int dx, int dy, int dz, int d, int d2) {
316 const int Dx = 2 * dx + (d2 == 0) - (d == 0);
317 const int Dy = 2 * dy + (d2 == 1) - (d == 1);
318 const int Dz = 2 * dz + (d2 == 2) - (d == 2);
319 return Dx * Dx + Dy * Dy + Dz * Dz <= 8;
320}
321
323template <class KeyView>
325 typename KeyView::non_const_value_type key) {
326 Index lo = 0, hi = nkeys - 1;
327 while (lo <= hi) {
328 const Index mid = lo + (hi - lo) / 2;
329 const auto k = keys(mid);
330 if (k == key)
331 return mid;
332 if (k < key)
333 lo = mid + 1;
334 else
335 hi = mid - 1;
336 }
337 return Index(-1);
338}
339
343template <class V>
344struct FlatFaces {
346 KOKKOS_INLINE_FUNCTION Index operator()(Index e, int d) const { return v(3 * e + d); }
347};
348template <class V>
352 return d == 0 ? v0(e) : (d == 1 ? v1(e) : v2(e));
353 }
354};
355
356// ---------------------------------------------------------------------------------------------
357// pore detection
358// ---------------------------------------------------------------------------------------------
359
362template <class Geo, class SdfView, class PoreView, class CountView>
363inline void detectPores(const Exec& space, const Geo& geo, const SdfView& sdf,
364 std::array<float, 3> origin, std::array<float, 3> spacing,
365 const PoreView& pores, const CountView& counter, int maxPores) {
366 const Geo g = geo;
367 const float ox = origin[0], oy = origin[1], oz = origin[2];
368 const float sx = spacing[0], sy = spacing[1], sz = spacing[2];
369 Kokkos::parallel_for(
370 "pnm::extract_pores", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
371 const int lx = ix + g.g, ly = iy + g.g, lz = iz + g.g;
372 const float cv = sdf(g.cell(lx, ly, lz));
373 if (cv <= 0.0f)
374 return;
375 if (!isPeak(g, sdf, lx, ly, lz, cv, g.gid(lx, ly, lz)))
376 return;
377 const int slot = Kokkos::atomic_fetch_add(&counter(), 1);
378 if (slot < maxPores)
379 pores(slot) = poreAt(g, sdf, lx, ly, lz, ox, oy, oz, sx, sy, sz);
380 });
381 space.fence();
382}
383
384// ---------------------------------------------------------------------------------------------
385// marker-controlled watershed: markers -> union-find CCL -> flood
386// ---------------------------------------------------------------------------------------------
387
389template <class Geo, class SdfView, class ParentView>
390inline void initMarkers(const Exec& space, const Geo& geo, const SdfView& sdf, float thr,
391 const ParentView& parent) {
392 using L = typename ParentView::non_const_value_type;
393 const Geo g = geo;
394 Kokkos::parallel_for(
395 "pnm::init_markers", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
396 const Index o = g.owned(ix, iy, iz);
397 parent(o) = (sdf(g.cell(ix + g.g, iy + g.g, iz + g.g)) < thr) ? L(o) : L(-1);
398 });
399 space.fence();
400}
401
403template <class ParentView>
404inline void cclFlatten(const Exec& space, const ParentView& parent, std::size_t n) {
405 using L = typename ParentView::non_const_value_type;
406 Kokkos::parallel_for(
407 "pnm::flatten", R1(space, 0, n), KOKKOS_LAMBDA(std::size_t idx) {
408 L l = parent(idx);
409 if (l >= 0) {
410 while (l != parent(l))
411 l = parent(l);
412 parent(idx) = l;
413 }
414 });
415 space.fence();
416}
417
421template <class Geo, class ParentView, class FlagView>
422inline void cclMergeMarkers(const Exec& space, const Geo& geo, const ParentView& parent,
423 const FlagView& changed) {
424 using L = typename ParentView::non_const_value_type;
425 const Geo g = geo;
426 Kokkos::parallel_for(
427 "pnm::merge_markers", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
428 const Index o = g.owned(ix, iy, iz);
429 const L my = parent(o);
430 if (my == -1)
431 return;
432 const int dz_l[13] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0};
433 const int dy_l[13] = {-1, -1, -1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0};
434 const int dx_l[13] = {-1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, 1};
435 for (int k = 0; k < 13; ++k) {
436 const int jx = ix + dx_l[k], jy = iy + dy_l[k], jz = iz + dz_l[k];
437 if (!g.inOwned(jx, jy, jz))
438 continue;
439 const L nl = parent(g.owned(jx, jy, jz));
440 if (nl != -1 && my != nl) {
441 L rm = my;
442 while (rm != parent(rm))
443 rm = parent(rm);
444 L rn = nl;
445 while (rn != parent(rn))
446 rn = parent(rn);
447 if (rm != rn) {
448 const L small = rm < rn ? rm : rn, large = rm < rn ? rn : rm;
449 Kokkos::atomic_min(&parent(large), small);
450 changed() = 1;
451 }
452 }
453 }
454 });
455 space.fence();
456}
457
460template <class ParentView, class FlagView, class MergeFn>
461inline void cclFixpoint(const Exec& space, const ParentView& parent, std::size_t n,
462 const FlagView& changed, const MergeFn& merge) {
463 int h_changed = 1;
464 while (h_changed) {
465 Kokkos::deep_copy(changed, 0);
466 merge();
469 }
470}
471
475template <class Geo, class SdfView, class LabelView, class FlagView>
476inline void floodSweep(const Exec& space, const Geo& geo, const SdfView& sdf,
477 const LabelView& labels, const LabelView& labelsN, const FlagView& changed) {
478 using L = typename LabelView::non_const_value_type;
479 const Geo g = geo;
480 Kokkos::parallel_for(
481 "pnm::flood", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
482 const int lx = ix + g.g, ly = iy + g.g, lz = iz + g.g;
483 const Index e = g.cell(lx, ly, lz);
484 if (sdf(e) >= 0.0f)
485 return; // pore: ignore
486 if (labels(e) != -1)
487 return; // already labelled
488 L best = -1;
489 for (int dz = -1; dz <= 1; ++dz)
490 for (int dy = -1; dy <= 1; ++dy)
491 for (int dx = -1; dx <= 1; ++dx) {
492 if (dx == 0 && dy == 0 && dz == 0)
493 continue;
494 const L nl = labels(g.cell(lx + dx, ly + dy, lz + dz));
495 if (nl != -1 && (best == -1 || nl < best))
496 best = nl;
497 }
498 if (best != -1) {
499 labelsN(e) = best;
500 changed() = 1;
501 }
502 });
503 space.fence();
504}
505
512template <class Geo, class SdfView, class LabelView, class FlagView, class SyncFn>
513inline void floodFixpoint(const Exec& space, const Geo& geo, const SdfView& sdf, LabelView& labels,
514 LabelView& labelsN, const FlagView& changed, const SyncFn& sync) {
515 int h_changed = 1;
516 while (h_changed) {
517 Kokkos::deep_copy(changed, 0);
518 Kokkos::deep_copy(labelsN, labels);
520 std::swap(labels, labelsN);
522 }
523}
524
525// ---------------------------------------------------------------------------------------------
526// gradient-path basins
527// ---------------------------------------------------------------------------------------------
528
534template <class SdfView, class EmitFn>
535inline void gradientWalk(const Exec& space, const GridGeo& geo, const SdfView& sdf, bool ascentAll,
536 const EmitFn& emit) {
537 const GridGeo g = geo;
538 Kokkos::parallel_for(
539 "pnm::gradient_path", R1(space, 0, g.numOwned()), KOKKOS_LAMBDA(std::size_t i0) {
540 const Index ci = Index(i0);
541 const bool ascent = ascentAll || (sdf(ci) > 0.0f);
542 Index walker = ci;
543 const int MAX_STEPS = 512;
544 for (int s = 0; s < MAX_STEPS; ++s) {
545 int wx, wy, wz;
546 g.ownedCoords(walker, wx, wy, wz);
547 const Index best = steepest(g, sdf, wx, wy, wz, walker, ascent);
548 if (best == walker)
549 break;
550 walker = best;
551 }
552 emit(ci, walker);
553 });
554 space.fence();
555}
556
561template <class Geo, class SdfView, class TargetView, class ChaseFn, class FinalFn, class PeakFn>
562inline void forestInit(const Exec& space, const Geo& geo, const SdfView& sdf,
563 const TargetView& target, const ChaseFn& chase, const FinalFn& finalOf,
564 const PeakFn& peakOf) {
565 const Geo g = geo;
566 Kokkos::parallel_for(
567 "pnm::grad_step", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
568 const int lx = ix + g.g, ly = iy + g.g, lz = iz + g.g;
569 const Index ci = g.cell(lx, ly, lz);
570 const Index cgid = g.gid(lx, ly, lz);
571 if (!chase(ci)) {
572 target(ci) = finalOf(ci, cgid);
573 return;
574 }
575 const Index best = steepest(g, sdf, lx, ly, lz, cgid, true);
576 target(ci) = (best == cgid) ? peakOf(ci, cgid) : best;
577 });
578 space.fence();
579}
580
585template <class Geo, class TargetView>
586inline int resolveHoldAtGhost(const Exec& space, const Geo& geo, const TargetView& target) {
587 const Geo g = geo;
588 int pending = 0;
589 Kokkos::parallel_reduce(
590 "pnm::resolve_roots", R1(space, 0, g.numOwned()),
591 KOKKOS_LAMBDA(std::size_t o, int& pend) {
592 int ix, iy, iz;
593 g.ownedCoords(Index(o), ix, iy, iz);
594 const Index ci = g.cell(ix + g.g, iy + g.g, iz + g.g);
595 Index t = target(ci);
596 if (t < 0)
597 return; // finalized
598 for (int s = 0; s < 64; ++s) {
599 const Index tl = g.localOf(t); // in-field by invariant
600 if (tl < 0)
601 break;
602 const Index tv = target(tl);
603 if (tv < 0) { // that cell knows its root: adopt, finalized
604 t = tv;
605 break;
606 }
607 const Index nl = g.localOf(tv);
608 if (nl < 0)
609 break; // next hop leaves the field: hold at tl, wait for its owner
610 t = tv; // advance within the field
611 }
612 if (t != target(ci))
613 target(ci) = t;
614 if (t >= 0)
615 pend += 1;
616 },
617 pending);
618 space.fence();
619 return pending;
620}
621
622// ---------------------------------------------------------------------------------------------
623// topology
624// ---------------------------------------------------------------------------------------------
625
629template <class Geo, class SegView, class PairView, class CountView>
630inline void boundaryPairs(const Exec& space, const Geo& geo, const SegView& seg,
631 const PairView& pairs, const CountView& cnt, Index maxPairs) {
632 const Geo g = geo;
633 Kokkos::parallel_for(
634 "pnm::boundary_pairs", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
635 const int lx = ix + g.g, ly = iy + g.g, lz = iz + g.g;
636 const int my = seg(g.cell(lx, ly, lz));
637 const int dx_l[3] = {1, 0, 0}, dy_l[3] = {0, 1, 0}, dz_l[3] = {0, 0, 1};
638 for (int k = 0; k < 3; ++k) {
639 const int nl = seg(g.cell(lx + dx_l[k], ly + dy_l[k], lz + dz_l[k]));
640 if (my != nl) {
641 const int slot = Kokkos::atomic_fetch_add(&cnt(), 1);
642 if (slot < maxPairs) {
643 pairs(2 * slot) = my < nl ? my : nl;
644 pairs(2 * slot + 1) = my < nl ? nl : my;
645 }
646 }
647 }
648 });
649 space.fence();
650}
651
653template <class PairView>
654inline std::vector<std::pair<int, int>> uniquePairs(const PairView& pairs, std::size_t count) {
655 std::vector<int> flat = downloadN(pairs, 2 * count);
656 std::vector<std::pair<int, int>> result(count);
657 for (std::size_t i = 0; i < count; ++i)
658 result[i] = {flat[2 * i], flat[2 * i + 1]};
659 std::sort(result.begin(), result.end());
660 result.erase(std::unique(result.begin(), result.end()), result.end());
661 return result;
662}
663
665template <class Geo, class SegView>
666inline int maxLabel(const Exec& space, const Geo& geo, const SegView& seg) {
667 const Geo g = geo;
668 int np = 0;
669 Kokkos::parallel_reduce(
670 "pnm::max_label", R1(space, 0, g.numOwned()),
671 KOKKOS_LAMBDA(std::size_t o, int& m) {
672 int ix, iy, iz;
673 g.ownedCoords(Index(o), ix, iy, iz);
674 const int sv = seg(g.cell(ix + g.g, iy + g.g, iz + g.g));
675 if (sv > m)
676 m = sv;
677 },
678 Kokkos::Max<int>(np));
679 space.fence();
680 return np;
681}
682
683// ---------------------------------------------------------------------------------------------
684// network flow: per-patch throats over the interface FACES (fid = 3*cell + d, the +d face)
685// ---------------------------------------------------------------------------------------------
686
692template <class Geo, class LabView, class SdfView, class OpenView, class CoreView, class ParentView,
693 class PairView>
694inline void faceInit(const Exec& space, const Geo& geo, const LabView& flowLab, const SdfView& sdf,
695 const OpenView& ox, const OpenView& oy, const OpenView& oz, bool hasOpen,
696 const CoreView& core, const ParentView& parent, const PairView& fpair) {
697 using L = typename ParentView::non_const_value_type;
698 const Geo g = geo;
699 const bool ho = hasOpen;
700 Kokkos::parallel_for(
701 "pnm::face_init", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
702 const int lx = ix + g.g, ly = iy + g.g, lz = iz + g.g;
703 const Index o = g.owned(ix, iy, iz);
704 const Index ce = g.cell(lx, ly, lz);
705 const int a = flowLab(ce);
706 for (int d = 0; d < 3; ++d) {
707 const Index f = 3 * o + d;
708 const Index nb = g.cell(lx + (d == 0), ly + (d == 1), lz + (d == 2));
709 const int b = flowLab(nb);
710 const double opn = ho ? (d == 0 ? ox(nb) : (d == 1 ? oy(nb) : oz(nb))) : 1.0;
711 const bool itf = (a != b && a > 0 && b > 0 && opn > 0.0);
712 core(f) = (itf && sdf(ce) > 0.0f && sdf(nb) > 0.0f) ? 1 : 0;
713 parent(f) = core(f) ? L(f) : L(-1);
714 fpair(f) = itf ? ((Index(a < b ? a : b) << 32) | (a < b ? b : a)) : Index(-1);
715 }
716 });
717 space.fence();
718}
719
722template <class Geo, class ParentView, class PairView, class FlagView>
723inline void faceMerge(const Exec& space, const Geo& geo, const ParentView& parent,
724 const PairView& fpair, const FlagView& changed) {
725 using L = typename ParentView::non_const_value_type;
726 const Geo g = geo;
727 Kokkos::parallel_for(
728 "pnm::face_merge", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
729 const Index o = g.owned(ix, iy, iz);
730 for (int d = 0; d < 3; ++d) {
731 const Index f = 3 * o + d;
732 if (parent(f) < 0)
733 continue;
734 const Index pk = fpair(f);
735 for (int dz = -1; dz <= 1; ++dz)
736 for (int dy = -1; dy <= 1; ++dy)
737 for (int dx = -1; dx <= 1; ++dx) {
738 const int jx = ix + dx, jy = iy + dy, jz = iz + dz;
739 if (!g.inOwned(jx, jy, jz))
740 continue;
741 const Index o2 = g.owned(jx, jy, jz);
742 for (int d2 = 0; d2 < 3; ++d2) {
743 const Index f2 = 3 * o2 + d2;
744 if (f2 == f || parent(f2) < 0 || fpair(f2) != pk)
745 continue;
746 if (!facesAdjacent(dx, dy, dz, d, d2))
747 continue;
748 L rm = L(f);
749 while (rm != parent(rm))
750 rm = parent(rm);
751 L rn = L(f2);
752 while (rn != parent(rn))
753 rn = parent(rn);
754 if (rm != rn) {
755 const L sml = rm < rn ? rm : rn, lrg = rm < rn ? rn : rm;
756 Kokkos::atomic_min(&parent(lrg), sml);
757 changed() = 1;
758 }
759 }
760 }
761 }
762 });
763 space.fence();
764}
765
768template <class Geo, class ParentView, class MinView>
769inline void faceMinGid(const Exec& space, const Geo& geo, const ParentView& parent,
770 const MinView& mg) {
771 const Geo g = geo;
772 Kokkos::parallel_for(
773 "pnm::face_mingid", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
774 const Index o = g.owned(ix, iy, iz);
775 const Index gid = g.gid(ix + g.g, iy + g.g, iz + g.g);
776 for (int d = 0; d < 3; ++d)
777 if (parent(3 * o + d) >= 0)
778 Kokkos::atomic_min(&mg(parent(3 * o + d)), 3 * gid + d);
779 });
780 space.fence();
781}
782
786template <class ParentView, class PairView, class LabView, class RootFn>
787inline void faceLabelCore(const Exec& space, std::size_t nf, const ParentView& parent,
788 const PairView& fpair, const LabView& faceLab, const RootFn& rootOf) {
789 Kokkos::parallel_for(
790 "pnm::face_label", R1(space, 0, nf), KOKKOS_LAMBDA(std::size_t f) {
791 faceLab(f) = parent(f) >= 0 ? rootOf(parent(f)) : (fpair(f) >= 0 ? kSent : Index(-1));
792 });
793 space.fence();
794}
795
797template <class ParentView, class LabView, class RootFn>
798inline void faceLabelLeftover(const Exec& space, std::size_t nf, const ParentView& parent,
799 const LabView& faceLab, const RootFn& rootOf) {
800 Kokkos::parallel_for(
801 "pnm::leftover_label", R1(space, 0, nf), KOKKOS_LAMBDA(std::size_t f) {
802 if (faceLab(f) == kSent)
803 faceLab(f) = rootOf(parent(f));
804 });
805 space.fence();
806}
807
809template <class ParentView, class LabView>
810inline void leftoverInit(const Exec& space, std::size_t nf, const LabView& faceLab,
811 const ParentView& parent) {
812 using L = typename ParentView::non_const_value_type;
813 Kokkos::parallel_for(
814 "pnm::leftover_init", R1(space, 0, nf),
815 KOKKOS_LAMBDA(std::size_t f) { parent(f) = (faceLab(f) == kSent) ? L(f) : L(-1); });
816 space.fence();
817}
818
823template <class Geo, class LabView, class PairView, class CoreView, class NbLab, class NbPair,
824 class FlagView>
825inline void filmAttachSweep(const Exec& space, const Geo& geo, const LabView& faceLab,
826 const PairView& fpair, const CoreView& core, const NbLab& nbLab,
827 const NbPair& nbPair, const FlagView& changed) {
828 const Geo g = geo;
829 Kokkos::parallel_for(
830 "pnm::film_attach", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
831 const Index o = g.owned(ix, iy, iz);
832 for (int d = 0; d < 3; ++d) {
833 const Index f = 3 * o + d;
834 if (core(f) || faceLab(f) < 0)
835 continue;
836 Index best = faceLab(f);
837 const Index pk = fpair(f);
838 for (int dz = -1; dz <= 1; ++dz)
839 for (int dy = -1; dy <= 1; ++dy)
840 for (int dx = -1; dx <= 1; ++dx) {
841 const Index e2 = g.cell(ix + g.g + dx, iy + g.g + dy, iz + g.g + dz);
842 for (int d2 = 0; d2 < 3; ++d2) {
843 const Index l2 = nbLab(e2, d2);
844 if (l2 < 0 || l2 >= kSent || l2 >= best)
845 continue;
846 if (nbPair(e2, d2) != pk)
847 continue;
848 if (!facesAdjacent(dx, dy, dz, d, d2))
849 continue;
850 best = l2;
851 }
852 }
853 if (best < faceLab(f)) {
854 faceLab(f) = best;
855 changed() = 1;
856 }
857 }
858 });
859 space.fence();
860}
861
865 std::vector<std::pair<int, int>> throats; // (li, lj) per slot
866 std::vector<Index> keySorted; // patch roots, ascending
867 std::vector<int> slotOf; // keySorted position -> throat slot
868};
869inline ThroatSlots throatSlots(std::vector<std::pair<Index, Index>> rootPair) {
870 std::sort(rootPair.begin(), rootPair.end(),
871 [](const std::pair<Index, Index>& x, const std::pair<Index, Index>& y) {
872 return x.second != y.second ? x.second < y.second : x.first < y.first;
873 });
875 std::vector<Index> rootF;
876 for (const auto& rp : rootPair) {
877 ts.throats.push_back({int(rp.second >> 32), int(rp.second & 0x7fffffff)});
878 rootF.push_back(rp.first);
879 }
880 ts.keySorted = rootF;
881 std::sort(ts.keySorted.begin(), ts.keySorted.end());
882 ts.slotOf.resize(rootF.size());
883 for (std::size_t t = 0; t < rootF.size(); ++t) {
884 const auto it = std::lower_bound(ts.keySorted.begin(), ts.keySorted.end(), rootF[t]);
885 ts.slotOf[std::size_t(it - ts.keySorted.begin())] = int(t);
886 }
887 return ts;
888}
889
895template <class Geo, class LabView, class FieldView, class OpenView, class FaceLabView,
896 class KeyView, class SlotView, class AncView, class AccView>
897inline void throatFlux(const Exec& space, const Geo& geo, const LabView& flowLab,
898 const FieldView& u, const FieldView& v, const FieldView& w,
899 const OpenView& ox, const OpenView& oy, const OpenView& oz, bool hasOpen,
900 const FaceLabView& faceLab, const KeyView& keyD, const SlotView& slotD,
901 std::size_t nt, const AncView& ancX, const AncView& ancY,
902 const AncView& ancZ, std::array<float, 3> origin,
903 std::array<float, 3> spacing, std::array<int, 3> gdims, const AccView& Q,
904 const AccView& A, const AccView& Cx, const AccView& Cy, const AccView& Cz,
905 const AccView& resid) {
906 const Geo g = geo;
907 const double Ax = double(spacing[1]) * spacing[2], Ay = double(spacing[0]) * spacing[2],
908 Az = double(spacing[0]) * spacing[1];
909 const std::int64_t ntl = static_cast<std::int64_t>(nt);
910 const bool ho = hasOpen;
911 const float oxo = origin[0], oyo = origin[1], ozo = origin[2];
912 const float sx = spacing[0], sy = spacing[1], sz = spacing[2];
913 const double Lx = double(gdims[0]) * sx, Ly = double(gdims[1]) * sy, Lz = double(gdims[2]) * sz;
914 Kokkos::parallel_for(
915 "pnm::throat_flux", ownedRange(space, g), KOKKOS_LAMBDA(int ix, int iy, int iz) {
916 const int lx = ix + g.g, ly = iy + g.g, lz = iz + g.g;
917 const Index o = g.owned(ix, iy, iz);
918 const int a = flowLab(g.cell(lx, ly, lz));
919 for (int d = 0; d < 3; ++d) {
920 const Index nb = g.cell(lx + (d == 0), ly + (d == 1), lz + (d == 2));
921 const int b = flowLab(nb);
922 if (a == b)
923 continue;
924 // the shared face is the -d face of the +d neighbour: velocity/openness live there
925 const double vel = d == 0 ? u(nb) : (d == 1 ? v(nb) : w(nb));
926 const double opn = ho ? (d == 0 ? ox(nb) : (d == 1 ? oy(nb) : oz(nb))) : 1.0;
927 const double area = d == 0 ? Ax : (d == 1 ? Ay : Az);
928 const double q = opn * vel * area; // positive = flow from c to the +d neighbour
929 if (a > 0)
930 Kokkos::atomic_add(&resid(a - 1), q);
931 if (b > 0)
932 Kokkos::atomic_add(&resid(b - 1), -q);
933 const Index rt = faceLab(3 * o + d); // patch root, -1 = no throat face
934 if (rt >= 0) {
935 const Index k = bsearchKey(keyD, ntl, rt);
936 const int slot = k >= 0 ? slotD(k) : -1;
937 const int lo = a < b ? a : b;
938 if (slot >= 0) {
939 const double w0 = opn * area;
940 Kokkos::atomic_add(&Q(slot), a < b ? q : -q);
941 Kokkos::atomic_add(&A(slot), w0);
942 // face center, min-imaged relative to the lower pore's peak voxel center
943 int gx, gy, gz;
944 g.gcoord(lx, ly, lz, gx, gy, gz);
945 double fp[3] = {oxo + (gx + (d == 0 ? 0.5 : 0.0)) * double(sx),
946 oyo + (gy + (d == 1 ? 0.5 : 0.0)) * double(sy),
947 ozo + (gz + (d == 2 ? 0.5 : 0.0)) * double(sz)};
948 const double pc[3] = {ancX(lo - 1), ancY(lo - 1), ancZ(lo - 1)};
949 const double Lw[3] = {Lx, Ly, Lz};
950 for (int a2 = 0; a2 < 3; ++a2) {
951 double dv = fp[a2] - pc[a2];
952 dv -= Lw[a2] * Kokkos::round(dv / Lw[a2]);
953 fp[a2] = dv;
954 }
955 Kokkos::atomic_add(&Cx(slot), w0 * fp[0]);
956 Kokkos::atomic_add(&Cy(slot), w0 * fp[1]);
957 Kokkos::atomic_add(&Cz(slot), w0 * fp[2]);
958 }
959 }
960 }
961 });
962 space.fence();
963}
964
970inline std::vector<double> throatPressureDrops(
971 const std::vector<std::pair<int, int>>& throats, const std::vector<Pore>& pores,
972 const std::vector<double>& porePressure, const std::vector<double>& throatArea,
973 const std::vector<double>& Cx, const std::vector<double>& Cy, const std::vector<double>& Cz,
974 const std::vector<double>& ancX, const std::vector<double>& ancY,
975 const std::vector<double>& ancZ, std::array<float, 3> spacing, std::array<int, 3> gdims,
976 std::array<double, 3> grad_p) {
977 const std::size_t nt = throats.size();
978 std::vector<double> dp(nt);
979 const double L[3] = {double(gdims[0]) * spacing[0], double(gdims[1]) * spacing[1],
980 double(gdims[2]) * spacing[2]};
981 for (std::size_t t = 0; t < nt; ++t) {
982 const int li = throats[t].first, lj = throats[t].second;
983 const Pore& pi = pores[li - 1];
984 const Pore& pj = pores[lj - 1];
985 const double aw = throatArea[t];
986 const double anc[3] = {ancX[li - 1], ancY[li - 1], ancZ[li - 1]};
987 const double ancj[3] = {ancX[lj - 1], ancY[lj - 1], ancZ[lj - 1]};
988 double macro = 0.0;
989 const double pip[3] = {double(pi.x), double(pi.y), double(pi.z)};
990 const double pjp[3] = {double(pj.x), double(pj.y), double(pj.z)};
991 for (int a = 0; a < 3; ++a) {
992 const double ct = (Cx[t] * (a == 0) + Cy[t] * (a == 1) + Cz[t] * (a == 2)) / aw;
993 const long long Dc = llround((ancj[a] - anc[a]) / double(spacing[a])) -
994 llround(ct / double(spacing[a]) + 1e-6);
995 const long long k = llround(double(Dc) / gdims[a]);
996 macro += grad_p[a] * ((pjp[a] - pip[a]) - L[a] * double(k));
997 }
998 dp[t] = (porePressure[li - 1] - porePressure[lj - 1]) - macro;
999 }
1000 return dp;
1001}
1002
1003} // namespace kernels
1004} // namespace pnm
1005
1006#endif // PECLET_PNM_PORE_KERNELS_HPP
void forestInit(const Exec &space, const Geo &geo, const SdfView &sdf, const TargetView &target, const ChaseFn &chase, const FinalFn &finalOf, const PeakFn &peakOf)
Steepest-neighbour forest for the hold-at-ghost root resolution.
Kokkos::RangePolicy< Exec > R1
void floodSweep(const Exec &space, const Geo &geo, const SdfView &sdf, const LabelView &labels, const LabelView &labelsN, const FlagView &changed)
One Jacobi flood sweep: every unlabelled solid voxel takes the smallest labelled 26-neighbour (reads ...
Index steepest(const Geo &g, const SdfView &sdf, int lx, int ly, int lz, Index cgid, bool ascent)
Steepest 26-neighbour under the (sdf, gid) lexicographic order: ascent (pores) or descent (solids); r...
void boundaryPairs(const Exec &space, const Geo &geo, const SegView &seg, const PairView &pairs, const CountView &cnt, Index maxPairs)
Boundary pairs: for every owned cell, the (min,max) label pairs across its +x/+y/+z faces where the l...
void leftoverInit(const Exec &space, std::size_t nf, const LabView &faceLab, const ParentView &parent)
Leftover tier init: parent = f for the films no core patch reached, -1 otherwise.
void faceLabelLeftover(const Exec &space, std::size_t nf, const ParentView &parent, const LabView &faceLab, const RootFn &rootOf)
Leftover-tier labels: the still-kSent films (their own CCL tier) get rootOf(parent).
MD3 ownedRange(const Exec &space, const Geo &geo)
The owned box of a geometry as an MDRange policy.
bool isPeak(const Geo &g, const SdfView &sdf, int lx, int ly, int lz, float cv, Index cgid)
Local SDF maximum under the global (sdf, gid) lexicographic order (26-neighbourhood).
void faceMerge(const Exec &space, const Geo &geo, const ParentView &parent, const PairView &fpair, const FlagView &changed)
One union sweep of the face CCL over the faces with parent >= 0 (same pair key + adjacency; owned-own...
void faceLabelCore(const Exec &space, std::size_t nf, const ParentView &parent, const PairView &fpair, const LabView &faceLab, const RootFn &rootOf)
Core-tier labels: faceLab = rootOf(parent) for the CCL'd faces, kSent for the remaining interface (fi...
Index bsearchKey(const KeyView &keys, Index nkeys, typename KeyView::non_const_value_type key)
Device binary search over a sorted key view; returns the position or -1.
void cclFixpoint(const Exec &space, const ParentView &parent, std::size_t n, const FlagView &changed, const MergeFn &merge)
Union-find to fixpoint: merge() launches one union sweep (setting changed), then a flatten; repeat un...
ThroatSlots throatSlots(std::vector< std::pair< Index, Index > > rootPair)
int maxLabel(const Exec &space, const Geo &geo, const SegView &seg)
The largest label over the owned box (= the number of pore labels).
void faceMinGid(const Exec &space, const Geo &geo, const ParentView &parent, const MinView &mg)
Per-component minimum GLOBAL face id (3*gid+d) of the local face CCL, keyed by the component root: th...
Kokkos::MDRangePolicy< Exec, Kokkos::Rank< 3 > > MD3
void filmAttachSweep(const Exec &space, const Geo &geo, const LabView &faceLab, const PairView &fpair, const CoreView &core, const NbLab &nbLab, const NbPair &nbPair, const FlagView &changed)
One Jacobi min-propagation sweep of the film attachment: every film face (interface,...
void faceInit(const Exec &space, const Geo &geo, const LabView &flowLab, const SdfView &sdf, const OpenView &ox, const OpenView &oy, const OpenView &oz, bool hasOpen, const CoreView &core, const ParentView &parent, const PairView &fpair)
Interface predicate + CORE tier per owned face: an interface face separates two different positive fl...
constexpr Index kSent
Film-face sentinel of the per-patch throat labelling: "interface face awaiting attachment".
void detectPores(const Exec &space, const Geo &geo, const SdfView &sdf, std::array< float, 3 > origin, std::array< float, 3 > spacing, const PoreView &pores, const CountView &counter, int maxPores)
Emit the owned peaks (local SDF maxima, sdf > 0) as Pore records into pores (atomic slot counter coun...
double trilinear(const Geo &g, const FieldView &p, const double gp[3])
Trilinear interpolation of a cell-centered periodic field at grid position gp (in cells).
void floodFixpoint(const Exec &space, const Geo &geo, const SdfView &sdf, LabelView &labels, LabelView &labelsN, const FlagView &changed, const SyncFn &sync)
Jacobi flood to fixpoint.
Pore poreAt(const Geo &g, const SdfView &sdf, int lx, int ly, int lz, float ox, float oy, float oz, float sx, float sy, float sz)
The pore record at a peak: sub-voxel position by the SDF^2-weighted centroid of the 3^3 neighbourhood...
void throatFlux(const Exec &space, const Geo &geo, const LabView &flowLab, const FieldView &u, const FieldView &v, const FieldView &w, const OpenView &ox, const OpenView &oy, const OpenView &oz, bool hasOpen, const FaceLabView &faceLab, const KeyView &keyD, const SlotView &slotD, std::size_t nt, const AncView &ancX, const AncView &ancY, const AncView &ancZ, std::array< float, 3 > origin, std::array< float, 3 > spacing, std::array< int, 3 > gdims, const AccView &Q, const AccView &A, const AccView &Cx, const AccView &Cy, const AccView &Cz, const AccView &resid)
Accumulate over every owned +face where the flow-basin label changes: the openness-weighted MAC flux ...
void gradientWalk(const Exec &space, const GridGeo &geo, const SdfView &sdf, bool ascentAll, const EmitFn &emit)
Single-rank gradient walk: from every owned cell follow the steepest neighbour (ascent for pores,...
std::vector< std::pair< int, int > > uniquePairs(const PairView &pairs, std::size_t count)
Interleaved device pairs -> host (l1,l2) list, sorted and unique.
bool facesAdjacent(int dx, int dy, int dz, int d, int d2)
Face adjacency of the per-patch throat CCL: two faces (cell offset (dx,dy,dz), directions d and d2) c...
void cclFlatten(const Exec &space, const ParentView &parent, std::size_t n)
Path compression: every labelled entry points at its root.
void initMarkers(const Exec &space, const Geo &geo, const SdfView &sdf, float thr, const ParentView &parent)
Deep-solid markers: parent(o) = o where sdf < thr, else -1 (owned-dense).
void cclMergeMarkers(const Exec &space, const Geo &geo, const ParentView &parent, const FlagView &changed)
One union sweep of the marker CCL (26-connectivity, 13 forward neighbours, union-by-min via atomic_mi...
int resolveHoldAtGhost(const Exec &space, const Geo &geo, const TargetView &target)
One round of pointer jumping with the hold-at-ghost invariant: chase within the field,...
std::vector< double > throatPressureDrops(const std::vector< std::pair< int, int > > &throats, const std::vector< Pore > &pores, const std::vector< double > &porePressure, const std::vector< double > &throatArea, const std::vector< double > &Cx, const std::vector< double > &Cy, const std::vector< double > &Cz, const std::vector< double > &ancX, const std::vector< double > &ancY, const std::vector< double > &ancZ, std::array< float, 3 > spacing, std::array< int, 3 > gdims, std::array< double, 3 > grad_p)
Total-pressure drop per throat: periodic parts + the macroscopic gradient along the throat- anchored ...
std::int64_t Index
Global voxel / face id (== peclet::core::Index; asserted where core is included).
int get_idx(int x, int y, int z, I3 res)
V::value_type readScalar(const V &d)
Read a device scalar (the changed / counter flags) back to the host.
int wrapc(int a, int n)
Exec::memory_space Mem
void uploadVec(const std::vector< T > &h, const Kokkos::View< T *, Mem > &d)
Bulk host->device upload of a whole std::vector via one deep_copy over an unmanaged host view.
std::vector< typename V::value_type > downloadN(const V &d, std::size_t count)
Download the first count elements of a device view into a fresh std::vector via one deep_copy — only ...
Kokkos::DefaultExecutionSpace Exec
Extended-block geometry policy (the MPI pipeline): ghost-inclusive local box <-> global periodic grid...
Index cellAtGlobal(int gx, int gy, int gz) const
bool inOwned(int ix, int iy, int iz) const
std::size_t numExt() const
Index localOf(Index gid) const
gid -> extended-local linear index if the cell has an image in this extended box, else -1.
Index cell(int lx, int ly, int lz) const
bool onSurface(int ix, int iy, int iz) const
Is the owned cell within one cell of a block face (the only cells that can see a ghost)?
Index gid(int lx, int ly, int lz) const
void ownedCoords(Index o, int &ix, int &iy, int &iz) const
Index owned(int ix, int iy, int iz) const
static int wrapc(int a, int n)
void gcoord(int lx, int ly, int lz, int &gx, int &gy, int &gz) const
Index lidx(int lx, int ly, int lz) const
std::size_t numOwned() const
Index gidAt(int lx, int ly, int lz) const
gid of the (periodically wrapped) global cell under extended-local coords.
Single-rank geometry policy: the field IS the whole periodic grid, owned == field coords,...
Index cellAtGlobal(int gx, int gy, int gz) const
void ownedCoords(Index o, int &ix, int &iy, int &iz) const
bool inOwned(int, int, int) const
Index gid(int lx, int ly, int lz) const
Index owned(int ix, int iy, int iz) const
Index localOf(Index gid) const
Index cell(int lx, int ly, int lz) const
void gcoord(int lx, int ly, int lz, int &gx, int &gy, int &gz) const
std::size_t numOwned() const
static GridGeo of(I3 res)
Per-face field accessors for the film-attachment sweep: label / pair key of face (cell e,...
Index operator()(Index e, int d) const
Index operator()(Index e, int d) const
Global unique (patch root, pair key) list -> the throat list ordered by (pair, root fid),...
std::vector< Index > keySorted
std::vector< int > slotOf
std::vector< std::pair< int, int > > throats