flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
mac_ibm.hpp
Go to the documentation of this file.
1
11#ifndef PECLET_FLOW_MAC_IBM_HPP
12#define PECLET_FLOW_MAC_IBM_HPP
13
14#include <Kokkos_Core.hpp>
15#include <type_traits>
16
17#include "cut_cell_ibm.hpp" // IbmOverlayT, ibmFillEntry
18#include "mac_cutcell.hpp" // peclet::flow::C3, peclet::flow::ccSampleExt, CCConst
19#include "mac_cutcell_mg.hpp" // MReal (G.6: the overlay follows the operator storage precision)
20#include "policy.hpp"
21
22namespace peclet::flow {
23
24using mreal = MReal; // matrix coefficient type; float unless -DPECLET_FLOW_OPERATOR_DOUBLE (G.6)
25// MConst / ibmRbgsSweep stay FIXED float: a standalone kernel-level facility exercised by
26// tests/kokkos/test_ibm.cpp with its own literal-float coefficients, independent of the solver's
27// MReal -- unlike IbmOverlay below, it is not on IbmSolver's overlay-precision path (ibmRbgsSweep
28// itself has no caller in flow_ibm.hpp), so tying it to `mreal` would only make the test's fixed
29// float buffers fail to bind under -DPECLET_FLOW_OPERATOR_DOUBLE for no G.6 benefit.
30using MConst = Kokkos::View<const float*, CCMem>;
31// The canonical cut-cell overlay type: IbmOverlayT<Space, Real> instantiated at this build's
32// operator precision. Bit-identical to the pre-G.6 hardcoded-float IbmOverlay when mreal = float.
34// The ghost-projection overlay (ghost_projection.hpp) at this build's operator precision --
35// defined here, not in ghost_projection.hpp, because mac_cutcell_mg.hpp #includes
36// ghost_projection.hpp BEFORE its own `using MReal = ...`, so ghost_projection.hpp cannot name
37// MReal/mreal itself. Bit-identical to the pre-G.6 hardcoded-float GpOverlay when mreal = float.
39
40struct Off3 {
41 float x, y, z;
42};
43
44// Cut cell = fluid centre with at least one solid axis neighbour.
45template <class Real>
47 if (sc <= Real(0))
48 return false;
49 for (int k = 0; k < 6; ++k)
50 if (sn[k] < Real(0))
51 return true;
52 return false;
53}
54
55// Find cut cells over the inner block and build the Robust-Scaled overlay (port of ibm_count_ext_k
56// + ibm_geometry_ext_k): per inner cell, gather the 7 staggered SDF samples; if cut, atomically
57// claim a slot, set idMap[cell]=slot, and fill the overlay. counter/idMap are reset here. Returns
58// the cut count (overlay arrays must be sized >= number of inner cells). bc_type 0=Dirichlet,
59// 1=Neumann.
87template <int SCHEME>
88inline int buildIbmOverlay(CCConst sdf, C3 ext, int g, Off3 off, int bc_type, const IbmOverlay& ov,
89 Kokkos::View<int*, CCMem> idMap, Kokkos::View<int, CCMem> counter,
91 C3 nn = C3{0, 0, 0}, mreal slipLambda = mreal(0), int comp = -1,
92 Kokkos::View<int, CCMem> sandwichSkipped = Kokkos::View<int, CCMem>(),
93 mreal hpx = mreal(1), mreal hpy = mreal(1), mreal hpz = mreal(1)) {
94 CCExec space;
95 Kokkos::deep_copy(space, counter, 0);
96 Kokkos::deep_copy(space, idMap, -1);
97 const bool hasEx = tx.size() > 0;
98 const bool hasSlip = (slipLambda > mreal(0)) && (comp >= 0) && (SCHEME == 0);
99 const mreal lamBase = slipLambda;
100 const int slipComp = comp;
101 int* skipPtr = sandwichSkipped.data();
102 if (sandwichSkipped.data() != nullptr)
103 Kokkos::deep_copy(space, sandwichSkipped, 0);
104 using MD = MDRange3<CCExec>;
105 Kokkos::parallel_for(
106 "peclet::flow::ibm_build_overlay", MD(space, {g, g, g}, {ext.x - g, ext.y - g, ext.z - g}),
107 KOKKOS_LAMBDA(int lx, int ly, int lz) {
108 const long idx = (long)lx + (long)ly * ext.x + (long)lz * (long)ext.x * ext.y;
109 // G.6: (mreal) instead of a hard (float) -- a double build no longer narrows the SDF
110 // sample before the overlay is built from it.
111 const mreal sc = (mreal)ccSampleExt(sdf, ext, lx + off.x, ly + off.y, lz + off.z);
112 const int d[6][3] = {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}};
113 mreal sn[6];
114 for (int k = 0; k < 6; ++k)
115 sn[k] = (mreal)ccSampleExt(sdf, ext, lx + d[k][0] + off.x, ly + d[k][1] + off.y,
116 lz + d[k][2] + off.z);
117 if (!ibmIsCut(sc, sn))
118 return;
119 const int slot = Kokkos::atomic_fetch_add(&counter(), 1);
120 idMap(idx) = slot;
121 // Optional analytic-SDF exact crossings (setExactCrossings): tx/ty/tz are INNER-grid
122 // arrays, t_a(i) = exact crossing fraction from this component's staggered point i toward
123 // i + e_a (NaN = no crossing). +a dir: theta = t_a(i); -a dir: theta = 1 - t_a(i - e_a),
124 // periodic wrap on the inner grid.
125 mreal thEx[6];
126 if (hasEx) {
127 const int ix = lx - g, iy = ly - g, iz = lz - g;
128 auto wrap = [](int v, int n) {
129 v %= n;
130 return v < 0 ? v + n : v;
131 };
132 const CCConst* ta[3] = {&tx, &ty, &tz};
133 for (int a = 0; a < 3; ++a) {
134 const long ip = (long)ix + (long)iy * nn.x + (long)iz * (long)nn.x * nn.y;
135 const int mx = a == 0 ? wrap(ix - 1, nn.x) : ix;
136 const int my = a == 1 ? wrap(iy - 1, nn.y) : iy;
137 const int mz = a == 2 ? wrap(iz - 1, nn.z) : iz;
138 const long im = (long)mx + (long)my * nn.x + (long)mz * (long)nn.x * nn.y;
139 thEx[2 * a] = (mreal)(*ta[a])(ip);
140 thEx[2 * a + 1] = mreal(1) - (mreal)(*ta[a])(im); // NaN propagates -> fallback
141 }
142 }
143 const mreal hpv[3] = {hpx, hpy, hpz}; // h_a'; exactly 1 isotropic (trap 9)
144 mreal lamAxis[3];
145 if (hasSlip) {
146 // grad(sdf) from the same 7 samples; k = 0/2/4 are the +a neighbours, 1/3/5 the -a ones.
147 // Phase 2 (§6.1/§6.4): g_a = h_a'·n_a, so the PHYSICAL unit normal is g/h' renormalised.
148 const mreal gx = mreal(0.5) * (sn[0] - sn[1]) / hpv[0],
149 gy = mreal(0.5) * (sn[2] - sn[3]) / hpv[1],
150 gz = mreal(0.5) * (sn[4] - sn[5]) / hpv[2];
151 mreal gm = Kokkos::sqrt(gx * gx + gy * gy + gz * gz);
152 if (gm < mreal(1e-6))
153 gm = mreal(1e-6);
154 const mreal nrm[3] = {gx / gm, gy / gm, gz / gm};
155 mreal tang = mreal(1) - nrm[slipComp] * nrm[slipComp];
156 if (tang < mreal(0))
157 tang = mreal(0);
158 const mreal lamEff = lamBase * tang;
159 for (int a = 0; a < 3; ++a) {
160 mreal na = Kokkos::fabs(nrm[a]);
161 if (na < mreal(1e-3))
162 na = mreal(1e-3); // wall nearly parallel to this axis: cap at 1000x (still finite)
163 lamAxis[a] = lamEff / (hpv[a] * na);
164 }
165 }
166 ibmFillEntry<SCHEME>(ov, slot, (int)idx, sc, sn, bc_type, hasEx ? thEx : nullptr,
167 hasSlip ? lamAxis : nullptr, skipPtr);
168 });
169
170 int cnt = 0;
171 Kokkos::deep_copy(cnt, counter);
172 return cnt;
173}
174
175// Volume fraction theta = clamp(0.5 + sdf_sample, 0, 1) at the staggered point (lx+off, ...).
176//
177// PHASE 2 (anisotropic cells, doc/anisotropic_metric.md §6.4), RATE ONLY — this is the velocity
178// multigrid's staircase classification, so it decides a coarse operator's coefficient and never the
179// fixed point. `sd` is a distance in hRef units and the staircase wants the distance in CELLS
180// along the normal, i.e. `theta = clamp(0.5 + d' |m|)` with `|m| = sqrt(sum_a n_a^2 w_a)` the index
181// distance per unit physical distance along the normal (n = the physical unit normal of §6.1, from
182// the sampled gradient at the staggered point). The isotropic `|m|` is a `sqrt` of a unit vector,
183// which is NOT exactly 1, so the `!aniso` path runs the literal legacy expression (the same
184// host-bool dispatch C1 used for the const-coefficient smoother).
185// A degenerate gradient (|g/h'| below the guard) falls back to `|m| = 1`, i.e. to the legacy
186// expression: with no direction there is no per-axis information to use, and a plateau of the
187// sampled SDF must not be turned into theta = 0.5 on a deep cell.
188inline void ibmVolfrac(CCField theta, CCConst sdf, C3 ext, Off3 off, bool aniso = false,
189 double hpx = 1.0, double hpy = 1.0, double hpz = 1.0, double wx = 1.0,
190 double wy = 1.0, double wz = 1.0) {
192 using MD = MDRange3<CCExec>;
193 if (!aniso) {
194 Kokkos::parallel_for(
195 "peclet::flow::ibm_volfrac", MD(space, {0, 0, 0}, {ext.x, ext.y, ext.z}),
196 KOKKOS_LAMBDA(int lx, int ly, int lz) {
197 const long i = (long)lx + (long)ly * ext.x + (long)lz * (long)ext.x * ext.y;
198 const double sd = ccSampleExt(sdf, ext, lx + off.x, ly + off.y, lz + off.z);
199 const double t = 0.5 + sd;
200 theta(i) = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);
201 });
202 return;
203 }
204 Kokkos::parallel_for(
205 "peclet::flow::ibm_volfrac_aniso", MD(space, {0, 0, 0}, {ext.x, ext.y, ext.z}),
206 KOKKOS_LAMBDA(int lx, int ly, int lz) {
207 const long i = (long)lx + (long)ly * ext.x + (long)lz * (long)ext.x * ext.y;
208 const double px = lx + off.x, py = ly + off.y, pz = lz + off.z;
209 const double sd = ccSampleExt(sdf, ext, px, py, pz);
210 // g_a = h_a' n_a from the central difference at the SAME staggered point.
211 const double gx =
212 0.5 *
213 (ccSampleExt(sdf, ext, px + 1.0, py, pz) - ccSampleExt(sdf, ext, px - 1.0, py, pz)) /
214 hpx;
215 const double gy =
216 0.5 *
217 (ccSampleExt(sdf, ext, px, py + 1.0, pz) - ccSampleExt(sdf, ext, px, py - 1.0, pz)) /
218 hpy;
219 const double gz =
220 0.5 *
221 (ccSampleExt(sdf, ext, px, py, pz + 1.0) - ccSampleExt(sdf, ext, px, py, pz - 1.0)) /
222 hpz;
223 const double gm = Kokkos::sqrt(gx * gx + gy * gy + gz * gz);
224 double mm = 1.0;
225 if (gm > 1e-12) {
226 const double nx = gx / gm, ny = gy / gm, nz = gz / gm;
227 mm = Kokkos::sqrt(nx * nx * wx + ny * ny * wy + nz * nz * wz);
228 }
229 const double t = 0.5 + sd * mm;
230 theta(i) = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);
231 });
232}
233
234// Solid mask: 1 where the staggered SDF point is inside the solid OR exactly ON the wall
235// (sd<=0), else 0.
236//
237// WHY `<=` AND NOT `<` (WO-V6b part A). `sdf == 0` at a velocity DOF happens exactly when a flat
238// wall sits on a cell FACE (an integer coordinate): the staggered sample is the mean of the two
239// cell-centre values +-1/2. Every other consumer of the same field calls that point NON-fluid --
240// `ibmIsCut` (`sc <= 0` => not a cut cell, so no Dirichlet closure is built), `ibmCleanFluidMask`
241// (`sc <= 0` => solid), `ccFractionCore`/`ccFaceOpen` (`sd <= 0` => the face is CLOSED). With `<`
242// this mask alone called it fluid, so the DOF was left unpinned AND unclosed: an unconstrained
243// normal velocity sitting on the wall, invisible to the projection (its face is closed, so it
244// never enters the divergence) but read by the advection operator of its fluid neighbours. A
245// driven two-phase slit then diverges geometrically while every diagnostic stays healthy
246// (WO-V7 finding 1). With `<=` the DOF is pinned to the wall datum, which for a DOF lying exactly
247// ON the wall is the exact Dirichlet value, and the neighbouring fluid DOF's plain interior
248// stencil becomes a wall-resolved discretization.
249inline void ibmSolidMask(CCField mask, CCConst sdf, C3 ext, Off3 off) {
251 using MD = MDRange3<CCExec>;
252 Kokkos::parallel_for(
253 "peclet::flow::ibm_solid", MD(space, {0, 0, 0}, {ext.x, ext.y, ext.z}),
254 KOKKOS_LAMBDA(int lx, int ly, int lz) {
255 const long i = (long)lx + (long)ly * ext.x + (long)lz * (long)ext.x * ext.y;
256 const double sd = ccSampleExt(sdf, ext, lx + off.x, ly + off.y, lz + off.z);
257 mask(i) = (sd <= 0.0) ? 1.0 : 0.0;
258 });
259}
260
261// Clean-fluid-interior mask: 1 only at fluid cells with no solid neighbour (not cut, not solid).
262inline void ibmCleanFluidMask(CCField m, CCConst sdf, C3 ext, Off3 off) {
264 using MD = MDRange3<CCExec>;
265 Kokkos::parallel_for(
266 "peclet::flow::ibm_clean", MD(space, {0, 0, 0}, {ext.x, ext.y, ext.z}),
267 KOKKOS_LAMBDA(int lx, int ly, int lz) {
268 const long i = (long)lx + (long)ly * ext.x + (long)lz * (long)ext.x * ext.y;
269 const mreal sc = (mreal)ccSampleExt(sdf, ext, lx + off.x, ly + off.y, lz + off.z);
270 const int d[6][3] = {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}};
271 mreal sn[6];
272 for (int k = 0; k < 6; ++k)
273 sn[k] = (mreal)ccSampleExt(sdf, ext, lx + d[k][0] + off.x, ly + d[k][1] + off.y,
274 lz + d[k][2] + off.z);
275 const bool solid = (sc <= mreal(0));
276 m(i) = (solid || ibmIsCut(sc, sn)) ? 0.0 : 1.0;
277 });
278}
279
280// One Red-Black sweep of the variable-coefficient stencil: x[i] = (b[i] - sum(A_off*x_nbr)) /
281// A_C[i]. float matrix coeffs promote to double; solid cells pinned to 0. Call colour 0 then 1.
282// Host backends take a line-sweep form: one task per (y,z) pencil, contiguous stride-2 x-loop
283// starting at the colour's parity — no per-cell parity branch, unit-stride reads. Bit-identical to
284// the MDRange form (same-colour cells are independent, so update order within a colour cannot
285// change the result); the device keeps the MDRange form untouched.
286template <class MC>
287inline void ibmRbgsStencilColor(CCField x, CCConst b, MC AC, MC AW, MC AE, MC AS, MC AN, MC AB,
288 MC AT, CCConst solidmask, C3 ext, C3 og, int g, int color) {
290 const bool hasMask = (solidmask.extent(0) != 0);
291 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
292 const int nyi = ext.y - 2 * g, nzi = ext.z - 2 * g;
293 Kokkos::parallel_for(
294 "peclet::flow::ibm_rbgs", Kokkos::RangePolicy<CCExec>(space, 0, (long)nyi * nzi),
295 KOKKOS_LAMBDA(long t) {
296 const int ly = g + (int)(t % nyi), lz = g + (int)(t / nyi);
297 const long sx = 1, sy = ext.x, sz = (long)ext.x * ext.y;
298 const int P = (color + og.x + og.y + ly + og.z + lz) & 1;
299 for (int lx = g + ((P ^ (g & 1)) & 1); lx < ext.x - g; lx += 2) {
300 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
301 if (hasMask && solidmask(i) > 0.5) {
302 x(i) = 0.0;
303 continue;
304 }
305 const double ac = AC(i);
306 if (Kokkos::fabs(ac) < 1e-30)
307 continue;
308 const double s = (double)AE(i) * x(i + sx) + (double)AW(i) * x(i - sx) +
309 (double)AN(i) * x(i + sy) + (double)AS(i) * x(i - sy) +
310 (double)AT(i) * x(i + sz) + (double)AB(i) * x(i - sz);
311 x(i) = (b(i) - s) / ac;
312 }
313 });
314 return;
315 }
316 using MD = MDRange3<CCExec>;
317 Kokkos::parallel_for(
318 "peclet::flow::ibm_rbgs", MD(space, {g, g, g}, {ext.x - g, ext.y - g, ext.z - g}),
319 KOKKOS_LAMBDA(int lx, int ly, int lz) {
320 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
321 return;
322 const long sx = 1, sy = ext.x, sz = (long)ext.x * ext.y;
323 const long i = (long)lx + (long)ly * ext.x + (long)lz * sz;
324 if (hasMask && solidmask(i) > 0.5) {
325 x(i) = 0.0;
326 return;
327 }
328 const double ac = AC(i);
329 if (Kokkos::fabs(ac) < 1e-30)
330 return;
331 const double s = (double)AE(i) * x(i + sx) + (double)AW(i) * x(i - sx) +
332 (double)AN(i) * x(i + sy) + (double)AS(i) * x(i - sy) +
333 (double)AT(i) * x(i + sz) + (double)AB(i) * x(i - sz);
334 x(i) = (b(i) - s) / ac;
335 });
336}
337
338// ibmRbgsStencilColor + a fused max|Δx| reduction over the swept colour (values already in
339// registers — no extra memory pass). The momentum tolerance stop runs this as the SECOND colour of
340// a sweep: one colour's max increment is the convergence proxy. Same math as the plain sweep.
341template <class MC>
342inline double ibmRbgsStencilColorDu(CCField x, CCConst b, MC AC, MC AW, MC AE, MC AS, MC AN, MC AB,
343 MC AT, CCConst solidmask, C3 ext, C3 og, int g, int color) {
345 const bool hasMask = (solidmask.extent(0) != 0);
346 double du = 0.0;
347 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
348 const int nyi = ext.y - 2 * g, nzi = ext.z - 2 * g;
349 Kokkos::parallel_reduce(
350 "peclet::flow::ibm_rbgs_du", Kokkos::RangePolicy<CCExec>(space, 0, (long)nyi * nzi),
351 KOKKOS_LAMBDA(long t, double& m) {
352 const int ly = g + (int)(t % nyi), lz = g + (int)(t / nyi);
353 const long sx = 1, sy = ext.x, sz = (long)ext.x * ext.y;
354 const int P = (color + og.x + og.y + ly + og.z + lz) & 1;
355 for (int lx = g + ((P ^ (g & 1)) & 1); lx < ext.x - g; lx += 2) {
356 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
357 if (hasMask && solidmask(i) > 0.5) {
358 x(i) = 0.0;
359 continue;
360 }
361 const double ac = AC(i);
362 if (Kokkos::fabs(ac) < 1e-30)
363 continue;
364 const double s = (double)AE(i) * x(i + sx) + (double)AW(i) * x(i - sx) +
365 (double)AN(i) * x(i + sy) + (double)AS(i) * x(i - sy) +
366 (double)AT(i) * x(i + sz) + (double)AB(i) * x(i - sz);
367 const double xn = (b(i) - s) / ac;
368 const double d = Kokkos::fabs(xn - x(i));
369 if (d > m)
370 m = d;
371 x(i) = xn;
372 }
373 },
374 Kokkos::Max<double>(du));
375 return du;
376 }
377 using MD = MDRange3<CCExec>;
378 Kokkos::parallel_reduce(
379 "peclet::flow::ibm_rbgs_du", MD(space, {g, g, g}, {ext.x - g, ext.y - g, ext.z - g}),
380 KOKKOS_LAMBDA(int lx, int ly, int lz, double& m) {
381 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
382 return;
383 const long sx = 1, sy = ext.x, sz = (long)ext.x * ext.y;
384 const long i = (long)lx + (long)ly * ext.x + (long)lz * sz;
385 if (hasMask && solidmask(i) > 0.5) {
386 x(i) = 0.0;
387 return;
388 }
389 const double ac = AC(i);
390 if (Kokkos::fabs(ac) < 1e-30)
391 return;
392 const double s = (double)AE(i) * x(i + sx) + (double)AW(i) * x(i - sx) +
393 (double)AN(i) * x(i + sy) + (double)AS(i) * x(i - sy) +
394 (double)AT(i) * x(i + sz) + (double)AB(i) * x(i - sz);
395 const double xn = (b(i) - s) / ac;
396 const double d = Kokkos::fabs(xn - x(i));
397 if (d > m)
398 m = d;
399 x(i) = xn;
400 },
401 Kokkos::Max<double>(du));
402 return du;
403}
404
405// Box-restricted variant for the distributed overlap smoother: sweeps [rlo,rhi) only, skipping
406// [slo,shi) (pass slo==shi to skip nothing). Interior-then-shell split of one colour's sweep is
407// bit-identical to the full sweep — a colour's cells never read same-colour cells (see
408// cutcellSmoothColorBox in mac_pressure.hpp).
409template <class MC>
410inline void ibmRbgsStencilColorBox(CCField x, CCConst b, MC AC, MC AW, MC AE, MC AS, MC AN, MC AB,
411 MC AT, CCConst solidmask, C3 ext, C3 og, int color, C3 rlo,
412 C3 rhi, C3 slo, C3 shi) {
413 if (rhi.x <= rlo.x || rhi.y <= rlo.y || rhi.z <= rlo.z)
414 return;
416 const bool hasMask = (solidmask.extent(0) != 0);
417 using MD = MDRange3<CCExec>;
418 Kokkos::parallel_for(
419 "peclet::flow::ibm_rbgs_box", MD(space, {rlo.x, rlo.y, rlo.z}, {rhi.x, rhi.y, rhi.z}),
420 KOKKOS_LAMBDA(int lx, int ly, int lz) {
421 if (lx >= slo.x && lx < shi.x && ly >= slo.y && ly < shi.y && lz >= slo.z && lz < shi.z)
422 return;
423 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
424 return;
425 const long sx = 1, sy = ext.x, sz = (long)ext.x * ext.y;
426 const long i = (long)lx + (long)ly * ext.x + (long)lz * sz;
427 if (hasMask && solidmask(i) > 0.5) {
428 x(i) = 0.0;
429 return;
430 }
431 const double ac = AC(i);
432 if (Kokkos::fabs(ac) < 1e-30)
433 return;
434 const double s = (double)AE(i) * x(i + sx) + (double)AW(i) * x(i - sx) +
435 (double)AN(i) * x(i + sy) + (double)AS(i) * x(i - sy) +
436 (double)AT(i) * x(i + sz) + (double)AB(i) * x(i - sz);
437 x(i) = (b(i) - s) / ac;
438 });
439}
440
441// ibmRbgsStencilColorBox + the fused max|Δx| reduction (the box form of ibmRbgsStencilColorDu),
442// for the distributed overlap of the momentum tolerance stop: interior-then-shell partial maxima
443// combine by max, which is order-independent — max(interior, shell) is bit-identical to the
444// one-pass reduction.
445template <class MC>
446inline double ibmRbgsStencilColorDuBox(CCField x, CCConst b, MC AC, MC AW, MC AE, MC AS, MC AN,
447 MC AB, MC AT, CCConst solidmask, C3 ext, C3 og, int color,
448 C3 rlo, C3 rhi, C3 slo, C3 shi) {
449 if (rhi.x <= rlo.x || rhi.y <= rlo.y || rhi.z <= rlo.z)
450 return 0.0;
452 const bool hasMask = (solidmask.extent(0) != 0);
453 double du = 0.0;
454 using MD = MDRange3<CCExec>;
455 Kokkos::parallel_reduce(
456 "peclet::flow::ibm_rbgs_du_box", MD(space, {rlo.x, rlo.y, rlo.z}, {rhi.x, rhi.y, rhi.z}),
457 KOKKOS_LAMBDA(int lx, int ly, int lz, double& m) {
458 if (lx >= slo.x && lx < shi.x && ly >= slo.y && ly < shi.y && lz >= slo.z && lz < shi.z)
459 return;
460 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
461 return;
462 const long sx = 1, sy = ext.x, sz = (long)ext.x * ext.y;
463 const long i = (long)lx + (long)ly * ext.x + (long)lz * sz;
464 if (hasMask && solidmask(i) > 0.5) {
465 x(i) = 0.0;
466 return;
467 }
468 const double ac = AC(i);
469 if (Kokkos::fabs(ac) < 1e-30)
470 return;
471 const double s = (double)AE(i) * x(i + sx) + (double)AW(i) * x(i - sx) +
472 (double)AN(i) * x(i + sy) + (double)AS(i) * x(i - sy) +
473 (double)AT(i) * x(i + sz) + (double)AB(i) * x(i - sz);
474 const double xn = (b(i) - s) / ac;
475 const double d = Kokkos::fabs(xn - x(i));
476 if (d > m)
477 m = d;
478 x(i) = xn;
479 },
480 Kokkos::Max<double>(du));
481 return du;
482}
483
484inline void ibmRbgsSweep(CCField x, CCConst b, MConst AC, MConst AW, MConst AE, MConst AS,
485 MConst AN, MConst AB, MConst AT, CCConst solidmask, C3 ext, C3 og, int g) {
486 ibmRbgsStencilColor(x, b, AC, AW, AE, AS, AN, AB, AT, solidmask, ext, og, g, 0);
487 ibmRbgsStencilColor(x, b, AC, AW, AE, AS, AN, AB, AT, solidmask, ext, og, g, 1);
488}
489
490} // namespace peclet::flow
491
492#endif // PECLET_FLOW_MAC_IBM_HPP
flow — portable (Kokkos) Robust-Scaled cut-cell IBM primitives + per-cut-cell overlay build.
flow — portable (Kokkos) cut-cell pressure-operator face openness from an SDF.
flow — portable (Kokkos) geometric multigrid for the cut-cell (variable-openness) pressure Poisson.
Slot slot(const Kokkos::View< double *, CCMem > &ks, int k)
void ibmSolidMask(CCField mask, CCConst sdf, C3 ext, Off3 off)
Definition mac_ibm.hpp:249
void ibmCleanFluidMask(CCField m, CCConst sdf, C3 ext, Off3 off)
Definition mac_ibm.hpp:262
void ibmVolfrac(CCField theta, CCConst sdf, C3 ext, Off3 off, bool aniso=false, double hpx=1.0, double hpy=1.0, double hpz=1.0, double wx=1.0, double wy=1.0, double wz=1.0)
Definition mac_ibm.hpp:188
MReal mreal
Definition mac_ibm.hpp:24
void ibmRbgsSweep(CCField x, CCConst b, MConst AC, MConst AW, MConst AE, MConst AS, MConst AN, MConst AB, MConst AT, CCConst solidmask, C3 ext, C3 og, int g)
Definition mac_ibm.hpp:484
Kokkos::View< double *, CCMem > CCField
bool ibmIsCut(Real sc, const Real sn[6])
Definition mac_ibm.hpp:46
void ibmRbgsStencilColorBox(CCField x, CCConst b, MC AC, MC AW, MC AE, MC AS, MC AN, MC AB, MC AT, CCConst solidmask, C3 ext, C3 og, int color, C3 rlo, C3 rhi, C3 slo, C3 shi)
Definition mac_ibm.hpp:410
Kokkos::DefaultExecutionSpace CCExec
int buildIbmOverlay(CCConst sdf, C3 ext, int g, Off3 off, int bc_type, const IbmOverlay &ov, Kokkos::View< int *, CCMem > idMap, Kokkos::View< int, CCMem > counter, CCConst tx=CCConst(), CCConst ty=CCConst(), CCConst tz=CCConst(), C3 nn=C3{0, 0, 0}, mreal slipLambda=mreal(0), int comp=-1, Kokkos::View< int, CCMem > sandwichSkipped=Kokkos::View< int, CCMem >(), mreal hpx=mreal(1), mreal hpy=mreal(1), mreal hpz=mreal(1))
slipLambda (WO-V6b, in CELLS, 0 = the validated no-slip closure) + comp (which velocity component thi...
Definition mac_ibm.hpp:88
double ibmRbgsStencilColorDuBox(CCField x, CCConst b, MC AC, MC AW, MC AE, MC AS, MC AN, MC AB, MC AT, CCConst solidmask, C3 ext, C3 og, int color, C3 rlo, C3 rhi, C3 slo, C3 shi)
Definition mac_ibm.hpp:446
Kokkos::View< const float *, CCMem > MConst
double ccSampleExt(CCConst sdf, C3 ext, double x, double y, double z)
double ibmRbgsStencilColorDu(CCField x, CCConst b, MC AC, MC AW, MC AE, MC AS, MC AN, MC AB, MC AT, CCConst solidmask, C3 ext, C3 og, int g, int color)
Definition mac_ibm.hpp:342
void ibmRbgsStencilColor(CCField x, CCConst b, MC AC, MC AW, MC AE, MC AS, MC AN, MC AB, MC AT, CCConst solidmask, C3 ext, C3 og, int g, int color)
Definition mac_ibm.hpp:287
Kokkos::View< const double *, CCMem > CCConst
flow — the multidimensional iteration policies: iteration order follows storage.
static constexpr double AC