flow 0.4.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
mac_cutcell.hpp
Go to the documentation of this file.
1
9#ifndef PECLET_FLOW_MAC_CUTCELL_HPP
10#define PECLET_FLOW_MAC_CUTCELL_HPP
11
12#include <cstdlib>
13#include <Kokkos_Core.hpp>
14#include <Kokkos_MathematicalFunctions.hpp>
15#include <type_traits>
16#include <utility>
17
18namespace peclet::flow {
19
20using CCExec = Kokkos::DefaultExecutionSpace;
21using CCMem = CCExec::memory_space;
22using CCField = Kokkos::View<double*, CCMem>;
23using CCConst = Kokkos::View<const double*, CCMem>;
24
25struct C3 {
26 int x, y, z;
27};
28
29// Masked fluid fraction of a face from its SDF samples (centre + 6 axis neighbours). type 1/2/3 =
30// x/y/z face. (sd<=0 => closed.) Verbatim from cc_fraction_core.
31KOKKOS_INLINE_FUNCTION double ccFractionCore(double sd, double sxp, double sxm, double syp,
32 double sym, double szp, double szm, int type,
33 double dx, double dy, double dz) {
34 if (sd <= 0.0)
35 return 0.0;
36 const double gx = (sxp - sxm) / (2.0 * dx), gy = (syp - sym) / (2.0 * dy),
37 gz = (szp - szm) / (2.0 * dz);
38 double gmag = Kokkos::sqrt(gx * gx + gy * gy + gz * gz);
39 if (gmag < 1e-6)
40 gmag = 1e-6;
41 const double nx = gx / gmag, ny = gy / gmag, nz = gz / gmag;
42 double denom = (type == 1) ? (Kokkos::fabs(ny) * dy + Kokkos::fabs(nz) * dz)
43 : (type == 2) ? (Kokkos::fabs(nx) * dx + Kokkos::fabs(nz) * dz)
44 : (Kokkos::fabs(nx) * dx + Kokkos::fabs(ny) * dy);
45 if (denom < 1e-9)
46 denom = 1e-9;
47 double frac = 0.5 + sd / denom;
48 if (frac < 0.0)
49 frac = 0.0;
50 if (frac > 1.0)
51 frac = 1.0;
52 return frac;
53}
54
55KOKKOS_INLINE_FUNCTION double ccSampleExt(CCConst sdf, C3 ext, double x, double y, double z) {
56 const double fx = Kokkos::floor(x), fy = Kokkos::floor(y), fz = Kokkos::floor(z);
57 const double wx = x - fx, wy = y - fy, wz = z - fz;
58 int x0 = (int)fx, y0 = (int)fy, z0 = (int)fz;
59 auto cl = [](int v, int n) { return v < 0 ? 0 : (v >= n ? n - 1 : v); };
60 const int x1 = cl(x0 + 1, ext.x), y1 = cl(y0 + 1, ext.y), z1 = cl(z0 + 1, ext.z);
61 x0 = cl(x0, ext.x);
62 y0 = cl(y0, ext.y);
63 z0 = cl(z0, ext.z);
64 const long sy = ext.x, sz = static_cast<long>(ext.x) * ext.y;
65 auto F = [&](int xx, int yy, int zz) {
66 return sdf(static_cast<long>(xx) + static_cast<long>(yy) * sy + static_cast<long>(zz) * sz);
67 };
68 const double c00 = F(x0, y0, z0) * (1 - wx) + F(x1, y0, z0) * wx;
69 const double c10 = F(x0, y1, z0) * (1 - wx) + F(x1, y1, z0) * wx;
70 const double c01 = F(x0, y0, z1) * (1 - wx) + F(x1, y0, z1) * wx;
71 const double c11 = F(x0, y1, z1) * (1 - wx) + F(x1, y1, z1) * wx;
72 const double c0 = c00 * (1 - wy) + c10 * wy, c1 = c01 * (1 - wy) + c11 * wy;
73 return c0 * (1 - wz) + c1 * wz;
74}
75
76KOKKOS_INLINE_FUNCTION double ccFaceOpen(CCConst sdf, C3 ext, double fx, double fy, double fz,
77 int type, double dx, double dy, double dz) {
78 const double sd = ccSampleExt(sdf, ext, fx, fy, fz);
79 if (sd <= 0.0)
80 return 0.0;
81 const double e = 1.0;
82 return ccFractionCore(
83 sd, ccSampleExt(sdf, ext, fx + e, fy, fz), ccSampleExt(sdf, ext, fx - e, fy, fz),
84 ccSampleExt(sdf, ext, fx, fy + e, fz), ccSampleExt(sdf, ext, fx, fy - e, fz),
85 ccSampleExt(sdf, ext, fx, fy, fz + e), ccSampleExt(sdf, ext, fx, fy, fz - e), type, dx, dy,
86 dz);
87}
88
89// Fluid fraction (sd >= 0 = fluid) of a triangle with linear vertex values (a, b, c): the exact
90// linear-simplex level-set area fraction. Denominators (x-y)(x-z) are strictly positive whenever
91// the signs are mixed (x is the odd one out), guarded against exact-zero degeneracies.
92KOKKOS_INLINE_FUNCTION double ccTriFrac(double a, double b, double c) {
93 const bool pa = a >= 0.0, pb = b >= 0.0, pc = c >= 0.0;
94 const int np = (pa ? 1 : 0) + (pb ? 1 : 0) + (pc ? 1 : 0);
95 if (np == 3)
96 return 1.0;
97 if (np == 0)
98 return 0.0;
99 double x, y, z;
100 if (np == 1) { // rotate the positive vertex into x
101 if (pa) { x = a; y = b; z = c; } else if (pb) { x = b; y = c; z = a; } else { x = c; y = a; z = b; }
102 const double den = (x - y) * (x - z);
103 return den > 1e-300 ? (x * x) / den : 1.0;
104 }
105 // np == 2: rotate the negative vertex into x
106 if (!pa) { x = a; y = b; z = c; } else if (!pb) { x = b; y = c; z = a; } else { x = c; y = a; z = b; }
107 const double den = (x - y) * (x - z);
108 return 1.0 - (den > 1e-300 ? (x * x) / den : 1.0);
109}
110
111// MARCHING-SQUARES face openness (setApertureOrder(2), 2026-08-26): the O(h^2) upgrade of
112// ccFaceOpen motivated by the measured convexity bias of the one-sample linear model (the
113// tangent-plane estimate over-closes apertures on convex solids by +0.59%/+0.27% in bed
114// permeability at R=8/12, decaying ~h^2 -- flow doc/collocated_paper_plan.md row 51). Five
115// trilinear samples per face (4 corners + center), triangle-fan decomposition (4 triangles of
116// area 1/4 around the center sample -- no marching-squares saddle ambiguity), exact linear
117// fraction per triangle. Sub-resolution floor 1e-6 (measured lesson: alpha ~ 1e-12 rows from
118// exact geometry destroy the operator conditioning; the crude model's clip was an accidental
119// regularizer -- the floor makes the regularization explicit).
120KOKKOS_INLINE_FUNCTION double ccFaceOpenMS(CCConst sdf, C3 ext, double fx, double fy, double fz,
121 int type) {
122 const double e = 0.5;
123 double t1x = 0, t1y = 0, t1z = 0, t2x = 0, t2y = 0, t2z = 0; // tangent half-offsets
124 if (type == 1) {
125 t1y = e;
126 t2z = e;
127 } else if (type == 2) {
128 t1x = e;
129 t2z = e;
130 } else {
131 t1x = e;
132 t2y = e;
133 }
134 const double c00 = ccSampleExt(sdf, ext, fx - t1x - t2x, fy - t1y - t2y, fz - t1z - t2z);
135 const double c10 = ccSampleExt(sdf, ext, fx + t1x - t2x, fy + t1y - t2y, fz + t1z - t2z);
136 const double c11 = ccSampleExt(sdf, ext, fx + t1x + t2x, fy + t1y + t2y, fz + t1z + t2z);
137 const double c01 = ccSampleExt(sdf, ext, fx - t1x + t2x, fy - t1y + t2y, fz - t1z + t2z);
138 const double cc = ccSampleExt(sdf, ext, fx, fy, fz);
139 const double frac = 0.25 * (ccTriFrac(c00, c10, cc) + ccTriFrac(c10, c11, cc) +
141 if (frac < 1e-6)
142 return 0.0;
143 if (frac > 1.0 - 1e-12)
144 return 1.0;
145 return frac;
146}
147
148// Fill staggered face openness over the whole extended block (ox[i] = -x face of cell i, etc.).
149// order 1 = the shipped one-sample linear model (byte-identical default); order 2 =
150// marching-squares (ccFaceOpenMS).
151inline void buildOpenness(CCField ox, CCField oy, CCField oz, CCConst sdf, C3 ext, double dx,
152 double dy, double dz, int order = 1) {
154 using MD = Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>;
155 if (order >= 2) {
156 Kokkos::parallel_for(
157 "peclet::flow::cc_open_ms", MD(space, {0, 0, 0}, {ext.x, ext.y, ext.z}),
158 KOKKOS_LAMBDA(int lx, int ly, int lz) {
159 const long i = static_cast<long>(lx) + static_cast<long>(ly) * ext.x +
160 static_cast<long>(lz) * static_cast<long>(ext.x) * ext.y;
161 ox(i) = ccFaceOpenMS(sdf, ext, lx - 0.5, ly, lz, 1);
162 oy(i) = ccFaceOpenMS(sdf, ext, lx, ly - 0.5, lz, 2);
163 oz(i) = ccFaceOpenMS(sdf, ext, lx, ly, lz - 0.5, 3);
164 });
165 return;
166 }
167 Kokkos::parallel_for(
168 "peclet::flow::cc_open", MD(space, {0, 0, 0}, {ext.x, ext.y, ext.z}),
169 KOKKOS_LAMBDA(int lx, int ly, int lz) {
170 const long i = static_cast<long>(lx) + static_cast<long>(ly) * ext.x +
171 static_cast<long>(lz) * static_cast<long>(ext.x) * ext.y;
172 ox(i) = ccFaceOpen(sdf, ext, lx - 0.5, ly, lz, 1, dx, dy, dz);
173 oy(i) = ccFaceOpen(sdf, ext, lx, ly - 0.5, lz, 2, dx, dy, dz);
174 oz(i) = ccFaceOpen(sdf, ext, lx, ly, lz - 0.5, 3, dx, dy, dz);
175 });
176}
177
178// HOST-only serial cutoff: below this many cells an OpenMP launch costs more than the work it does
179// (fork/join is ~20-30 us at 24 threads), so run the loop sequentially instead. That is
180// BIT-IDENTICAL for elementwise kernels and for colored sweeps (same-color cells are independent,
181// so the order within a sweep is irrelevant); reductions are deliberately NOT cut over — that would
182// change the FP summation order with the block size, and the multi-rank bit-exactness contract is
183// worth more than the microseconds. The device path never sees this (hostRunSerial is false there).
184//
185// MEASURED (5965WX, 24 threads, per-level V-cycle timer): the win lives almost entirely in the MG's
186// BOTTOM level, which fires ~24 trivial launches per V-cycle — 64^3/rank L4(4^3): 0.018 -> 0.007 s
187// per 50 V-cycles, ~8% of the whole V-cycle; 128^3/rank ~1.5%; 256^3/rank (the fat-rank target
188// size) ~0.2%, i.e. below run-to-run noise. A LARGER cutoff back-fires (131072 serializes levels
189// with real work: 64^3 projection 16.6 -> 20.6 ms/step), so keep it small.
190inline long hostSerialCellCutoff() {
191 static const long n = [] {
192 const char* e = std::getenv("PECLET_FLOW_HOST_SERIAL_CELLS");
193 return e ? std::atol(e) : 8192L;
194 }();
195 return n;
196}
197// True when a host launch of `cells` cells should run sequentially instead (always false on device).
198inline bool hostRunSerial(long cells) {
199 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>)
200 return cells > 0 && cells < hostSerialCellCutoff();
201 else
202 return false;
203}
204
205// Launch a 3-D elementwise kernel with a HOST-TUNED MDRange tiling: one full-x row per tile (the
206// Kokkos host default tiles are tiny, wrecking streaming locality — measured 5.6x on the RB-GS
207// smoother when its loop went x-contiguous). The lambda is passed through UNCHANGED, and the
208// device keeps the default MDRange tiling untouched — byte-identical results on both backends
209// (elementwise kernels are order-independent).
210template <class F>
211inline void ccFor3(const char* name, C3 lo, C3 hi, F f) {
213 using MD = Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>;
214 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
215 if (hostRunSerial((long)(hi.x - lo.x) * (hi.y - lo.y) * (hi.z - lo.z))) {
216 for (int lz = lo.z; lz < hi.z; ++lz) // too small to be worth a fork/join (bit-identical)
217 for (int ly = lo.y; ly < hi.y; ++ly)
218 for (int lx = lo.x; lx < hi.x; ++lx)
219 f(lx, ly, lz);
220 return;
221 }
222 Kokkos::parallel_for(
223 name, MD(space, {lo.x, lo.y, lo.z}, {hi.x, hi.y, hi.z}, {hi.x - lo.x, 2, 2}), f);
224 } else {
225 Kokkos::parallel_for(name, MD(space, {lo.x, lo.y, lo.z}, {hi.x, hi.y, hi.z}), f);
226 }
227}
228
229// Reduction sibling of ccFor3 (same host tiling rationale). NOTE: the host tiling changes the
230// FP accumulation order of sum-reductions — numerically legitimate (parallel reductions are
231// unordered by contract) but not bit-identical to the previous host rounding.
232template <class F, class R>
233inline void ccReduce3(const char* name, C3 lo, C3 hi, F f, R&& reducer) {
235 using MD = Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>;
236 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
237 Kokkos::parallel_reduce(
238 name, MD(space, {lo.x, lo.y, lo.z}, {hi.x, hi.y, hi.z}, {hi.x - lo.x, 2, 2}), f,
239 std::forward<R>(reducer));
240 } else {
241 Kokkos::parallel_reduce(name, MD(space, {lo.x, lo.y, lo.z}, {hi.x, hi.y, hi.z}), f,
242 std::forward<R>(reducer));
243 }
244}
245
246} // namespace peclet::flow
247
248#endif // PECLET_FLOW_MAC_CUTCELL_HPP
long hostSerialCellCutoff()
CCExec::memory_space CCMem
double ccFaceOpen(CCConst sdf, C3 ext, double fx, double fy, double fz, int type, double dx, double dy, double dz)
double ccFaceOpenMS(CCConst sdf, C3 ext, double fx, double fy, double fz, int type)
double ccFractionCore(double sd, double sxp, double sxm, double syp, double sym, double szp, double szm, int type, double dx, double dy, double dz)
double ccTriFrac(double a, double b, double c)
void ccReduce3(const char *name, C3 lo, C3 hi, F f, R &&reducer)
void ccFor3(const char *name, C3 lo, C3 hi, F f)
void ibmFillEntry(const OV &o, int list_idx, int c_idx, float sdf_c, const float sdf_n[6], int bc_type, const float *thEx)
Kokkos::View< double *, CCMem > CCField
Kokkos::DefaultExecutionSpace CCExec
double ccSampleExt(CCConst sdf, C3 ext, double x, double y, double z)
void buildOpenness(CCField ox, CCField oy, CCField oz, CCConst sdf, C3 ext, double dx, double dy, double dz, int order=1)
bool hostRunSerial(long cells)
Kokkos::View< const double *, CCMem > CCConst
static constexpr double F