flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
mac_pressure.hpp
Go to the documentation of this file.
1
10#ifndef PECLET_FLOW_MAC_PRESSURE_HPP
11#define PECLET_FLOW_MAC_PRESSURE_HPP
12
13#include <Kokkos_Core.hpp>
14#include <type_traits>
15
16#include "mac_cutcell.hpp"
17#include "policy.hpp"
18
19namespace peclet::flow {
20
21// --- A0: the per-cell bodies (doc/vof_step_performance_design.md §4.5 rule H, §5.2) -------------
22// Every launch form of these kernels -- the device MDRange, the host pencil, and the single-team
23// kernels to come -- calls the SAME inline body, so the per-cell arithmetic cannot drift between
24// them. Each expression is a verbatim copy of the kernel it came from, in the same order: a
25// rewritten expression can contract into a different FMA chain on the device (design note R1).
26// The neighbour INDICES of the field being smoothed or applied are arguments (xp = i + sx,
27// xm = i - sx, ...), so a caller can hand a periodically wrapped index instead of a ghost one; the
28// coefficients are always read at their own (unwrapped) indices.
29
30// A = -div(open grad): AC = sum of the 6 face terms (openness*gf), off-diagonal across each face =
31// -term. ox[i] is the -x face openness of cell i (== +x face of cell i-1). (mg_build_op_k port.)
32template <class OpV, class OV>
33KOKKOS_INLINE_FUNCTION void cutcellBuildOpCell(const OpV& AC, const OpV& AW, const OpV& AE,
34 const OpV& AS, const OpV& AN, const OpV& AB,
35 const OpV& AT, const OV& ox, const OV& oy,
36 const OV& oz, long i, long sx, long sy, long sz,
37 double gfx, double gfy, double gfz) {
38 const double tw = ox(i) * gfx, te = ox(i + sx) * gfx;
39 const double ts = oy(i) * gfy, tn = oy(i + sy) * gfy;
40 const double tb = oz(i) * gfz, tt = oz(i + sz) * gfz;
41 AW(i) = -tw;
42 AE(i) = -te;
43 AS(i) = -ts;
44 AN(i) = -tn;
45 AB(i) = -tb;
46 AT(i) = -tt;
47 AC(i) = te + tw + tn + ts + tt + tb;
48}
49// One red/black Gauss-Seidel update of cell i: phi = (b - offdiag)/AC; AC~0 (fully solid) cells are
50// decoupled and keep phi.
51template <class PV, class BV, class OpV>
52KOKKOS_INLINE_FUNCTION void cutcellSmoothCell(const PV& phi, const BV& b, const OpV& AC,
53 const OpV& AW, const OpV& AE, const OpV& AS,
54 const OpV& AN, const OpV& AB, const OpV& AT, long i,
55 long xp, long xm, long yp, long ym, long zp,
56 long zm) {
57 const double ac = AC(i);
58 if (ac < 1e-30)
59 return; // fully closed (solid) cell: decoupled, phi stays 0
60 const double s = AE(i) * phi(xp) + AW(i) * phi(xm) + AN(i) * phi(yp) + AS(i) * phi(ym) +
61 AT(i) * phi(zp) + AB(i) * phi(zm);
62 phi(i) = (b(i) - s) / ac;
63}
64// (A x)_i from the bands (applyCutcellOp).
65template <class XV, class OpV>
66KOKKOS_INLINE_FUNCTION double cutcellApplyCell(const XV& x, const OpV& AC, const OpV& AW,
67 const OpV& AE, const OpV& AS, const OpV& AN,
68 const OpV& AB, const OpV& AT, long i, long xp,
69 long xm, long yp, long ym, long zp, long zm) {
70 return AC(i) * x(i) + AE(i) * x(xp) + AW(i) * x(xm) + AN(i) * x(yp) + AS(i) * x(ym) +
71 AT(i) * x(zp) + AB(i) * x(zm);
72}
73// (A x)_i in the exact flux form from the double face openness (applyCutcellOpExact).
74template <class XV, class OV>
75KOKKOS_INLINE_FUNCTION double cutcellApplyExactCell(const XV& x, const OV& ox, const OV& oy,
76 const OV& oz, long i, long sx, long sy, long sz,
77 long xp, long xm, long yp, long ym, long zp,
78 long zm, double gfx, double gfy, double gfz) {
79 const double xi = x(i);
80 return ox(i) * gfx * (xi - x(xm)) + ox(i + sx) * gfx * (xi - x(xp)) + oy(i) * gfy * (xi - x(ym)) +
81 oy(i + sy) * gfy * (xi - x(yp)) + oz(i) * gfz * (xi - x(zm)) +
82 oz(i + sz) * gfz * (xi - x(zp));
83}
84
85template <class OpV>
86inline void buildCutcellOp(OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, CCConst ox,
87 CCConst oy, CCConst oz, C3 e, int g, double gfx, double gfy,
88 double gfz) {
89 ccFor3(
90 "peclet::flow::cc_build_op", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
91 KOKKOS_LAMBDA(int lx, int ly, int lz) {
92 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
93 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
94 cutcellBuildOpCell(AC, AW, AE, AS, AN, AB, AT, ox, oy, oz, i, sx, sy, sz, gfx, gfy, gfz);
95 });
96}
97
98// --- A2: the FACE form of the pressure operator (doc/vof_step_performance_design.md §4.2, §5.3) --
99// The pressure hierarchy stores the diagonal AC and ONE band coefficient per face instead of seven
100// bands: AFX(i) is the off-diagonal coefficient of cell i's LOW x face, -open_x(i) * gfx (MReal) --
101// exactly the old AW(i) -- and the same face is cell i-1's high face, so
102// AW(i) = AFX(i) AE(i) = AFX(i+sx) AS(i) = AFY(i) AN(i) = AFY(i+sy)
103// AB(i) = AFZ(i) AT(i) = AFZ(i+sz).
104// Bit-identical to the bands: AFX holds the same MReal-rounded value AW held, and every consumer
105// reads it into the identical band expression -- the order AE, AW, AN, AS, AT, AB, each term a
106// (coefficient) * (neighbour) product. Storing the band SIGN matters on the device: the first form
107// of this change stored +open*gf and read (-TX(i+1))*phi + ..., and nvcc then contracted a
108// different product into each FMA than for the band expression -- the bubble column drifted by
109// 8.8e-15 (u, relative) in 50 steps; host builds do not contract and were bitwise either way
110// (doc/vof_step_performance_design.md §4.2 note, WO-3). AC is built by the identical expression.
111// 32 instead of 56 bytes of operator per cell (double), and three arrays per level instead of six.
112// The seven-band kernels above stay for the scalar-transport smoother and the standalone kernel
113// tests.
114//
115// AFX/AFY/AFZ are written over the FULL extent [0, ext) -- every index a band consumer ever reads,
116// including the i+sx of the last ring cell of a communication-avoiding (g=2) level -- from the
117// ghost-filled, boundary-re-imposed openness; AC over the build box [gb, ext-gb) as before.
118template <class OpV, class OV>
119KOKKOS_INLINE_FUNCTION void cutcellBuildFaceOpCell(const OpV& AC, const OpV& AFX, const OpV& AFY,
120 const OpV& AFZ, const OV& ox, const OV& oy,
121 const OV& oz, long i, long sx, long sy, long sz,
122 double gfx, double gfy, double gfz, bool diag) {
123 const double tw = ox(i) * gfx;
124 const double ts = oy(i) * gfy;
125 const double tb = oz(i) * gfz;
126 AFX(i) = -tw;
127 AFY(i) = -ts;
128 AFZ(i) = -tb;
129 if (diag) {
130 const double te = ox(i + sx) * gfx;
131 const double tn = oy(i + sy) * gfy;
132 const double tt = oz(i + sz) * gfz;
133 AC(i) = te + tw + tn + ts + tt + tb;
134 }
135}
136template <class PV, class BV, class OpV>
137KOKKOS_INLINE_FUNCTION void cutcellSmoothFaceCell(const PV& phi, const BV& b, const OpV& AC,
138 const OpV& AFX, const OpV& AFY, const OpV& AFZ,
139 long i, long sx, long sy, long sz, long xp,
140 long xm, long yp, long ym, long zp, long zm) {
141 const double ac = AC(i);
142 if (ac < 1e-30)
143 return; // fully closed (solid) cell: decoupled, phi stays 0
144 const double s = AFX(i + sx) * phi(xp) + AFX(i) * phi(xm) + AFY(i + sy) * phi(yp) +
145 AFY(i) * phi(ym) + AFZ(i + sz) * phi(zp) + AFZ(i) * phi(zm);
146 phi(i) = (b(i) - s) / ac;
147}
148template <class XV, class OpV>
149KOKKOS_INLINE_FUNCTION double cutcellApplyFaceCell(const XV& x, const OpV& AC, const OpV& AFX,
150 const OpV& AFY, const OpV& AFZ, long i, long sx,
151 long sy, long sz, long xp, long xm, long yp,
152 long ym, long zp, long zm) {
153 return AC(i) * x(i) + AFX(i + sx) * x(xp) + AFX(i) * x(xm) + AFY(i + sy) * x(yp) +
154 AFY(i) * x(ym) + AFZ(i + sz) * x(zp) + AFZ(i) * x(zm);
155}
156template <class RV, class XV, class BV, class OpV>
157KOKKOS_INLINE_FUNCTION void cutcellResidualFaceCell(const RV& r, const XV& x, const BV& b,
158 const OpV& AC, const OpV& AFX, const OpV& AFY,
159 const OpV& AFZ, long i, long sx, long sy,
160 long sz, long xp, long xm, long yp, long ym,
161 long zp, long zm) {
162 const double Ax = (double)AC(i) * x(i) + (double)AFX(i + sx) * x(xp) + (double)AFX(i) * x(xm) +
163 (double)AFY(i + sy) * x(yp) + (double)AFY(i) * x(ym) +
164 (double)AFZ(i + sz) * x(zp) + (double)AFZ(i) * x(zm);
165 r(i) = b(i) - Ax;
166}
167
168// Face-form build: AFX/AFY/AFZ over [0, ext), AC over the box [gb, ext-gb) (gb = the level's build
169// width, as buildCutcellOp's g).
170template <class OpV>
171inline void buildCutcellOpFace(OpV AC, OpV AFX, OpV AFY, OpV AFZ, CCConst ox, CCConst oy,
172 CCConst oz, C3 e, int gb, double gfx, double gfy, double gfz) {
173 ccFor3(
174 "peclet::flow::cc_build_op", C3{0, 0, 0}, C3{e.x, e.y, e.z},
175 KOKKOS_LAMBDA(int lx, int ly, int lz) {
176 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
177 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
178 const bool diag =
179 lx >= gb && lx < e.x - gb && ly >= gb && ly < e.y - gb && lz >= gb && lz < e.z - gb;
180 cutcellBuildFaceOpCell(AC, AFX, AFY, AFZ, ox, oy, oz, i, sx, sy, sz, gfx, gfy, gfz, diag);
181 });
182}
183// Face-form siblings of cutcellSmoothColor / cutcellSmoothColorBox (same launch forms).
184template <class OpV>
185inline void cutcellSmoothColorFace(CCField phi, CCConst b, OpV AC, OpV AFX, OpV AFY, OpV AFZ, C3 e,
186 C3 og, int g, int color) {
188 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
189 const int nyi = e.y - 2 * g, nzi = e.z - 2 * g;
190 const long cells = (long)nyi * nzi * (e.x - 2 * g);
191 auto pencil = KOKKOS_LAMBDA(long t) {
192 const int ly = g + (int)(t % nyi), lz = g + (int)(t / nyi);
193 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
194 const int P = (color + og.x + og.y + ly + og.z + lz) & 1;
195 PECLET_FLOW_OMP_SIMD // same-colour cells are independent (ccFor3's contract)
196 for (int lx = g + ((P ^ (g & 1)) & 1); lx < e.x - g; lx += 2) {
197 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
198 cutcellSmoothFaceCell(phi, b, AC, AFX, AFY, AFZ, i, sx, sy, sz, i + sx, i - sx, i + sy,
199 i - sy, i + sz, i - sz);
200 }
201 };
202 if (hostRunSerial(cells)) { // coarse MG level: the fork/join costs more than the sweep
203 for (long t = 0; t < (long)nyi * nzi; ++t)
204 pencil(t);
205 return;
206 }
207 Kokkos::parallel_for("peclet::flow::cc_smooth",
208 Kokkos::RangePolicy<CCExec>(space, 0, (long)nyi * nzi), pencil);
209 return;
210 }
211 using MD = MDRange3<CCExec>;
212 Kokkos::parallel_for(
213 "peclet::flow::cc_smooth", MD(space, {g, g, g}, {e.x - g, e.y - g, e.z - g}),
214 KOKKOS_LAMBDA(int lx, int ly, int lz) {
215 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
216 return;
217 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
218 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
219 cutcellSmoothFaceCell(phi, b, AC, AFX, AFY, AFZ, i, sx, sy, sz, i + sx, i - sx, i + sy,
220 i - sy, i + sz, i - sz);
221 });
222}
223template <class OpV>
224inline void cutcellSmoothColorBoxFace(CCField phi, CCConst b, OpV AC, OpV AFX, OpV AFY, OpV AFZ,
225 C3 e, C3 og, int color, C3 rlo, C3 rhi, C3 slo, C3 shi) {
226 if (rhi.x <= rlo.x || rhi.y <= rlo.y || rhi.z <= rlo.z)
227 return;
229 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
230 const int nyi = rhi.y - rlo.y, nzi = rhi.z - rlo.z;
231 const long cells = (long)nyi * nzi * (rhi.x - rlo.x);
232 auto pencil = KOKKOS_LAMBDA(long t) {
233 const int ly = rlo.y + (int)(t % nyi), lz = rlo.z + (int)(t / nyi);
234 const bool yzSkip = ly >= slo.y && ly < shi.y && lz >= slo.z && lz < shi.z;
235 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
236 const int P = (color + og.x + og.y + ly + og.z + lz) & 1;
237 for (int lx = rlo.x + ((P ^ (rlo.x & 1)) & 1); lx < rhi.x; lx += 2) {
238 if (yzSkip && lx >= slo.x && lx < shi.x)
239 continue; // inside the skip box (already swept by the interior pass)
240 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
241 cutcellSmoothFaceCell(phi, b, AC, AFX, AFY, AFZ, i, sx, sy, sz, i + sx, i - sx, i + sy,
242 i - sy, i + sz, i - sz);
243 }
244 };
245 if (hostRunSerial(cells)) { // coarse MG level: the fork/join costs more than the sweep
246 for (long t = 0; t < (long)nyi * nzi; ++t)
247 pencil(t);
248 return;
249 }
250 Kokkos::parallel_for("peclet::flow::cc_smooth_box",
251 Kokkos::RangePolicy<CCExec>(space, 0, (long)nyi * nzi), pencil);
252 return;
253 }
254 using MD = MDRange3<CCExec>;
255 Kokkos::parallel_for(
256 "peclet::flow::cc_smooth_box", MD(space, {rlo.x, rlo.y, rlo.z}, {rhi.x, rhi.y, rhi.z}),
257 KOKKOS_LAMBDA(int lx, int ly, int lz) {
258 if (lx >= slo.x && lx < shi.x && ly >= slo.y && ly < shi.y && lz >= slo.z && lz < shi.z)
259 return; // inside the skip box (already swept by the interior pass)
260 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
261 return;
262 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
263 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
264 cutcellSmoothFaceCell(phi, b, AC, AFX, AFY, AFZ, i, sx, sy, sz, i + sx, i - sx, i + sy,
265 i - sy, i + sz, i - sz);
266 });
267}
268// Face-form siblings of applyCutcellOp / applyCutcellOpBox.
269template <class OpV>
270inline void applyCutcellOpFace(CCField y, CCConst x, OpV AC, OpV AFX, OpV AFY, OpV AFZ, C3 e,
271 int g) {
272 ccFor3(
273 "peclet::flow::cc_apply", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
274 KOKKOS_LAMBDA(int lx, int ly, int lz) {
275 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
276 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
277 y(i) = cutcellApplyFaceCell(x, AC, AFX, AFY, AFZ, i, sx, sy, sz, i + sx, i - sx, i + sy,
278 i - sy, i + sz, i - sz);
279 });
280}
281template <class OpV>
282inline void applyCutcellOpBoxFace(CCField y, CCConst x, OpV AC, OpV AFX, OpV AFY, OpV AFZ, C3 e,
283 C3 rlo, C3 rhi, C3 slo, C3 shi) {
284 if (rhi.x <= rlo.x || rhi.y <= rlo.y || rhi.z <= rlo.z)
285 return;
286 ccFor3(
287 "peclet::flow::cc_apply_box", C3{rlo.x, rlo.y, rlo.z}, C3{rhi.x, rhi.y, rhi.z},
288 KOKKOS_LAMBDA(int lx, int ly, int lz) {
289 if (lx >= slo.x && lx < shi.x && ly >= slo.y && ly < shi.y && lz >= slo.z && lz < shi.z)
290 return;
291 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
292 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
293 y(i) = cutcellApplyFaceCell(x, AC, AFX, AFY, AFZ, i, sx, sy, sz, i + sx, i - sx, i + sy,
294 i - sy, i + sz, i - sz);
295 });
296}
297
298// Open-weighted flux divergence d_i = sum_f signed(o_f * face-velocity), consistent with A
299// (diverg_open_k).
300inline void divergOpen(CCConst u, CCConst v, CCConst w, CCConst ox, CCConst oy, CCConst oz,
301 CCField d, C3 e, int g) {
302 ccFor3(
303 "peclet::flow::diverg_open", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
304 KOKKOS_LAMBDA(int x, int y, int z) {
305 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
306 const long i = (long)x + (long)y * sy + (long)z * sz;
307 d(i) = (ox(i + sx) * u(i + sx) - ox(i) * u(i)) + (oy(i + sy) * v(i + sy) - oy(i) * v(i)) +
308 (oz(i + sz) * w(i + sz) - oz(i) * w(i));
309 });
310}
311
312// One red/black sweep of the variable operator: phi=(b - offdiag)/AC; AC~0 (fully solid) cells
313// decoupled. b carries the negated divergence so the system is A phi = -div(u*) (matches the
314// validated const-coeff sign).
315template <class OpV>
316inline void cutcellSmoothColor(CCField phi, CCConst b, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN,
317 OpV AB, OpV AT, C3 e, C3 og, int g, int color) {
319 // Host backends: line-sweep form (one (y,z) pencil per task, stride-2 x-loop at the colour's
320 // parity) — bit-identical (same-colour cells are independent); device keeps MDRange untouched.
321 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
322 const int nyi = e.y - 2 * g, nzi = e.z - 2 * g;
323 const long cells = (long)nyi * nzi * (e.x - 2 * g);
324 auto pencil = KOKKOS_LAMBDA(long t) {
325 const int ly = g + (int)(t % nyi), lz = g + (int)(t / nyi);
326 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
327 const int P = (color + og.x + og.y + ly + og.z + lz) & 1;
328 PECLET_FLOW_OMP_SIMD // same-colour cells are independent (ccFor3's contract)
329 for (int lx = g + ((P ^ (g & 1)) & 1); lx < e.x - g; lx += 2) {
330 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
331 cutcellSmoothCell(phi, b, AC, AW, AE, AS, AN, AB, AT, i, i + sx, i - sx, i + sy, i - sy,
332 i + sz, i - sz);
333 }
334 };
335 if (hostRunSerial(cells)) { // coarse MG level: the fork/join costs more than the sweep
336 for (long t = 0; t < (long)nyi * nzi; ++t)
337 pencil(t);
338 return;
339 }
340 Kokkos::parallel_for("peclet::flow::cc_smooth",
341 Kokkos::RangePolicy<CCExec>(space, 0, (long)nyi * nzi), pencil);
342 return;
343 }
344 using MD = MDRange3<CCExec>;
345 Kokkos::parallel_for(
346 "peclet::flow::cc_smooth", MD(space, {g, g, g}, {e.x - g, e.y - g, e.z - g}),
347 KOKKOS_LAMBDA(int lx, int ly, int lz) {
348 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
349 return;
350 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
351 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
352 cutcellSmoothCell(phi, b, AC, AW, AE, AS, AN, AB, AT, i, i + sx, i - sx, i + sy, i - sy,
353 i + sz, i - sz);
354 });
355}
356
357// Box-restricted red/black sweep for the distributed overlap smoother: sweeps [rlo,rhi) only,
358// skipping the box [slo,shi) (pass slo==shi to skip nothing). Used to split one color's sweep into
359// an INTERIOR pass (cells whose 7-point stencil reads no ghost cell — runs while the halo exchange
360// is in flight) and a boundary-SHELL pass (after the exchange lands). A color's cells never read
361// same-color cells, so interior-then-shell ordering is bit-identical to the full blocking sweep
362// (and a cell swept twice recomputes the identical value — overlapping shell slabs are safe).
363template <class OpV>
364inline void cutcellSmoothColorBox(CCField phi, CCConst b, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN,
365 OpV AB, OpV AT, C3 e, C3 og, int color, C3 rlo, C3 rhi, C3 slo,
366 C3 shi) {
367 if (rhi.x <= rlo.x || rhi.y <= rlo.y || rhi.z <= rlo.z)
368 return;
370 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
371 // Host line-sweep form of the box sweep (see cutcellSmoothColor); the (ly,lz)-level skip test
372 // hoists out of the x-loop, the x-range skip stays per cell.
373 const int nyi = rhi.y - rlo.y, nzi = rhi.z - rlo.z;
374 const long cells = (long)nyi * nzi * (rhi.x - rlo.x);
375 auto pencil = KOKKOS_LAMBDA(long t) {
376 const int ly = rlo.y + (int)(t % nyi), lz = rlo.z + (int)(t / nyi);
377 const bool yzSkip = ly >= slo.y && ly < shi.y && lz >= slo.z && lz < shi.z;
378 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
379 const int P = (color + og.x + og.y + ly + og.z + lz) & 1;
380 for (int lx = rlo.x + ((P ^ (rlo.x & 1)) & 1); lx < rhi.x; lx += 2) {
381 if (yzSkip && lx >= slo.x && lx < shi.x)
382 continue; // inside the skip box (already swept by the interior pass)
383 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
384 cutcellSmoothCell(phi, b, AC, AW, AE, AS, AN, AB, AT, i, i + sx, i - sx, i + sy, i - sy,
385 i + sz, i - sz);
386 }
387 };
388 if (hostRunSerial(cells)) { // coarse MG level: the fork/join costs more than the sweep
389 for (long t = 0; t < (long)nyi * nzi; ++t)
390 pencil(t);
391 return;
392 }
393 Kokkos::parallel_for("peclet::flow::cc_smooth_box",
394 Kokkos::RangePolicy<CCExec>(space, 0, (long)nyi * nzi), pencil);
395 return;
396 }
397 using MD = MDRange3<CCExec>;
398 Kokkos::parallel_for(
399 "peclet::flow::cc_smooth_box", MD(space, {rlo.x, rlo.y, rlo.z}, {rhi.x, rhi.y, rhi.z}),
400 KOKKOS_LAMBDA(int lx, int ly, int lz) {
401 if (lx >= slo.x && lx < shi.x && ly >= slo.y && ly < shi.y && lz >= slo.z && lz < shi.z)
402 return; // inside the skip box (already swept by the interior pass)
403 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
404 return;
405 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
406 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
407 cutcellSmoothCell(phi, b, AC, AW, AE, AS, AN, AB, AT, i, i + sx, i - sx, i + sy, i - sy,
408 i + sz, i - sz);
409 });
410}
411
412// y = A x for the cut-cell operator over inner cells (matvec for PCG; mg_apply_var_k port).
413template <class OpV>
414inline void applyCutcellOp(CCField y, CCConst x, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB,
415 OpV AT, C3 e, int g) {
416 ccFor3(
417 "peclet::flow::cc_apply", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
418 KOKKOS_LAMBDA(int lx, int ly, int lz) {
419 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
420 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
421 y(i) = cutcellApplyCell(x, AC, AW, AE, AS, AN, AB, AT, i, i + sx, i - sx, i + sy, i - sy,
422 i + sz, i - sz);
423 });
424}
425
426// Box-restricted matvec for the distributed overlap: rows in [rlo,rhi) skipping [slo,shi). Reads
427// x, writes y (no aliasing), so the interior rows can apply while x's ghost exchange is in flight
428// and the shell rows apply after it lands — trivially bit-identical to the blocking order.
429template <class OpV>
430inline void applyCutcellOpBox(CCField y, CCConst x, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB,
431 OpV AT, C3 e, C3 rlo, C3 rhi, C3 slo, C3 shi) {
432 if (rhi.x <= rlo.x || rhi.y <= rlo.y || rhi.z <= rlo.z)
433 return;
434 ccFor3(
435 "peclet::flow::cc_apply_box", C3{rlo.x, rlo.y, rlo.z}, C3{rhi.x, rhi.y, rhi.z},
436 KOKKOS_LAMBDA(int lx, int ly, int lz) {
437 if (lx >= slo.x && lx < shi.x && ly >= slo.y && ly < shi.y && lz >= slo.z && lz < shi.z)
438 return;
439 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
440 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
441 y(i) = cutcellApplyCell(x, AC, AW, AE, AS, AN, AB, AT, i, i + sx, i - sx, i + sy, i - sy,
442 i + sz, i - sz);
443 });
444}
445
446// --- Exact (matrix-free, double) level-0 apply — the defect-correction residual/matvec ---------
447//
448// P1 of the suite-wide defect-correction campaign (docs/DEFECT_CORRECTION_PLAN.md). The band form
449// above reads the STORED coefficients, which are float by default (MReal), so the operator the
450// Krylov method actually inverts is the fp32-rounded one and its singular row-sum identity
451// A*1 = 0 holds only to eps_f32 (WO-M measured the consequence: a resolution-independent residual
452// floor at ~5e-9 followed by a 1e4-1e5 rebound). These two kernels apply the SAME operator
453// matrix-free from the double face openness that buildCutcellOp assembles the bands from
454// (Level::ox/oy/oz, CCField = View<double*>, ghost-filled and boundary-re-imposed by
455// setOpenness) — so they need no new storage, and they read 24 B/cell of coefficient instead of
456// 28.
457//
458// Written in FLUX (difference) form, y_i = sum_f t_f (x_i - x_nbr) with t_f = open_f * gf:
459// * the diagonal is never formed, so it can never disagree with the faces;
460// * a constant vector is annihilated BITWISE whatever the precision of t_f, because every
461// difference vanishes identically. A stored double diagonal only gets A*1 = 0 to eps_f64.
462// * a fully-closed (solid) cell has every t_f = 0, so y_i = 0 exactly, as with the bands
463// (AC = 0 there). Where the openness is tiny-but-nonzero the two DISAGREE, and that is the
464// point: the band AC is the float-rounded sum of the six faces, this is the exact one.
465// Sign convention matches buildCutcellOp exactly: AC = sum(t_f), A<face> = -t_f, hence
466// AC*x_i + sum(-t_f*x_nbr) == sum_f t_f (x_i - x_nbr).
467//
468// The hierarchy below level 0 is untouched — it is the preconditioner and may stay float.
470 int g, double gfx, double gfy, double gfz) {
471 ccFor3(
472 "peclet::flow::cc_apply_exact", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
473 KOKKOS_LAMBDA(int lx, int ly, int lz) {
474 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
475 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
476 y(i) = cutcellApplyExactCell(x, ox, oy, oz, i, sx, sy, sz, i + sx, i - sx, i + sy, i - sy,
477 i + sz, i - sz, gfx, gfy, gfz);
478 });
479}
480
481// Box-restricted sibling of applyCutcellOpExact, for the distributed overlap matvec: rows in
482// [rlo,rhi) skipping [slo,shi). Same skip-box signature as applyCutcellOpBox, and the same
483// argument for bit-identity with the blocking form — it reads x and the openness, writes y, no
484// aliasing — so interior-then-shell ordering reproduces the blocking apply exactly.
486 C3 rlo, C3 rhi, C3 slo, C3 shi, double gfx, double gfy,
487 double gfz) {
488 if (rhi.x <= rlo.x || rhi.y <= rlo.y || rhi.z <= rlo.z)
489 return;
490 ccFor3(
491 "peclet::flow::cc_apply_exact_box", C3{rlo.x, rlo.y, rlo.z}, C3{rhi.x, rhi.y, rhi.z},
492 KOKKOS_LAMBDA(int lx, int ly, int lz) {
493 if (lx >= slo.x && lx < shi.x && ly >= slo.y && ly < shi.y && lz >= slo.z && lz < shi.z)
494 return;
495 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
496 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
497 y(i) = cutcellApplyExactCell(x, ox, oy, oz, i, sx, sy, sz, i + sx, i - sx, i + sy, i - sy,
498 i + sz, i - sz, gfx, gfy, gfz);
499 });
500}
501
502// PHASE 2 (anisotropic cells), doc/anisotropic_metric.md §1.3/§3: every correction kernel below
503// takes the per-axis pressure weight `w_a = 1/h_a'^2` and applies it OUTSIDE the whole existing
504// expression (`u -= w_x * (today's term)`) — the same weight `CutcellMG::setOpenness` puts on the
505// operator row of that axis, which is what makes the corrected open flux telescope. On the
506// isotropic path `w == 1.0` exactly and `1.0 * x == x` in IEEE-754, so these are bit-identical;
507// the defaulted arguments keep the kernel-level tests calling the historical signature.
508//
509// Projection correction u -= grad(phi) on the staggered faces (correct_k port). No openness here —
510// the openness lives in the operator + divergence; closed faces carry phi~0 on both sides so stay
511// unchanged.
512inline void projectCorrect(CCField u, CCField v, CCField w, CCConst phi, C3 e, int g,
513 double wx = 1.0, double wy = 1.0, double wz = 1.0) {
514 ccFor3(
515 "peclet::flow::correct", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
516 KOKKOS_LAMBDA(int x, int y, int z) {
517 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
518 const long i = (long)x + (long)y * sy + (long)z * sz;
519 u(i) -= wx * (phi(i) - phi(i - sx));
520 v(i) -= wy * (phi(i) - phi(i - sy));
521 w(i) -= wz * (phi(i) - phi(i - sz));
522 });
523}
524
525// Variable-density projection correction (sibling of projectCorrect): u_f -= (rho0/rho_f) grad(phi)
526// with rho_f the arithmetic face mean — the SAME face density that scaled the Poisson coefficient
527// c_f = open_f*rho0/rho_f, so the corrected open flux telescopes to A*phi exactly (discrete
528// consistency; constant rho == rho0 reduces to projectCorrect identically, ratio 1.0 exact in FP).
530 double rho0, C3 e, int g, double wx = 1.0, double wy = 1.0,
531 double wz = 1.0) {
532 ccFor3(
533 "peclet::flow::correct_var", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
534 KOKKOS_LAMBDA(int x, int y, int z) {
535 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
536 const long i = (long)x + (long)y * sy + (long)z * sz;
537 u(i) -= wx * (rho0 / (0.5 * (rho(i) + rho(i - sx))) * (phi(i) - phi(i - sx)));
538 v(i) -= wy * (rho0 / (0.5 * (rho(i) + rho(i - sy))) * (phi(i) - phi(i - sy)));
539 w(i) -= wz * (rho0 / (0.5 * (rho(i) + rho(i - sz))) * (phi(i) - phi(i - sz)));
540 });
541}
542
543// Variable-density Poisson face coefficients on the MG (g=1) block: c_f = open_f * rho0 / rho_f,
544// rho_f = arithmetic face mean. Computed over the inner cells only — CutcellMG::setOpenness runs
545// its own periodic/halo ghost fill + non-periodic boundary re-imposition on whatever level-0 fields
546// it receives, exactly as for the raw openness (the coefficient "rides the openness rails"). The
547// rho ghost ring of the g=1 block must be valid (bridged from the filled G=2 field).
548inline void buildRhoCoeff(CCField cx, CCField cy, CCField cz, CCConst ox, CCConst oy, CCConst oz,
549 CCConst rho, double rho0, C3 e, int g) {
550 ccFor3(
551 "peclet::flow::rho_coeff", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
552 KOKKOS_LAMBDA(int x, int y, int z) {
553 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
554 const long i = (long)x + (long)y * sy + (long)z * sz;
555 cx(i) = ox(i) * rho0 / (0.5 * (rho(i) + rho(i - sx)));
556 cy(i) = oy(i) * rho0 / (0.5 * (rho(i) + rho(i - sy)));
557 cz(i) = oz(i) * rho0 / (0.5 * (rho(i) + rho(i - sz)));
558 });
559}
560
561// --- Harmonic rho_f face mean (VoF rung V2a / WO-J item 5; the flagged coarsening trap of
562// MULTIPHYSICS_PLAN.md:474) -------------------------------------------------------------------
563//
564// SIBLINGS of buildRhoCoeff / projectCorrectVar with rho_f = 2*rho_a*rho_b/(rho_a+rho_b) instead of
565// the arithmetic mean. Default OFF (set_rho_face_harmonic); the validated arithmetic kernels above
566// are untouched and stay the default.
567//
568// Read this before turning it on. The two means are NOT interchangeable, and the arithmetic one is
569// the correct fine-level choice for THIS discretization, for two independent reasons:
570// 1. The face coefficient is c_f = open_f * rho0/rho_f, i.e. proportional to the mobility
571// beta = 1/rho. The ARITHMETIC mean of rho is exactly the HARMONIC mean of beta — the
572// series-resistance / homogenization-correct choice for a flux crossing two half-cells
573// (VOF_PLAN.md §5). Taking rho harmonic makes beta arithmetic, i.e. the parallel rule, which
574// is the wrong one for a normal flux.
575// 2. Discrete hydrostatic balance is EXACT only because the momentum time term, the
576// face-interpolated body force, and the projection coefficient use the SAME rho_f
577// (doc/variable_density_projection.md §1/§3). The momentum side interpolates rho
578// arithmetically (mass is volume-additive on the staggered control volume) and is not part of
579// this switch, so switching only the projection breaks that three-way consistency and leaves
580// a permanent spurious velocity at the interface. This is measured, not asserted — see the
581// WO-J findings entry.
582// The knob exists because arithmetic COARSENING of the coefficient is what makes the V-cycle
583// preconditioner indefinite past ratio ~1e3 (doc/variable_density_projection.md §2), so the
584// question "does a harmonic face mean help the solver" needs to be answerable by measurement
585// rather than by argument. It ships measured.
586//
587// The projection stays an EXACT projection either way: the operator coefficient and the velocity
588// correction must use the same rho_f or the corrected open flux no longer telescopes to A*phi, so
589// the flag switches BOTH kernels together (IbmSolver::project / projectVelocities).
591 CCConst oz, CCConst rho, double rho0, C3 e, int g) {
592 ccFor3(
593 "peclet::flow::rho_coeff_harm", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
594 KOKKOS_LAMBDA(int x, int y, int z) {
595 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
596 const long i = (long)x + (long)y * sy + (long)z * sz;
597 // 2 a b/(a+b) written as rho0*(a+b)/(2ab) for the coefficient rho0/rho_f.
598 cx(i) = ox(i) * rho0 * (rho(i) + rho(i - sx)) / (2.0 * rho(i) * rho(i - sx));
599 cy(i) = oy(i) * rho0 * (rho(i) + rho(i - sy)) / (2.0 * rho(i) * rho(i - sy));
600 cz(i) = oz(i) * rho0 * (rho(i) + rho(i - sz)) / (2.0 * rho(i) * rho(i - sz));
601 });
602}
603
604// WO-R2 item 1 — the variable-density coefficient of the HIGH-side outflow domain face.
605//
606// `buildRhoCoeff` runs over the INNER cells, and the staggered face index of a cell is its LOW
607// face, so the low domain face of an axis (inner index `g`) is covered and the high one
608// (`dims-g`, a ghost index) is not. `CutcellMG::applyBoundaryOpenness` used to fill that gap with
609// the literal openness 1.0, which is right for the raw-openness operator and wrong by the density
610// ratio for the coefficient one. This writes the missing plane, `open_f * rho0/rho_f` with:
611// * open_f = 1 — the same fully-open value the raw path imposed there (the g=1 coefficient
612// block's ghost ring carries no geometric openness of its own; a solid cutting an OUTFLOW
613// plane is a separate, pre-existing gap that the raw path has too);
614// * rho_f = the arithmetic (or harmonic) face mean of the LAST INNER cell and the domain ghost,
615// literally the expression `bcCorrectOutflowVar` applies at that face — which is what makes
616// the projection there exact. Under the Neumann property ghost (`fillPropGhosts`) both
617// evaluate to the last inner cell's own rho.
618// The whole transverse plane is written (ghost ring included) so that a distributed CA level's
619// ring rows coarsen from a valid fine plane; `rho`'s ghost ring must be filled first, which is
620// what the bridge in IbmSolver::project guarantees.
621inline void buildRhoCoeffOutflowFace(CCField ca, CCConst oa, CCConst rho, double rho0, C3 e, int g,
622 int a, bool harmonic) {
624 int dims[3] = {e.x, e.y, e.z};
625 long st[3] = {1, (long)e.x, (long)e.x * e.y};
626 const int b = (a + 1) % 3, c = (a + 2) % 3;
627 const long sa = st[a], sb = st[b], sc = st[c];
628 const int bf = dims[a] - g;
629 using MD = MDRange2<CCExec>;
630 Kokkos::parallel_for(
631 "peclet::flow::rho_coeff_outflow", MD(space, {0, 0}, {dims[b], dims[c]}),
632 KOKKOS_LAMBDA(int p0, int p1) {
633 const long i = (long)p0 * sb + (long)p1 * sc + (long)bf * sa;
634 const double ra = rho(i), rb = rho(i - sa);
635 const double rf = harmonic ? (2.0 * ra * rb / (ra + rb)) : (0.5 * (ra + rb));
636 // SCALING_ISSUES #3: `oa(i)` is this face's cut-cell aperture, which every OTHER face of
637 // the coefficient carries (buildRhoCoeff multiplies by it) and this one used to drop --
638 // invisible while the outlet is clear of solid (aperture 1), an operator that disagrees
639 // with the divergence constraint by (1 - aperture) once a solid cuts the outlet.
640 ca(i) = oa(i) * rho0 / rf;
641 });
642}
643
645 double rho0, C3 e, int g, double wx = 1.0, double wy = 1.0,
646 double wz = 1.0) {
647 ccFor3(
648 "peclet::flow::correct_var_harm", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
649 KOKKOS_LAMBDA(int x, int y, int z) {
650 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
651 const long i = (long)x + (long)y * sy + (long)z * sz;
652 u(i) -= wx * (rho0 * (rho(i) + rho(i - sx)) / (2.0 * rho(i) * rho(i - sx)) *
653 (phi(i) - phi(i - sx)));
654 v(i) -= wy * (rho0 * (rho(i) + rho(i - sy)) / (2.0 * rho(i) * rho(i - sy)) *
655 (phi(i) - phi(i - sy)));
656 w(i) -= wz * (rho0 * (rho(i) + rho(i - sz)) / (2.0 * rho(i) * rho(i - sz)) *
657 (phi(i) - phi(i - sz)));
658 });
659}
660
661// --- Volume-averaged (porous) continuity for unresolved CFD-DEM
662// ----------------------------------- The proper continuity is d(eps)/dt + div(eps u) = 0 (eps =
663// void fraction from the particles), so the velocity is NOT solenoidal: div(eps u) = -d(eps)/dt.
664// These size the projection to that constraint.
665
666// eps-weighted open-face divergence: d = div(open * eps_f * u), eps_f = arithmetic face mean.
667// Reduces to divergOpen when eps == 1 everywhere (no particles).
669 CCConst eps, CCField d, C3 e, int g) {
670 ccFor3(
671 "peclet::flow::diverg_open_eps", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
672 KOKKOS_LAMBDA(int x, int y, int z) {
673 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
674 const long i = (long)x + (long)y * sy + (long)z * sz;
675 const double exp = 0.5 * (eps(i) + eps(i + sx)), exm = 0.5 * (eps(i) + eps(i - sx));
676 const double eyp = 0.5 * (eps(i) + eps(i + sy)), eym = 0.5 * (eps(i) + eps(i - sy));
677 const double ezp = 0.5 * (eps(i) + eps(i + sz)), ezm = 0.5 * (eps(i) + eps(i - sz));
678 d(i) = (ox(i + sx) * exp * u(i + sx) - ox(i) * exm * u(i)) +
679 (oy(i + sy) * eyp * v(i + sy) - oy(i) * eym * v(i)) +
680 (oz(i + sz) * ezp * w(i + sz) - oz(i) * ezm * w(i));
681 });
682}
683
684// Porous Poisson face coefficient c_f = open_f * eps_f (eps_f = arithmetic face mean).
685// Constant-density gas: the correction stays u -= grad(phi) (projectCorrect) so the open*eps flux
686// telescopes to A*phi. (Combining with variable rho — c_f *= rho0/rho_f, projectCorrectVar — is a
687// later composition.)
688// SCALING_ISSUES #3: the three porous builders run ONE index past the inner block along each axis,
689// so the HIGH domain face of each axis -- a ghost index no other kernel writes, and the face the
690// MG's `applyBoundaryOpenness` carries at a Dirichlet outlet -- is built by the same formula as
691// every other face, cut-cell aperture `o(i)` included. The extra plane is read-only elsewhere (the
692// per-level ghost fill overwrites it) and every operand it reads is a ghost-filled index.
693inline void buildPorousCoeff(CCField cx, CCField cy, CCField cz, CCConst ox, CCConst oy, CCConst oz,
694 CCConst eps, C3 e, int g) {
695 ccFor3(
696 "peclet::flow::porous_coeff", C3{g, g, g}, C3{e.x - g + 1, e.y - g + 1, e.z - g + 1},
697 KOKKOS_LAMBDA(int x, int y, int z) {
698 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
699 const long i = (long)x + (long)y * sy + (long)z * sz;
700 cx(i) = ox(i) * 0.5 * (eps(i) + eps(i - sx));
701 cy(i) = oy(i) * 0.5 * (eps(i) + eps(i - sy));
702 cz(i) = oz(i) * 0.5 * (eps(i) + eps(i - sz));
703 });
704}
705
706// Semi-implicit-drag porous coefficient: c_f = open_f * eps_f * w_f, with the face drag-relaxation
707// w_f = idt/(idt + beta_f) (idt = rho/dt, beta = the momentum-diagonal drag coefficient). This
708// makes the pressure correction CONSISTENT with the drag-loaded momentum diagonal A_P = idt + beta:
709// where the drag is stiff (dense bed) w_f -> 0 and the pressure barely moves the velocity (the drag
710// holds it) — the SIMPLE/PISO-with-implicit-drag scheme (OpenFOAM rAU, MFIX). Reduces to
711// buildPorousCoeff when beta==0 (w==1). The correction MUST use the same w_f
712// (projectCorrectPorousDrag) so the open*eps*w flux telescopes to A*phi.
714 CCConst oz, CCConst eps, CCConst beta, double idt, C3 e, int g) {
715 ccFor3(
716 "peclet::flow::porous_coeff_drag", C3{g, g, g}, C3{e.x - g + 1, e.y - g + 1, e.z - g + 1},
717 KOKKOS_LAMBDA(int x, int y, int z) {
718 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
719 const long i = (long)x + (long)y * sy + (long)z * sz;
720 cx(i) = ox(i) * 0.5 * (eps(i) + eps(i - sx)) * idt / (idt + 0.5 * (beta(i) + beta(i - sx)));
721 cy(i) = oy(i) * 0.5 * (eps(i) + eps(i - sy)) * idt / (idt + 0.5 * (beta(i) + beta(i - sy)));
722 cz(i) = oz(i) * 0.5 * (eps(i) + eps(i - sz)) * idt / (idt + 0.5 * (beta(i) + beta(i - sz)));
723 });
724}
725
726// --- eps-CONSERVATIVE porous projection pair -------------------------------------------------
727// For the conservative volume-averaged momentum (time term (eps_f rho/dt) u, Model-B full -grad p)
728// the face momentum diagonal is D_f = eps_f*rho*idt + beta_f, the velocity correction is
729// u_f -= (rho*idt / D_f) * grad(phi) (beta=0, eps=1 -> plain projectCorrect exactly)
730// and the flux constraint div(eps u) = rhs makes the Poisson coefficient
731// c_f = open_f * eps_f * (rho*idt/D_f) = open_f * (eps_f*rho*idt) / D_f
732// — the eps of the flux cancels against the eps of the inertia, unlike the plain-u pair above
733// (buildPorousCoeff*/projectCorrectPorousDrag), which is consistent only for the non-conservative
734// momentum form and kinematically drags gas along with the moving porosity (energy injection).
736 CCConst oz, CCConst eps, CCConst beta, bool useBeta, double rhoidt,
737 C3 e, int g) {
738 ccFor3(
739 "peclet::flow::porous_coeff_cons", C3{g, g, g}, C3{e.x - g + 1, e.y - g + 1, e.z - g + 1},
740 KOKKOS_LAMBDA(int x, int y, int z) {
741 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
742 const long i = (long)x + (long)y * sy + (long)z * sz;
743 auto cf = [&](long s, CCConst o) {
744 const double epsF = 0.5 * (eps(i) + eps(i - s));
745 const double bF = useBeta ? 0.5 * (beta(i) + beta(i - s)) : 0.0;
746 const double inert = epsF * rhoidt;
747 return o(i) * inert / (inert + bF) * 1.0;
748 };
749 cx(i) = cf(sx, ox);
750 cy(i) = cf(sy, oy);
751 cz(i) = cf(sz, oz);
752 });
753}
755 CCConst beta, bool useBeta, double rhoidt, C3 e, int g,
756 double wx = 1.0, double wy = 1.0, double wz = 1.0) {
757 ccFor3(
758 "peclet::flow::correct_porous_cons", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
759 KOKKOS_LAMBDA(int x, int y, int z) {
760 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
761 const long i = (long)x + (long)y * sy + (long)z * sz;
762 auto wf = [&](long s) {
763 const double epsF = 0.5 * (eps(i) + eps(i - s));
764 const double bF = useBeta ? 0.5 * (beta(i) + beta(i - s)) : 0.0;
765 return rhoidt / (epsF * rhoidt + bF);
766 };
767 u(i) -= wx * (wf(sx) * (phi(i) - phi(i - sx)));
768 v(i) -= wy * (wf(sy) * (phi(i) - phi(i - sy)));
769 w(i) -= wz * (wf(sz) * (phi(i) - phi(i - sz)));
770 });
771}
772
773// Drag-relaxed velocity correction (sibling of projectCorrect): u_f -= w_f * grad(phi), w_f the
774// SAME face drag relaxation as buildPorousCoeffDrag. beta==0 -> w==1 -> projectCorrect exactly.
776 double idt, C3 e, int g, double wx = 1.0, double wy = 1.0,
777 double wz = 1.0) {
778 ccFor3(
779 "peclet::flow::correct_porous_drag", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
780 KOKKOS_LAMBDA(int x, int y, int z) {
781 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
782 const long i = (long)x + (long)y * sy + (long)z * sz;
783 u(i) -= wx * (idt / (idt + 0.5 * (beta(i) + beta(i - sx))) * (phi(i) - phi(i - sx)));
784 v(i) -= wy * (idt / (idt + 0.5 * (beta(i) + beta(i - sy))) * (phi(i) - phi(i - sy)));
785 w(i) -= wz * (idt / (idt + 0.5 * (beta(i) + beta(i - sz))) * (phi(i) - phi(i - sz)));
786 });
787}
788
789} // namespace peclet::flow
790
791#endif // PECLET_FLOW_MAC_PRESSURE_HPP
flow — portable (Kokkos) cut-cell pressure-operator face openness from an SDF.
#define PECLET_FLOW_OMP_SIMD
double cutcellApplyExactCell(const XV &x, const OV &ox, const OV &oy, const OV &oz, long i, long sx, long sy, long sz, long xp, long xm, long yp, long ym, long zp, long zm, double gfx, double gfy, double gfz)
void buildRhoCoeffOutflowFace(CCField ca, CCConst oa, CCConst rho, double rho0, C3 e, int g, int a, bool harmonic)
void buildPorousCoeffDrag(CCField cx, CCField cy, CCField cz, CCConst ox, CCConst oy, CCConst oz, CCConst eps, CCConst beta, double idt, C3 e, int g)
void applyCutcellOpFace(CCField y, CCConst x, OpV AC, OpV AFX, OpV AFY, OpV AFZ, C3 e, int g)
void cutcellBuildFaceOpCell(const OpV &AC, const OpV &AFX, const OpV &AFY, const OpV &AFZ, const OV &ox, const OV &oy, const OV &oz, long i, long sx, long sy, long sz, double gfx, double gfy, double gfz, bool diag)
void projectCorrectPorousDrag(CCField u, CCField v, CCField w, CCConst phi, CCConst beta, double idt, C3 e, int g, double wx=1.0, double wy=1.0, double wz=1.0)
void buildRhoCoeff(CCField cx, CCField cy, CCField cz, CCConst ox, CCConst oy, CCConst oz, CCConst rho, double rho0, C3 e, int g)
void cutcellResidualFaceCell(const RV &r, const XV &x, const BV &b, const OpV &AC, const OpV &AFX, const OpV &AFY, const OpV &AFZ, long i, long sx, long sy, long sz, long xp, long xm, long yp, long ym, long zp, long zm)
void buildPorousCoeffCons(CCField cx, CCField cy, CCField cz, CCConst ox, CCConst oy, CCConst oz, CCConst eps, CCConst beta, bool useBeta, double rhoidt, C3 e, int g)
void cutcellSmoothFaceCell(const PV &phi, const BV &b, const OpV &AC, const OpV &AFX, const OpV &AFY, const OpV &AFZ, long i, long sx, long sy, long sz, long xp, long xm, long yp, long ym, long zp, long zm)
void divergOpen(CCConst u, CCConst v, CCConst w, CCConst ox, CCConst oy, CCConst oz, CCField d, C3 e, int g)
void projectCorrect(CCField u, CCField v, CCField w, CCConst phi, C3 e, int g, double wx=1.0, double wy=1.0, double wz=1.0)
void cutcellSmoothColorBox(CCField phi, CCConst b, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, C3 e, C3 og, int color, C3 rlo, C3 rhi, C3 slo, C3 shi)
void applyCutcellOpExact(CCField y, CCConst x, CCConst ox, CCConst oy, CCConst oz, C3 e, int g, double gfx, double gfy, double gfz)
void applyCutcellOp(CCField y, CCConst x, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, C3 e, int g)
void projectCorrectPorousCons(CCField u, CCField v, CCField w, CCConst phi, CCConst eps, CCConst beta, bool useBeta, double rhoidt, C3 e, int g, double wx=1.0, double wy=1.0, double wz=1.0)
void cutcellSmoothColorBoxFace(CCField phi, CCConst b, OpV AC, OpV AFX, OpV AFY, OpV AFZ, C3 e, C3 og, int color, C3 rlo, C3 rhi, C3 slo, C3 shi)
void buildCutcellOpFace(OpV AC, OpV AFX, OpV AFY, OpV AFZ, CCConst ox, CCConst oy, CCConst oz, C3 e, int gb, double gfx, double gfy, double gfz)
void cutcellBuildOpCell(const OpV &AC, const OpV &AW, const OpV &AE, const OpV &AS, const OpV &AN, const OpV &AB, const OpV &AT, const OV &ox, const OV &oy, const OV &oz, long i, long sx, long sy, long sz, double gfx, double gfy, double gfz)
void cutcellSmoothCell(const PV &phi, const BV &b, const OpV &AC, const OpV &AW, const OpV &AE, const OpV &AS, const OpV &AN, const OpV &AB, const OpV &AT, long i, long xp, long xm, long yp, long ym, long zp, long zm)
double cutcellApplyCell(const XV &x, const OpV &AC, const OpV &AW, const OpV &AE, const OpV &AS, const OpV &AN, const OpV &AB, const OpV &AT, long i, long xp, long xm, long yp, long ym, long zp, long zm)
void cutcellSmoothColorFace(CCField phi, CCConst b, OpV AC, OpV AFX, OpV AFY, OpV AFZ, C3 e, C3 og, int g, int color)
void applyCutcellOpBoxFace(CCField y, CCConst x, OpV AC, OpV AFX, OpV AFY, OpV AFZ, C3 e, C3 rlo, C3 rhi, C3 slo, C3 shi)
void projectCorrectVar(CCField u, CCField v, CCField w, CCConst phi, CCConst rho, double rho0, C3 e, int g, double wx=1.0, double wy=1.0, double wz=1.0)
void ccFor3(const char *name, C3 lo, C3 hi, F f)
void buildCutcellOp(OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, CCConst ox, CCConst oy, CCConst oz, C3 e, int g, double gfx, double gfy, double gfz)
Kokkos::View< double *, CCMem > CCField
void cutcellSmoothColor(CCField phi, CCConst b, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, C3 e, C3 og, int g, int color)
void buildPorousCoeff(CCField cx, CCField cy, CCField cz, CCConst ox, CCConst oy, CCConst oz, CCConst eps, C3 e, int g)
Kokkos::DefaultExecutionSpace CCExec
void buildRhoCoeffHarm(CCField cx, CCField cy, CCField cz, CCConst ox, CCConst oy, CCConst oz, CCConst rho, double rho0, C3 e, int g)
void divergOpenEps(CCConst u, CCConst v, CCConst w, CCConst ox, CCConst oy, CCConst oz, CCConst eps, CCField d, C3 e, int g)
void projectCorrectVarHarm(CCField u, CCField v, CCField w, CCConst phi, CCConst rho, double rho0, C3 e, int g, double wx=1.0, double wy=1.0, double wz=1.0)
void applyCutcellOpExactBox(CCField y, CCConst x, CCConst ox, CCConst oy, CCConst oz, C3 e, C3 rlo, C3 rhi, C3 slo, C3 shi, double gfx, double gfy, double gfz)
bool hostRunSerial(long cells)
double cutcellApplyFaceCell(const XV &x, const OpV &AC, const OpV &AFX, const OpV &AFY, const OpV &AFZ, long i, long sx, long sy, long sz, long xp, long xm, long yp, long ym, long zp, long zm)
void applyCutcellOpBox(CCField y, CCConst x, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, C3 e, C3 rlo, C3 rhi, C3 slo, C3 shi)
Kokkos::View< const double *, CCMem > CCConst
flow — the multidimensional iteration policies: iteration order follows storage.
static constexpr double AC
Kokkos::View< double *, peclet::flow::CCMem > OpV