flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
scalar_mg.hpp
Go to the documentation of this file.
1// ScalarMG: the multigrid preconditioner of the cut-cell scalar path (doc/scalar_ibm_design.md
2// §5.2, D10; WO-4 single phase). One instance per cut-cell scalar; it preconditions the SPD
3// lumped-probe SURROGATE S of §4.3 — the BiCGStab of `scalar_krylov.hpp` runs on the true probe
4// operator, and one V-cycle of this class is its z = M^-1 r.
5//
6// Level table (§5.2): VelocityMG::init / initMpi's rule verbatim — in-place coarsening per axis
7// while the global dimension is even and >= 4 and (distributed) every rank's block origin and size
8// are even on that axis, then the shared aspect rule `CutcellMG::mgChooseRatio` (theta = 2, read
9// from setMetric's h'). No depth cap (the "full table"), no telescoping (§13 Q3). Level 0 is the
10// scalar's own G = 2 block (its ghost exchange is the solver's, injected as `Fill`); coarse levels
11// carry g = 1 and their own `GridHalo<double>`.
12//
13// Per level the surrogate is stored in FACE form, double: AC (diagonal) and AFX/AFY/AFZ, AFX(i) the
14// off-diagonal coefficient of cell i's LOW x face (mac_pressure.hpp's face form). Level 0 (§4.3):
15// AC = the WO-3 surrogate diagonal SAC (bands' diagonal + the lumped wall term), AF = -Lam w_a a,
16// guarded toward a cell that is not an unknown exactly as `sco::buildBands` (so the level-0 sweeps
17// are bitwise the WO-3 band sweeps). Coarse level L, coarse cell C (REDISCRETIZED, §5.2):
18// * face: t_a = w_a(L) <Lam a>, w_a(L) = w_a / cfac_a^2, <Lam a> the plain average of the fine
19// sub-faces' products, coarsened recursively by `coarsenOpenAvg`'s cell body;
20// * mass: m_C = restrictAvg of the children's m (kappa idt, + the lumped implicit outflow
21// under advection, WO-5);
22// * wall: W_C = (1/N_L) sum over the level-0 facets under C of alpha G(s_phi) — the plain
23// average
24// of the level-0 wall terms cw, at the FINE probe distance (design Amendment A1; the
25// level's own s_L overshoots the coarse correction by ~2^L and diverged, log WO-4).
26// Robin and Dirichlet alike (Neumann: 0); in WO-7 the conjugate coupling the same way.
27// A direct gather, one thread per coarse cell over its level-0 descendants in fixed
28// order;
29// * pins: C is pinned iff every child is (the unknown flags restricted with max); pinned rows
30// are
31// identity rows with zero correction;
32// * Dirichlet domain faces: 2 w_a(L) <Lam a_bf>, the boundary plane averaged level by level.
33// Ghosts beyond a non-periodic global face are pinned on every level (the record clears the level-0
34// ghost unknown flags; a coarse ghost's children are those ghosts), so a coarse correction is 0
35// there before the trilinear prolongation reads it, and every face coefficient toward them is 0.
36//
37// Smoother: red-black Gauss-Seidel, colour = (gx + gy + gz) mod 2 from GLOBAL indices, an exchange
38// before each colour — on a single rank on a device backend (WO-9b; flow's A3) the colour passes
39// and the residual of a level with even inner dimensions read the periodic neighbour through the
40// wrapped index instead (`wrapReads`); a coarse level's fill is ONE launch (`smg::fillShell`), no
41// fence. V-cycle: pre 2 sweeps R -> B, residual (after a fresh exchange), restrictAvg, (singular:
42// the coarse rhs mean removed), recurse, coarse ghosts filled, prolongAdd (trilinear), pinned cells
43// re-zeroed, post 2 sweeps B -> R. Bottom: 16 sweeps (8 R -> B then 8 B -> R, so the cycle stays
44// symmetric) plus the mean removal when singular. The level rule (§5.2): transient with kappa_A = 1
45// + 4 dt' D' sum_a w_a < kFullTableKappa = 25 uses level 0 alone (2 + 2 sweeps, the WO-3
46// preconditioner); otherwise, and always when steady, the full table. The cycle is a fixed linear
47// operator.
48//
49// Steady mode WITH advection (design Amendment A2, §6.7; `Inputs::advective`, the same on every
50// rank): the cycle runs on the ADVECTIVE surrogate S_adv, in band form (AC, AW..AT). Level 0 is
51// the operator's 7 bands with SAC as the diagonal (no new storage). A coarse level keeps everything
52// above (faces, wall, Dirichlet planes, pins; its mass carries the open-face outflow omega_open,
53// not the interior outflow) and adds the summed positive parts of the sub-face fluxes, Qp/Qm per
54// low face per unit level volume (the piecewise-constant Galerkin FOU): AC += the cell's outgoing
55// Q, AW = AFX - Qp_x, AE = AFX(i+e_x) - Qm_x(i+e_x), ... The sweep (unknown cells of the colour),
56// the residual, applySurrogate and the contraction instrument use the bands; the cycle structure,
57// transfers, colours and exchanges are unchanged. Transient mode and steady mode at rest take the
58// face-form path above, bitwise unchanged.
59#ifndef PECLET_FLOW_SCALAR_MG_HPP
60#define PECLET_FLOW_SCALAR_MG_HPP
61
62#include <algorithm>
63#include <cmath>
64#include <cstdint>
65#include <functional>
66#include <Kokkos_Core.hpp>
67#include <limits>
68#include <memory>
69#include <string>
70#include <type_traits>
71#include <utility>
72#include <vector>
73
74#include "mac_cutcell_mg.hpp" // coarsenOpenAvgCell, restrictAvg, prolongAdd, CutcellMG::mgChooseRatio
75#include "mac_pressure.hpp" // cutcellSmoothFaceCell, cutcellResidualFaceCell, cutcellApplyFaceCell
76#include "policy.hpp"
77#include "scalar_cutcell_geometry.hpp" // ScalarFacetOverlay
78
79namespace peclet::flow {
80
81namespace smg {
82
88 double lam = 0.0;
89 long st = 1;
90 KOKKOS_INLINE_FUNCTION double operator()(long i) const {
91 return (unk(i) > 0.5 && unk(i - st) > 0.5) ? lam * ap(i) : 0.0;
92 }
93};
94
99struct MassField {
101 double idt = 0.0;
102 bool hasOut = false;
103 KOKKOS_INLINE_FUNCTION double operator()(long i) const {
104 if (!(unk(i) > 0.5))
105 return 0.0;
106 return hasOut ? kappa(i) * idt + out(i) : kappa(i) * idt;
107 }
108};
109
111template <class FV>
112inline void restrictMax(CCField coarse, const FV& fine, C3 cext, C3 fext, int gc, int gf, C3 cinner,
113 C3 ratio) {
114 ccFor3(
115 "peclet::flow::smg_restrict_max", C3{0, 0, 0}, cinner,
116 KOKKOS_LAMBDA(int icx, int icy, int icz) {
117 const long fsy = fext.x, fsz = (long)fext.x * fext.y;
118 double m = 0.0;
119 for (int dz = 0; dz < ratio.z; ++dz)
120 for (int dy = 0; dy < ratio.y; ++dy)
121 for (int dx = 0; dx < ratio.x; ++dx) {
122 const long fi = (long)(ratio.x * icx + dx + gf) +
123 (long)(ratio.y * icy + dy + gf) * fsy +
124 (long)(ratio.z * icz + dz + gf) * fsz;
125 m = fine(fi) > m ? fine(fi) : m;
126 }
127 coarse((long)(icx + gc) + (long)(icy + gc) * cext.x +
128 (long)(icz + gc) * (long)cext.x * cext.y) = m > 0.5 ? 1.0 : 0.0;
129 });
130}
131
133inline void zeroPinned(CCField x, CCConst unk, C3 e, int g) {
134 ccFor3(
135 "peclet::flow::smg_zero_pinned", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
136 KOKKOS_LAMBDA(int lx, int ly, int lz) {
137 const long i = (long)lx + (long)ly * e.x + (long)lz * (long)e.x * e.y;
138 if (!(unk(i) > 0.5))
139 x(i) = 0.0;
140 });
141}
142
145inline void zeroPlanes(CCField f, C3 e, int a, int lo, int hi) {
146 if (hi <= lo)
147 return;
148 const int ext[3] = {e.x, e.y, e.z};
149 const int b = (a + 1) % 3, c = (a + 2) % 3;
150 const long st[3] = {1, (long)e.x, (long)e.x * e.y};
151 const long sa = st[a], sb = st[b], sc = st[c];
152 const int nb = ext[b], nc = ext[c];
154 auto row = KOKKOS_LAMBDA(long t) { // one transverse row (pb, pc) through the planes
155 const int pb = (int)(t % nb), pc = (int)(t / nb);
156 for (int k = lo; k < hi; ++k)
157 f((long)k * sa + (long)pb * sb + (long)pc * sc) = 0.0;
158 };
159 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
160 if (hostRunSerial((long)nb * nc * (hi - lo))) {
161 for (long t = 0; t < (long)nb * nc; ++t)
162 row(t);
163 return;
164 }
165 }
166 Kokkos::parallel_for("peclet::flow::smg_zero_planes",
167 Kokkos::RangePolicy<CCExec>(space, 0, (long)nb * nc), row);
168}
169
179inline void fillShell(CCField f, C3 e, C3 inner, int g, const bool per[3]) {
181 const int nx = inner.x, ny = inner.y, nz = inner.z;
182 const long exy = (long)e.x * e.y;
183 const bool px = per[0], py = per[1], pz = per[2];
184 CCField ff = f;
185 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
186 // host launch rule: rows (one index computation per row, a contiguous x loop inside)
187 const long rZ = 2L * g * e.y, rY = (long)nz * 2 * g, rX = (long)nz * ny;
188 auto row = [=](long t) {
189 long dst, src;
190 bool zero;
191 if (t < rZ) { // z-ghost plane row (y, zg), all x
192 const int l = (int)(t / e.y), y = (int)(t - (long)l * e.y);
193 const int z = l < g ? l : nz + l;
194 const bool yg = y < g || y >= g + ny;
195 const int sy = y < g ? y + ny : (y >= g + ny ? y - ny : y);
196 const int sz = z < g ? z + nz : z - nz;
197 zero = !pz || (yg && !py);
198 dst = (long)y * e.x + (long)z * exy;
199 src = (long)sy * e.x + (long)sz * exy;
200 } else if (t < rZ + rY) { // y-ghost row (yg, z) of the inner z range, all x
201 const long t2 = t - rZ;
202 const int zi = (int)(t2 / (2 * g)), l = (int)(t2 - (long)zi * 2 * g);
203 const int y = l < g ? l : ny + l, z = g + zi;
204 const int sy = y < g ? y + ny : y - ny;
205 zero = !py;
206 dst = (long)y * e.x + (long)z * exy;
207 src = (long)sy * e.x + (long)z * exy;
208 } else { // the x-ghost cells of the inner row (y, z)
209 const long t3 = t - rZ - rY;
210 const int zi = (int)(t3 / ny), yi = (int)(t3 - (long)zi * ny);
211 const long base = (long)(g + yi) * e.x + (long)(g + zi) * exy;
212 for (int x = 0; x < g; ++x)
213 ff(base + x) = px ? ff(base + x + nx) : 0.0;
214 for (int x = g + nx; x < e.x; ++x)
215 ff(base + x) = px ? ff(base + x - nx) : 0.0;
216 return;
217 }
218 if (zero) {
219 for (int x = 0; x < e.x; ++x)
220 ff(dst + x) = 0.0;
221 return;
222 }
223 for (int x = 0; x < g; ++x)
224 ff(dst + x) = px ? ff(src + x + nx) : 0.0;
225 for (int x = g; x < g + nx; ++x)
226 ff(dst + x) = ff(src + x);
227 for (int x = g + nx; x < e.x; ++x)
228 ff(dst + x) = px ? ff(src + x - nx) : 0.0;
229 };
230 const long cells = 2L * g * exy + (long)nz * 2 * g * e.x + (long)nz * ny * 2 * g;
231 if (hostRunSerial(cells)) {
232 for (long t = 0; t < rZ + rY + rX; ++t)
233 row(t);
234 return;
235 }
236 Kokkos::parallel_for("peclet::flow::smg_fill_shell",
237 Kokkos::RangePolicy<CCExec>(space, 0, rZ + rY + rX), row);
238 return;
239 }
240 const long nZ = 2L * g * exy, nY = (long)nz * 2 * g * e.x, nX = (long)nz * ny * 2 * g;
241 Kokkos::parallel_for(
242 "peclet::flow::smg_fill_shell", Kokkos::RangePolicy<CCExec>(space, 0, nZ + nY + nX),
243 KOKKOS_LAMBDA(long t) {
244 int x, y, z;
245 if (t < nZ) {
246 const int l = (int)(t / exy);
247 const long r = t - (long)l * exy;
248 x = (int)(r % e.x);
249 y = (int)(r / e.x);
250 z = l < g ? l : nz + l;
251 } else if (t < nZ + nY) {
252 const long t2 = t - nZ, rw = 2L * g * e.x;
253 const long zi = t2 / rw, r = t2 - zi * rw;
254 const int l = (int)(r / e.x);
255 x = (int)(r % e.x);
256 y = l < g ? l : ny + l;
257 z = g + (int)zi;
258 } else {
259 const long t3 = t - nZ - nY, rw = (long)ny * 2 * g;
260 const long zi = t3 / rw, r = t3 - zi * rw;
261 const int l = (int)(r % (2 * g));
262 x = l < g ? l : nx + l;
263 y = g + (int)(r / (2 * g));
264 z = g + (int)zi;
265 }
266 const bool gx = x < g || x >= g + nx, gy = y < g || y >= g + ny, gz = z < g || z >= g + nz;
267 const long d = (long)x + (long)y * e.x + (long)z * exy;
268 if ((gx && !px) || (gy && !py) || (gz && !pz)) {
269 ff(d) = 0.0;
270 return;
271 }
272 const int sx = x < g ? x + nx : (x >= g + nx ? x - nx : x);
273 const int sy = y < g ? y + ny : (y >= g + ny ? y - ny : y);
274 const int sz = z < g ? z + nz : (z >= g + nz ? z - nz : z);
275 ff(d) = ff((long)sx + (long)sy * e.x + (long)sz * exy);
276 });
277}
278
283struct Nb {
284 long xp, xm, yp, ym, zp, zm;
285};
286KOKKOS_INLINE_FUNCTION Nb nbrs(int lx, int ly, int lz, C3 n, int g, long i, long sy, long sz,
287 int wrap) {
288 Nb w;
289 w.xm = ((wrap & 1) && lx == g) ? i + (long)(n.x - 1) : i - 1;
290 w.xp = ((wrap & 1) && lx == g + n.x - 1) ? i - (long)(n.x - 1) : i + 1;
291 w.ym = ((wrap & 2) && ly == g) ? i + (long)(n.y - 1) * sy : i - sy;
292 w.yp = ((wrap & 2) && ly == g + n.y - 1) ? i - (long)(n.y - 1) * sy : i + sy;
293 w.zm = ((wrap & 4) && lz == g) ? i + (long)(n.z - 1) * sz : i - sz;
294 w.zp = ((wrap & 4) && lz == g + n.z - 1) ? i - (long)(n.z - 1) * sz : i + sz;
295 return w;
296}
297
301KOKKOS_INLINE_FUNCTION Nb nbrsOn(int lx, int ly, int lz, C3 n, int g, long i, long sy, long sz,
302 int wrap) {
303 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
304 (void)lx, (void)ly, (void)lz, (void)n, (void)g, (void)wrap;
305 return Nb{i + 1, i - 1, i + sy, i - sy, i + sz, i - sz};
306 } else {
307 return nbrs(lx, ly, lz, n, g, i, sy, sz, wrap);
308 }
309}
310
313template <class OpV>
314inline void sweepColorFace(CCField x, CCConst rhs, OpV AC, OpV AFX, OpV AFY, OpV AFZ, C3 e, C3 og,
315 int g, int color, C3 n, int wrap) {
317 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
318 const int nyi = e.y - 2 * g, nzi = e.z - 2 * g;
319 const long cells = (long)nyi * nzi * (e.x - 2 * g);
320 auto pencil = KOKKOS_LAMBDA(long t) {
321 const int ly = g + (int)(t % nyi), lz = g + (int)(t / nyi);
322 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
323 const int P = (color + og.x + og.y + ly + og.z + lz) & 1;
324 PECLET_FLOW_OMP_SIMD // same-colour cells are independent (ccFor3's contract)
325 for (int lx = g + ((P ^ (g & 1)) & 1); lx < e.x - g; lx += 2) {
326 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
327 const Nb w = nbrsOn(lx, ly, lz, n, g, i, sy, sz, wrap);
328 cutcellSmoothFaceCell(x, rhs, AC, AFX, AFY, AFZ, i, sx, sy, sz, w.xp, w.xm, w.yp, w.ym,
329 w.zp, w.zm);
330 }
331 };
332 if (hostRunSerial(cells)) { // coarse MG level: the fork/join costs more than the sweep
333 for (long t = 0; t < (long)nyi * nzi; ++t)
334 pencil(t);
335 return;
336 }
337 Kokkos::parallel_for("peclet::flow::smg_smooth",
338 Kokkos::RangePolicy<CCExec>(space, 0, (long)nyi * nzi), pencil);
339 return;
340 }
341 using MD = MDRange3<CCExec>;
342 Kokkos::parallel_for(
343 "peclet::flow::smg_smooth", MD(space, {g, g, g}, {e.x - g, e.y - g, e.z - g}),
344 KOKKOS_LAMBDA(int lx, int ly, int lz) {
345 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
346 return;
347 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
348 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
349 const Nb w = nbrsOn(lx, ly, lz, n, g, i, sy, sz, wrap);
350 cutcellSmoothFaceCell(x, rhs, AC, AFX, AFY, AFZ, i, sx, sy, sz, w.xp, w.xm, w.yp, w.ym,
351 w.zp, w.zm);
352 });
353}
354
358 CCField AFZ, C3 e, int g, C3 n, int wrap) {
359 ccFor3(
360 "peclet::flow::smg_residual", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
361 KOKKOS_LAMBDA(int lx, int ly, int lz) {
362 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
363 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
364 const Nb w = nbrsOn(lx, ly, lz, n, g, i, sy, sz, wrap);
365 cutcellResidualFaceCell(r, x, b, AC, AFX, AFY, AFZ, i, sx, sy, sz, w.xp, w.xm, w.yp, w.ym,
366 w.zp, w.zm);
367 });
368}
369
370// ---- the advective path (design Amendment A2, §6.7): band form ----------------------------------
371
378 long st = 1;
379 double sgn = 1.0;
380 KOKKOS_INLINE_FUNCTION double operator()(long i) const {
381 return (unk(i) > 0.5 && unk(i - st) > 0.5) ? Kokkos::fmax(sgn * phi(i), 0.0) : 0.0;
382 }
383};
384
387template <class XV, class BV>
388KOKKOS_INLINE_FUNCTION double bandOff(const XV& x, const BV& AW, const BV& AE, const BV& AS,
389 const BV& AN, const BV& AB, const BV& AT, long i,
390 const Nb& w) {
391 return ((AW(i) * x(w.xm) + AE(i) * x(w.xp)) + (AS(i) * x(w.ym) + AN(i) * x(w.yp))) +
392 (AB(i) * x(w.zm) + AT(i) * x(w.zp));
393}
394
398template <class BV, class UV>
399inline void sweepColorBand(CCField x, CCConst rhs, BV AC, BV AW, BV AE, BV AS, BV AN, BV AB, BV AT,
400 UV unk, C3 e, C3 og, int g, int color, C3 n, int wrap) {
402 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
403 const int nyi = e.y - 2 * g, nzi = e.z - 2 * g;
404 const long cells = (long)nyi * nzi * (e.x - 2 * g);
405 auto pencil = KOKKOS_LAMBDA(long t) {
406 const int ly = g + (int)(t % nyi), lz = g + (int)(t / nyi);
407 const long sy = e.x, sz = (long)e.x * e.y;
408 const int P = (color + og.x + og.y + ly + og.z + lz) & 1;
409 for (int lx = g + ((P ^ (g & 1)) & 1); lx < e.x - g; lx += 2) {
410 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
411 if (!(unk(i) > 0.5))
412 continue;
413 const Nb w = nbrsOn(lx, ly, lz, n, g, i, sy, sz, wrap);
414 x(i) = (rhs(i) - bandOff(x, AW, AE, AS, AN, AB, AT, i, w)) / AC(i);
415 }
416 };
417 if (hostRunSerial(cells)) { // coarse MG level: the fork/join costs more than the sweep
418 for (long t = 0; t < (long)nyi * nzi; ++t)
419 pencil(t);
420 return;
421 }
422 Kokkos::parallel_for("peclet::flow::smg_smooth_band",
423 Kokkos::RangePolicy<CCExec>(space, 0, (long)nyi * nzi), pencil);
424 return;
425 }
426 using MD = MDRange3<CCExec>;
427 Kokkos::parallel_for(
428 "peclet::flow::smg_smooth_band", MD(space, {g, g, g}, {e.x - g, e.y - g, e.z - g}),
429 KOKKOS_LAMBDA(int lx, int ly, int lz) {
430 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
431 return;
432 const long sy = e.x, sz = (long)e.x * e.y;
433 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
434 if (!(unk(i) > 0.5))
435 return;
436 const Nb w = nbrsOn(lx, ly, lz, n, g, i, sy, sz, wrap);
437 x(i) = (rhs(i) - bandOff(x, AW, AE, AS, AN, AB, AT, i, w)) / AC(i);
438 });
439}
440
443template <class XV, class OV>
444KOKKOS_INLINE_FUNCTION double faceOff(const XV& x, const OV& AFX, const OV& AFY, const OV& AFZ,
445 long i, long sx, long sy, long sz, const Nb& w) {
446 return AFX(i + sx) * x(w.xp) + AFX(i) * x(w.xm) + AFY(i + sy) * x(w.yp) + AFY(i) * x(w.ym) +
447 AFZ(i + sz) * x(w.zp) + AFZ(i) * x(w.zm);
448}
449
458template <bool Band, class XV, class BV>
459KOKKOS_INLINE_FUNCTION void twoPhaseCell(const XV& x, const XV& rhs, const BV& AC, const BV& AFX,
460 const BV& AFY, const BV& AFZ, const BV& AW, const BV& AE,
461 const BV& AS, const BV& AN, const BV& AB, const BV& AT,
462 const XV& xs, const XV& rhss, const BV& ACs,
463 const BV& AFXs, const BV& AFYs, const BV& AFZs,
464 const BV& W, const CCConst& unkF, long i, long sx, long sy,
465 long sz, const Nb& w) {
466 const double wc = W(i);
467 const double af = AC(i), as = ACs(i);
468 if (wc > 0.0) {
469 const double nf = Band ? bandOff(x, AW, AE, AS, AN, AB, AT, i, w)
470 : faceOff(x, AFX, AFY, AFZ, i, sx, sy, sz, w);
471 const double rf = rhs(i) - nf;
472 const double rs = rhss(i) - faceOff(xs, AFXs, AFYs, AFZs, i, sx, sy, sz, w);
473 const double det = af * as - wc * wc;
474 if (det > 1e-300 * af * as) {
475 x(i) = (as * rf + wc * rs) / det;
476 xs(i) = (wc * rf + af * rs) / det;
477 } else {
478 x(i) = (rf + wc * xs(i)) / af;
479 xs(i) = (rs + wc * x(i)) / as;
480 }
481 return;
482 }
483 if constexpr (Band) {
484 if (unkF(i) > 0.5)
485 x(i) = (rhs(i) - bandOff(x, AW, AE, AS, AN, AB, AT, i, w)) / af;
486 } else {
487 if (!(af < 1e-30))
488 x(i) = (rhs(i) - faceOff(x, AFX, AFY, AFZ, i, sx, sy, sz, w)) / af;
489 }
490 if (!(as < 1e-30))
491 xs(i) = (rhss(i) - faceOff(xs, AFXs, AFYs, AFZs, i, sx, sy, sz, w)) / as;
492}
493
495template <bool Band>
496inline void sweepColorTwo(CCField x, CCField rhs, CCField AC, CCField AFX, CCField AFY, CCField AFZ,
497 CCField AW, CCField AE, CCField AS, CCField AN, CCField AB, CCField AT,
498 CCField xs, CCField rhss, CCField ACs, CCField AFXs, CCField AFYs,
499 CCField AFZs, CCField W, CCConst unkF, C3 e, C3 og, int g, int color,
500 C3 n, int wrap) {
502 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>) {
503 const int nyi = e.y - 2 * g, nzi = e.z - 2 * g;
504 const long cells = (long)nyi * nzi * (e.x - 2 * g);
505 auto pencil = KOKKOS_LAMBDA(long t) {
506 const int ly = g + (int)(t % nyi), lz = g + (int)(t / nyi);
507 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
508 const int P = (color + og.x + og.y + ly + og.z + lz) & 1;
509 for (int lx = g + ((P ^ (g & 1)) & 1); lx < e.x - g; lx += 2) {
510 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
511 const Nb w = nbrsOn(lx, ly, lz, n, g, i, sy, sz, wrap);
512 twoPhaseCell<Band>(x, rhs, AC, AFX, AFY, AFZ, AW, AE, AS, AN, AB, AT, xs, rhss, ACs, AFXs,
513 AFYs, AFZs, W, unkF, i, sx, sy, sz, w);
514 }
515 };
516 if (hostRunSerial(cells)) {
517 for (long t = 0; t < (long)nyi * nzi; ++t)
518 pencil(t);
519 return;
520 }
521 Kokkos::parallel_for("peclet::flow::smg_smooth_two",
522 Kokkos::RangePolicy<CCExec>(space, 0, (long)nyi * nzi), pencil);
523 return;
524 }
525 using MD = MDRange3<CCExec>;
526 Kokkos::parallel_for(
527 "peclet::flow::smg_smooth_two", MD(space, {g, g, g}, {e.x - g, e.y - g, e.z - g}),
528 KOKKOS_LAMBDA(int lx, int ly, int lz) {
529 if (((og.x + lx + og.y + ly + og.z + lz) & 1) != color)
530 return;
531 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
532 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
533 const Nb w = nbrsOn(lx, ly, lz, n, g, i, sy, sz, wrap);
534 twoPhaseCell<Band>(x, rhs, AC, AFX, AFY, AFZ, AW, AE, AS, AN, AB, AT, xs, rhss, ACs, AFXs,
535 AFYs, AFZs, W, unkF, i, sx, sy, sz, w);
536 });
537}
538
541template <bool Band>
543 CCField AFZ, CCField AW, CCField AE, CCField AS, CCField AN, CCField AB,
544 CCField AT, CCField rs, CCConst xs, CCConst bs, CCField ACs, CCField AFXs,
545 CCField AFYs, CCField AFZs, CCField W, C3 e, int g, C3 n, int wrap) {
546 ccFor3(
547 "peclet::flow::smg_residual_two", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
548 KOKKOS_LAMBDA(int lx, int ly, int lz) {
549 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
550 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
551 const Nb w = nbrsOn(lx, ly, lz, n, g, i, sy, sz, wrap);
552 const double ax = Band ? AC(i) * x(i) + bandOff(x, AW, AE, AS, AN, AB, AT, i, w)
553 : AC(i) * x(i) + faceOff(x, AFX, AFY, AFZ, i, sx, sy, sz, w);
554 const double axs = ACs(i) * xs(i) + faceOff(xs, AFXs, AFYs, AFZs, i, sx, sy, sz, w);
555 const double wc = W(i);
556 r(i) = (b(i) - ax) + wc * xs(i);
557 rs(i) = (bs(i) - axs) + wc * x(i);
558 });
559}
560
563 CCField AS, CCField AN, CCField AB, CCField AT, C3 e, int g, C3 n,
564 int wrap) {
565 ccFor3(
566 "peclet::flow::smg_residual_band", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
567 KOKKOS_LAMBDA(int lx, int ly, int lz) {
568 const long sy = e.x, sz = (long)e.x * e.y;
569 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
570 const Nb w = nbrsOn(lx, ly, lz, n, g, i, sy, sz, wrap);
571 r(i) = b(i) - (AC(i) * x(i) + bandOff(x, AW, AE, AS, AN, AB, AT, i, w));
572 });
573}
574
575} // namespace smg
576
577class ScalarMG {
578 public:
579 static constexpr int G0 = 2;
580 static constexpr int GC = 1;
589 static constexpr double kFullTableKappa = 25.0;
590 using Fill = std::function<void(CCField)>;
591
592 struct Level {
593 C3 ext{0, 0, 0}, inner{0, 0, 0}, ratio{1, 1, 1}, cfac{1, 1, 1};
594 C3 og{0, 0, 0};
595 C3 gdim{0, 0, 0};
596 int g = GC;
597 std::size_t n = 0;
602 Kokkos::View<double*, CCMem> plane[6];
603 double nUnk = 0.0;
609 CCField qp[3], qm[3];
617 double nUnkS = 0.0;
618#ifdef PECLET_FLOW_MPI
619 std::shared_ptr<GridHaloTopology<3>> halo;
620 std::shared_ptr<GridHalo<double>> dev;
621#endif
622 };
623
625 void setMetric(const double w[3], const double hp[3]) {
626 for (int a = 0; a < 3; ++a) {
627 w_[a] = w[a];
628 hp_[a] = hp[a];
629 }
630 aniso_ = !(hp_[0] == 1.0 && hp_[1] == 1.0 && hp_[2] == 1.0);
631 }
633 void setPeriodic(const bool per[3]) {
634 for (int a = 0; a < 3; ++a)
635 per_[a] = per[a];
636 }
637
641 void setLevel0Wrap(bool on) { fill0Wraps_ = on; }
642
644 void init(int nx, int ny, int nz, Fill fill0) {
645 lv_.clear();
646 distributed_ = false;
647 fill0_ = std::move(fill0);
648 C3 inner{nx, ny, nz}, cf{1, 1, 1};
649 for (int L = 0; L < kMaxLevels; ++L) {
650 Level v;
651 v.g = L == 0 ? G0 : GC;
652 v.inner = inner;
653 v.gdim = inner;
654 v.ext = C3{inner.x + 2 * v.g, inner.y + 2 * v.g, inner.z + 2 * v.g};
655 v.cfac = cf;
656 v.n = (std::size_t)v.ext.x * v.ext.y * v.ext.z;
657 const bool canA[3] = {can(inner.x), can(inner.y), can(inner.z)};
658 const C3 ratio = chooseRatio(canA, cf);
659 const C3 next{ratio.x == 2 ? inner.x / 2 : inner.x, ratio.y == 2 ? inner.y / 2 : inner.y,
660 ratio.z == 2 ? inner.z / 2 : inner.z};
661 v.ratio = ratio;
662 allocate(v, L);
663 lv_.push_back(v);
664 if (next.x == inner.x && next.y == inner.y && next.z == inner.z)
665 break;
666 inner = next;
667 cf = C3{cf.x * ratio.x, cf.y * ratio.y, cf.z * ratio.z};
668 }
669 }
670#ifdef PECLET_FLOW_MPI
674 void initMpi(const peclet::core::decomp::BlockDecomposer<3>& dec0, MPI_Comm comm, C3 og0,
675 Fill fill0) {
676 lv_.clear();
677 distributed_ = true;
678 comm_ = comm;
679 fill0_ = std::move(fill0);
680 int rank = 0;
681 MPI_Comm_rank(comm, &rank);
682 std::array<bool, 3> per{true, true, true}; // periodic everywhere; non-periodic ghosts zeroed
683 const auto& g0 = dec0.globalSize();
684 C3 gs{(int)g0[0], (int)g0[1], (int)g0[2]}, cf{1, 1, 1};
685 auto evenOn = [](const peclet::core::decomp::BlockDecomposer<3>& d, int ax) {
686 for (std::size_t b = 0; b < d.numBlocks(); ++b) {
687 const auto blk = d.block(b);
688 if (blk.origin[ax] % 2 != 0 || blk.size[ax] % 2 != 0)
689 return false;
690 }
691 return true;
692 };
693 peclet::core::decomp::BlockDecomposer<3> dec = dec0;
694 for (int L = 0; L < kMaxLevels; ++L) {
695 Level v;
696 v.g = L == 0 ? G0 : GC;
697 if (L == 0) {
698 const auto blk = dec.block((std::size_t)rank);
699 v.inner = C3{(int)blk.size[0], (int)blk.size[1], (int)blk.size[2]};
700 v.ext = C3{v.inner.x + 2 * G0, v.inner.y + 2 * G0, v.inner.z + 2 * G0};
701 v.og = og0;
702 } else {
703 v.halo = std::make_shared<GridHaloTopology<3>>();
704 v.halo->buildTopology(dec, rank, v.g, per, comm);
705 v.dev = std::make_shared<GridHalo<double>>();
706 v.dev->init(*v.halo);
707 v.dev->setLabel("smg L" + std::to_string(L));
708 const auto& idx = v.halo->indexer();
709 const auto eg = idx.sizeInclGhost(), ino = idx.sizeInner(), oig = idx.originInclGhost();
710 v.ext = {(int)eg[0], (int)eg[1], (int)eg[2]};
711 v.inner = {(int)ino[0], (int)ino[1], (int)ino[2]};
712 v.og = {(int)oig[0] + v.g, (int)oig[1] + v.g, (int)oig[2] + v.g};
713 }
714 v.gdim = gs;
715 v.cfac = cf;
716 v.n = (std::size_t)v.ext.x * v.ext.y * v.ext.z;
717 const bool canA[3] = {can(gs.x) && evenOn(dec, 0), can(gs.y) && evenOn(dec, 1),
718 can(gs.z) && evenOn(dec, 2)};
719 const C3 ratio = chooseRatio(canA, cf);
720 const C3 next{ratio.x == 2 ? gs.x / 2 : gs.x, ratio.y == 2 ? gs.y / 2 : gs.y,
721 ratio.z == 2 ? gs.z / 2 : gs.z};
722 v.ratio = ratio;
723 allocate(v, L);
724 lv_.push_back(v);
725 if (next.x == gs.x && next.y == gs.y && next.z == gs.z)
726 break;
727 gs = next;
728 cf = C3{cf.x * ratio.x, cf.y * ratio.y, cf.z * ratio.z};
729 dec = dec.coarsened(peclet::core::IVec<3>{ratio.x, ratio.y, ratio.z});
730 }
731 }
732#endif
733
734 int levels() const { return (int)lv_.size(); }
736 int levelsUsed() const { return nUse_; }
737 const Level& level(int L) const { return lv_[L]; }
739 std::vector<C3> levelRatios() const {
740 std::vector<C3> r;
741 for (const auto& v : lv_)
742 r.push_back(v.ratio);
743 return r;
744 }
745
747 struct Inputs {
748 double lam = 0.0;
749 double idt = 0.0;
750 bool fullTable = true;
751 bool singular = false;
754 const scg::ScalarFacetOverlay* fac = nullptr;
755 Kokkos::View<const double*, CCMem> facetW;
756 bool dirFace[6] = {false, false, false, false, false, false};
761 bool hasOutflow = false;
767 bool advective = false;
775 bool twoPhase = false;
778 Kokkos::View<const double*, CCMem> facetC;
779 };
780
782 void build(const Inputs& in) {
783 singular_ = in.singular;
784 adv_ = in.advective;
785 nUse_ = in.fullTable ? (int)lv_.size() : 1;
786 Level& l0 = lv_[0];
787 l0.AC = in.SAC;
788 if (adv_) { // A2: the operator's bands, no new storage
789 l0.AW = in.AW;
790 l0.AE = in.AE;
791 l0.AS = in.AS;
792 l0.AN = in.AN;
793 l0.AB = in.AB;
794 l0.AT = in.AT;
795 }
796 l0.unk = CCField(); // level 0's flag is the geometry's (const) view
797 unk0_ = in.unknown;
799 l0.nUnk = countUnknown(0);
800 two_ = in.twoPhase;
801 if (two_) { // WO-7: the solid phase and the coupling on level 0
802 l0.ACs = in.SACs;
803 l0.W = in.W0;
804 unkS0_ = in.sunk;
806 l0.nUnkS = countUnknownS(0);
807 }
808 for (int f = 0; f < 6; ++f)
809 dirFace_[f] = in.dirFace[f];
810 if (nUse_ > 1) {
811 const bool newLists = buildCutLists(in);
812 wallReuse_ = !newLists && wallKeep(in);
813 for (int f = 0; f < 6; ++f)
814 if (dirFace_[f] && touches(l0, f))
815 buildPlane0(in, f);
816 for (int L = 1; L < nUse_; ++L)
817 buildCoarse(in, L);
818 }
819 }
820
824 void apply(CCField z, CCField r) {
825 Level& l0 = lv_[0];
826 l0.x = z;
827 l0.rhs = r;
828 Kokkos::deep_copy(CCExec(), z, 0.0);
829 if (nUse_ == 1) {
830 for (int k = 0; k < 2; ++k) {
831 sweepColor(0, 0);
832 sweepColor(0, 1);
833 }
834 for (int k = 0; k < 2; ++k) {
835 sweepColor(0, 1);
836 sweepColor(0, 0);
837 }
838 return;
839 }
840 vcycle(0);
841 }
842
847 Level& l0 = lv_[0];
848 l0.x = zf;
849 l0.rhs = rf;
850 l0.xs = zs;
851 l0.rhss = rs;
852 Kokkos::deep_copy(CCExec(), zf, 0.0);
853 Kokkos::deep_copy(CCExec(), zs, 0.0);
854 if (nUse_ == 1) {
855 for (int k = 0; k < 2; ++k) {
856 sweepColor(0, 0);
857 sweepColor(0, 1);
858 }
859 for (int k = 0; k < 2; ++k) {
860 sweepColor(0, 1);
861 sweepColor(0, 0);
862 }
863 return;
864 }
865 vcycleTwo(0);
866 }
867
871 Level& l0 = lv_[0];
872 fill(0, xf);
873 fill(0, xs);
874 const C3 e = l0.ext;
875 const int g = l0.g;
876 CCField AC = l0.AC, AFX = l0.AFX, AFY = l0.AFY, AFZ = l0.AFZ, ACs = l0.ACs, AFXs = l0.AFXs,
877 AFYs = l0.AFYs, AFZs = l0.AFZs, W = l0.W, AW = l0.AW, AE = l0.AE, AS = l0.AS,
878 AN = l0.AN, AB = l0.AB, AT = l0.AT;
879 const bool band = adv_;
880 CCConst x = xf, s = xs;
881 ccFor3(
882 "peclet::flow::smg_apply_two", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
883 KOKKOS_LAMBDA(int lx, int ly, int lz) {
884 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
885 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
886 const smg::Nb w{i + sx, i - sx, i + sy, i - sy, i + sz, i - sz};
887 const double ax = band ? AC(i) * x(i) + smg::bandOff(x, AW, AE, AS, AN, AB, AT, i, w)
888 : AC(i) * x(i) + smg::faceOff(x, AFX, AFY, AFZ, i, sx, sy, sz, w);
889 const double axs = ACs(i) * s(i) + smg::faceOff(s, AFXs, AFYs, AFZs, i, sx, sy, sz, w);
890 yf(i) = ax - W(i) * s(i);
891 ys(i) = axs - W(i) * x(i);
892 });
893 }
894
897 Level& l0 = lv_[0];
898 fill(0, x);
899 const C3 e = l0.ext;
900 const int g = l0.g;
901 if (adv_) { // A2: S_adv at level 0 = the operator's band matvec with SAC as the diagonal
902 applyCutcellOp(y, CCConst(x), l0.AC, l0.AW, l0.AE, l0.AS, l0.AN, l0.AB, l0.AT, e, g);
903 return;
904 }
905 CCField AC = l0.AC, AFX = l0.AFX, AFY = l0.AFY, AFZ = l0.AFZ;
906 CCConst xx = x;
907 ccFor3(
908 "peclet::flow::smg_apply", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
909 KOKKOS_LAMBDA(int lx, int ly, int lz) {
910 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
911 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
912 y(i) = cutcellApplyFaceCell(xx, AC, AFX, AFY, AFZ, i, sx, sy, sz, i + sx, i - sx, i + sy,
913 i - sy, i + sz, i - sz);
914 });
915 }
916
921 std::vector<double> contraction(int iters, CCField e, CCField t, CCField z) {
922 Level& l0 = lv_[0];
923 const C3 ext = l0.ext, og = l0.og;
924 const int g = l0.g;
925 CCConst unk = unk0_;
926 ccFor3(
927 "peclet::flow::smg_seed", C3{g, g, g}, C3{ext.x - g, ext.y - g, ext.z - g},
928 KOKKOS_LAMBDA(int lx, int ly, int lz) {
929 const long i = (long)lx + (long)ly * ext.x + (long)lz * (long)ext.x * ext.y;
930 std::uint64_t h = (std::uint64_t)(og.x + lx - g) * 0x9E3779B97F4A7C15ull ^
931 (std::uint64_t)(og.y + ly - g) * 0xC2B2AE3D27D4EB4Full ^
932 (std::uint64_t)(og.z + lz - g) * 0x165667B19E3779F9ull;
933 h ^= h >> 29;
934 h *= 0xBF58476D1CE4E5B9ull;
935 h ^= h >> 32;
936 e(i) = unk(i) > 0.5 ? (double)(h >> 11) * (1.0 / 9007199254740992.0) - 0.5 : 0.0;
937 });
938 if (singular_)
939 removeMean(0, e);
940 std::vector<double> ratios;
941 double prev = norm2(e);
942 for (int k = 0; k < iters; ++k) {
943 applySurrogate(t, e);
944 apply(z, t);
945 if (singular_)
946 removeMean(0, z);
947 ccFor3(
948 "peclet::flow::smg_err_update", C3{g, g, g}, C3{ext.x - g, ext.y - g, ext.z - g},
949 KOKKOS_LAMBDA(int lx, int ly, int lz) {
950 const long i = (long)lx + (long)ly * ext.x + (long)lz * (long)ext.x * ext.y;
951 e(i) -= z(i);
952 });
953 if (singular_) // keep the iterate off the null space (round-off would otherwise grow there)
954 removeMean(0, e);
955 const double cur = norm2(e);
956 ratios.push_back(prev > 0.0 ? cur / prev : 0.0);
957 if (!(cur > 0.0))
958 break;
959 // renormalize (keeps the iterate O(1) over many iterations)
960 const double s = 1.0 / cur;
961 ccFor3(
962 "peclet::flow::smg_err_scale", C3{g, g, g}, C3{ext.x - g, ext.y - g, ext.z - g},
963 KOKKOS_LAMBDA(int lx, int ly, int lz) {
964 const long i = (long)lx + (long)ly * ext.x + (long)lz * (long)ext.x * ext.y;
965 e(i) *= s;
966 });
967 prev = 1.0;
968 }
969 return ratios;
970 }
971
972 // ---- (public for nvcc extended lambdas) ----
973 void vcycle(int L) {
974 Level& lv = lv_[L];
975 if (L + 1 == nUse_) { // bottom: 16 sweeps, symmetric order
976 for (int k = 0; k < kBottom / 2; ++k)
977 sweep(L, true);
978 for (int k = 0; k < kBottom / 2; ++k)
979 sweep(L, false);
980 if (singular_)
981 removeMean(L, lv.x);
982 return;
983 }
984 for (int k = 0; k < kPre; ++k)
985 sweep(L, true);
986 // the smoother leaves the ghosts one colour stale: a fresh fill, or the fused reads
987 const int wr = wrapReads(L);
988 if (wr < 0)
989 fill(L, lv.x);
990 if (adv_)
991 smg::residualBand(lv.res, CCConst(lv.x), CCConst(lv.rhs), lv.AC, lv.AW, lv.AE, lv.AS, lv.AN,
992 lv.AB, lv.AT, lv.ext, lv.g, lv.inner, wr < 0 ? 0 : wr);
993 else
994 smg::residualFace(lv.res, CCConst(lv.x), CCConst(lv.rhs), lv.AC, lv.AFX, lv.AFY, lv.AFZ,
995 lv.ext, lv.g, lv.inner, wr < 0 ? 0 : wr);
996 Level& cs = lv_[L + 1];
997 restrictZeroX(cs, lv, cs.rhs, lv.res, cs.x);
998 if (singular_)
999 removeMean(L + 1, cs.rhs);
1000 vcycle(L + 1);
1001 fill(L + 1, cs.x); // coarse ghosts: exchanged, 0 beyond a non-periodic global face
1002 prolongAdd(lv.x, CCConst(cs.x), lv.ext, cs.ext, lv.g, cs.g, lv.inner, lv.ratio);
1003 smg::zeroPinned(lv.x, unkOf(L), lv.ext, lv.g);
1004 for (int k = 0; k < kPost; ++k)
1005 sweep(L, false);
1006 }
1007
1011 void vcycleTwo(int L) {
1012 Level& lv = lv_[L];
1013 if (L + 1 == nUse_) {
1014 for (int k = 0; k < kBottom / 2; ++k)
1015 sweep(L, true);
1016 for (int k = 0; k < kBottom / 2; ++k)
1017 sweep(L, false);
1018 if (singular_)
1019 removeMeanTwo(L, lv.x, lv.xs);
1020 return;
1021 }
1022 for (int k = 0; k < kPre; ++k)
1023 sweep(L, true);
1024 const int wr = wrapReads(L);
1025 if (wr < 0) {
1026 fill(L, lv.x);
1027 fill(L, lv.xs);
1028 }
1029 const int w = wr < 0 ? 0 : wr;
1030 if (adv_)
1031 smg::residualTwo<true>(lv.res, CCConst(lv.x), CCConst(lv.rhs), lv.AC, lv.AFX, lv.AFY, lv.AFZ,
1032 lv.AW, lv.AE, lv.AS, lv.AN, lv.AB, lv.AT, lv.ress, CCConst(lv.xs),
1033 CCConst(lv.rhss), lv.ACs, lv.AFXs, lv.AFYs, lv.AFZs, lv.W, lv.ext,
1034 lv.g, lv.inner, w);
1035 else
1036 smg::residualTwo<false>(lv.res, CCConst(lv.x), CCConst(lv.rhs), lv.AC, lv.AFX, lv.AFY, lv.AFZ,
1037 lv.AW, lv.AE, lv.AS, lv.AN, lv.AB, lv.AT, lv.ress, CCConst(lv.xs),
1038 CCConst(lv.rhss), lv.ACs, lv.AFXs, lv.AFYs, lv.AFZs, lv.W, lv.ext,
1039 lv.g, lv.inner, w);
1040 Level& cs = lv_[L + 1];
1041 restrictZeroX(cs, lv, cs.rhs, lv.res, cs.x);
1042 restrictZeroX(cs, lv, cs.rhss, lv.ress, cs.xs);
1043 if (singular_)
1044 removeMeanTwo(L + 1, cs.rhs, cs.rhss);
1045 vcycleTwo(L + 1);
1046 fill(L + 1, cs.x);
1047 fill(L + 1, cs.xs);
1048 prolongAdd(lv.x, CCConst(cs.x), lv.ext, cs.ext, lv.g, cs.g, lv.inner, lv.ratio);
1049 prolongAdd(lv.xs, CCConst(cs.xs), lv.ext, cs.ext, lv.g, cs.g, lv.inner, lv.ratio);
1050 smg::zeroPinned(lv.x, unkOf(L), lv.ext, lv.g);
1051 smg::zeroPinned(lv.xs, unkSOf(L), lv.ext, lv.g);
1052 for (int k = 0; k < kPost; ++k)
1053 sweep(L, false);
1054 }
1055
1062 if (!distributed_) {
1063 restrictAvgZeroX(crhs, cx, CCConst(fres), cs.ext, lv.ext, cs.g, lv.g, cs.inner, lv.ratio);
1064 return;
1065 }
1066 restrictAvg(crhs, CCConst(fres), cs.ext, lv.ext, cs.g, lv.g, cs.inner, lv.ratio);
1067 Kokkos::deep_copy(CCExec(), cx, 0.0);
1068 }
1069
1072 void sweepColor(int L, int rb) {
1073 Level& lv = lv_[L];
1074 // WO-9b: on a single-rank level with every inner dimension even the pass reads the periodic
1075 // neighbours directly (wrapSmooth), so no fill; otherwise the fill before each colour
1076 const int ws = wrapSmooth(L);
1077 const int w = ws < 0 ? 0 : ws;
1078 if (ws < 0)
1079 fill(L, lv.x);
1080 const int kc = (rb + 3 * lv.g) & 1;
1081 if (two_) { // WO-7: the coupled two-field update (one more exchange: the solid's)
1082 if (ws < 0)
1083 fill(L, lv.xs);
1084 if (adv_)
1085 smg::sweepColorTwo<true>(lv.x, lv.rhs, lv.AC, lv.AFX, lv.AFY, lv.AFZ, lv.AW, lv.AE, lv.AS,
1086 lv.AN, lv.AB, lv.AT, lv.xs, lv.rhss, lv.ACs, lv.AFXs, lv.AFYs,
1087 lv.AFZs, lv.W, unkOf(L), lv.ext, lv.og, lv.g, kc, lv.inner, w);
1088 else
1089 smg::sweepColorTwo<false>(lv.x, lv.rhs, lv.AC, lv.AFX, lv.AFY, lv.AFZ, lv.AW, lv.AE, lv.AS,
1090 lv.AN, lv.AB, lv.AT, lv.xs, lv.rhss, lv.ACs, lv.AFXs, lv.AFYs,
1091 lv.AFZs, lv.W, unkOf(L), lv.ext, lv.og, lv.g, kc, lv.inner, w);
1092 return;
1093 }
1094 if (adv_) {
1095 smg::sweepColorBand(lv.x, CCConst(lv.rhs), lv.AC, lv.AW, lv.AE, lv.AS, lv.AN, lv.AB, lv.AT,
1096 unkOf(L), lv.ext, lv.og, lv.g, kc, lv.inner, w);
1097 return;
1098 }
1099 smg::sweepColorFace(lv.x, CCConst(lv.rhs), lv.AC, lv.AFX, lv.AFY, lv.AFZ, lv.ext, lv.og, lv.g,
1100 kc, lv.inner, w);
1101 }
1102
1104 void sweep(int L, bool fwd) {
1105 sweepColor(L, fwd ? 0 : 1);
1106 sweepColor(L, fwd ? 1 : 0);
1107 }
1108
1111 void fill(int L, CCField f) {
1112 if (L == 0) {
1113 fill0_(f);
1114 return;
1115 }
1116 Level& lv = lv_[L];
1117#ifdef PECLET_FLOW_MPI
1118 if (distributed_) {
1119 lv.dev->exchange(f);
1120 zeroNonPeriodicGhosts(lv, f);
1121 return;
1122 }
1123#endif
1124 smg::fillShell(f, lv.ext, lv.inner, lv.g, per_); // WO-9b: one launch, no fence
1125 }
1126
1134 int wrapReads(int L) const {
1135 // Device backends only: on a host backend the per-cell wrapped-index arithmetic costs more than
1136 // the fill it saves — it keeps the x-row loop from vectorizing (128^3 bed advance, 1 thread:
1137 // the smoother 700 -> 1131 ms and the residual 80 -> 124 ms per advance, against 44 ms of
1138 // level-0 fills), so host passes fill first (`fill` is one launch per coarse level anyway).
1139 if constexpr (std::is_same_v<typename CCExec::memory_space, Kokkos::HostSpace>)
1140 return -1;
1141 if (distributed_)
1142 return -1;
1143 if (L == 0)
1144 return fill0Wraps_ ? 7 : -1;
1145 return (per_[0] ? 1 : 0) | (per_[1] ? 2 : 0) | (per_[2] ? 4 : 0);
1146 }
1149 int wrapSmooth(int L) const {
1150 const Level& lv = lv_[L];
1151 if (lv.inner.x % 2 != 0 || lv.inner.y % 2 != 0 || lv.inner.z % 2 != 0)
1152 return -1;
1153 return wrapReads(L);
1154 }
1155
1157 bool touches(const Level& lv, int f) const {
1158 const int a = f / 2;
1159 const int o = (a == 0) ? lv.og.x : (a == 1) ? lv.og.y : lv.og.z;
1160 const int n = (a == 0) ? lv.inner.x : (a == 1) ? lv.inner.y : lv.inner.z;
1161 const int gn = (a == 0) ? lv.gdim.x : (a == 1) ? lv.gdim.y : lv.gdim.z;
1162 return (f % 2 == 0) ? (o == 0) : (o + n == gn);
1163 }
1164
1165 public: // nvcc: a member enclosing an extended device lambda must be public (data stays private)
1166 static constexpr int kMaxLevels = 64;
1167 static constexpr int kPre = 2, kPost = 2, kBottom = 16;
1168
1169 static bool can(int d) { return (d % 2 == 0) && (d / 2 >= 2); }
1170 C3 chooseRatio(const bool canA[3], C3 cf) const {
1171 const double H[3] = {hp_[0] * (double)cf.x, hp_[1] * (double)cf.y, hp_[2] * (double)cf.z};
1172 return CutcellMG::mgChooseRatio(H, canA, aniso_, kAspectTheta);
1173 }
1174 static constexpr double kAspectTheta = 2.0;
1175
1176 void allocate(Level& v, int L) {
1177 if (L == 0) {
1178 v.res = CCField("smg_res0", v.n);
1179 v.AFX = CCField("smg_afx0", v.n);
1180 v.AFY = CCField("smg_afy0", v.n);
1181 v.AFZ = CCField("smg_afz0", v.n);
1182 return;
1183 }
1184 v.x = CCField("smg_x", v.n);
1185 v.rhs = CCField("smg_rhs", v.n);
1186 v.res = CCField("smg_res", v.n);
1187 v.AC = CCField("smg_ac", v.n);
1188 v.AFX = CCField("smg_afx", v.n);
1189 v.AFY = CCField("smg_afy", v.n);
1190 v.AFZ = CCField("smg_afz", v.n);
1191 v.unk = CCField("smg_unk", v.n);
1192 v.mass = CCField("smg_mass", v.n);
1193 v.px = CCField("smg_px", v.n);
1194 v.py = CCField("smg_py", v.n);
1195 v.pz = CCField("smg_pz", v.n);
1196 }
1197
1198 CCConst unkOf(int L) const { return L == 0 ? unk0_ : CCConst(lv_[L].unk); }
1199 CCConst unkSOf(int L) const { return L == 0 ? unkS0_ : CCConst(lv_[L].unks); }
1200
1203 void allocateSolid(Level& v, int L) {
1204 if (v.ress.extent(0) == v.n)
1205 return;
1206 v.ress = CCField("smg_ress", v.n);
1207 v.AFXs = CCField("smg_afxs", v.n);
1208 v.AFYs = CCField("smg_afys", v.n);
1209 v.AFZs = CCField("smg_afzs", v.n);
1210 if (L == 0)
1211 return;
1212 v.xs = CCField("smg_xs", v.n);
1213 v.rhss = CCField("smg_rhss", v.n);
1214 v.ACs = CCField("smg_acs", v.n);
1215 v.unks = CCField("smg_unks", v.n);
1216 v.masss = CCField("smg_masss", v.n);
1217 v.pxs = CCField("smg_pxs", v.n);
1218 v.pys = CCField("smg_pys", v.n);
1219 v.pzs = CCField("smg_pzs", v.n);
1220 v.W = CCField("smg_w", v.n);
1221 }
1222
1223 double countUnknownS(int L) const {
1224 const Level& lv = lv_[L];
1225 const C3 e = lv.ext;
1226 const int g = lv.g;
1227 CCConst unk = unkSOf(L);
1228 double s = 0.0;
1229 ccReduce3(
1230 "peclet::flow::smg_count_s", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
1231 KOKKOS_LAMBDA(int lx, int ly, int lz, double& acc) {
1232 const long i = (long)lx + (long)ly * e.x + (long)lz * (long)e.x * e.y;
1233 acc += unk(i) > 0.5 ? 1.0 : 0.0;
1234 },
1235 Kokkos::Sum<double>(s));
1236 allSum(&s, 1);
1237 return s;
1238 }
1239
1242 void removeMeanTwo(int L, CCField f, CCField s) {
1243 const Level& lv = lv_[L];
1244 const double nTot = lv.nUnk + lv.nUnkS;
1245 if (!(nTot > 0.0))
1246 return;
1247 const C3 e = lv.ext;
1248 const int g = lv.g;
1249 CCConst uf = unkOf(L), us = unkSOf(L);
1250 double sum = 0.0;
1251 ccReduce3(
1252 "peclet::flow::smg_mean_two", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
1253 KOKKOS_LAMBDA(int lx, int ly, int lz, double& acc) {
1254 const long i = (long)lx + (long)ly * e.x + (long)lz * (long)e.x * e.y;
1255 acc += (uf(i) > 0.5 ? f(i) : 0.0) + (us(i) > 0.5 ? s(i) : 0.0);
1256 },
1257 Kokkos::Sum<double>(sum));
1258 allSum(&sum, 1);
1259 const double mean = sum / nTot;
1260 ccFor3(
1261 "peclet::flow::smg_mean_two_sub", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
1262 KOKKOS_LAMBDA(int lx, int ly, int lz) {
1263 const long i = (long)lx + (long)ly * e.x + (long)lz * (long)e.x * e.y;
1264 if (uf(i) > 0.5)
1265 f(i) -= mean;
1266 if (us(i) > 0.5)
1267 s(i) -= mean;
1268 });
1269 }
1270
1274 Level& l0 = lv_[0];
1275 allocateSolid(l0, 0);
1276 const C3 e = l0.ext;
1277 const double wx = w_[0], wy = w_[1], wz = w_[2];
1278 CCConst px = in.spx, py = in.spy, pz = in.spz;
1279 CCField AFX = l0.AFXs, AFY = l0.AFYs, AFZ = l0.AFZs;
1280 ccFor3(
1281 "peclet::flow::smg_faces0_solid", C3{0, 0, 0}, e, KOKKOS_LAMBDA(int x, int y, int z) {
1282 const long i = (long)x + (long)y * e.x + (long)z * (long)e.x * e.y;
1283 AFX(i) = -(wx * px(i));
1284 AFY(i) = -(wy * py(i));
1285 AFZ(i) = -(wz * pz(i));
1286 });
1287 }
1288
1292 void buildCoarseSolid(const Inputs& in, int L) {
1293 Level& c = lv_[L];
1294 Level& fin = lv_[L - 1];
1295 const C3 ratio = fin.ratio;
1296 if (L == 1)
1297 smg::restrictMax(c.unks, in.sunk, c.ext, fin.ext, c.g, fin.g, c.inner, ratio);
1298 else
1299 smg::restrictMax(c.unks, CCConst(fin.unks), c.ext, fin.ext, c.g, fin.g, c.inner, ratio);
1300 c.nUnkS = countUnknownS(L);
1301 {
1302 CCField px = c.pxs, py = c.pys, pz = c.pzs;
1303 const C3 ce = c.ext, fe = fin.ext;
1304 const int gc = c.g, gf = fin.g;
1305 const CCConst fx = L == 1 ? in.spx : CCConst(fin.pxs),
1306 fy = L == 1 ? in.spy : CCConst(fin.pys),
1307 fz = L == 1 ? in.spz : CCConst(fin.pzs);
1308 ccFor3(
1309 "peclet::flow::smg_coarsen_faces_solid", C3{0, 0, 0}, c.inner,
1310 KOKKOS_LAMBDA(int icx, int icy, int icz) {
1311 coarsenOpenAvgCell(px, py, pz, fx, fy, fz, ce, fe, gc, gf, ratio, icx, icy, icz);
1312 });
1313 fill(L, c.pxs);
1314 fill(L, c.pys);
1315 fill(L, c.pzs);
1316 const CCField P[3] = {c.pxs, c.pys, c.pzs};
1317 for (int a = 0; a < 3; ++a)
1318 if (!per_[a] && touches(c, 2 * a))
1319 smg::zeroPlanes(P[a], c.ext, a, 0, c.g + 1);
1320 }
1321 restrictAvg(c.masss, L == 1 ? in.ms0 : CCConst(fin.masss), c.ext, fin.ext, c.g, fin.g, c.inner,
1322 ratio);
1323 const C3 e = c.ext, ci = c.inner, cf = c.cfac;
1324 const int g = c.g;
1325 const double wx = w_[0] / ((double)cf.x * cf.x), wy = w_[1] / ((double)cf.y * cf.y),
1326 wz = w_[2] / ((double)cf.z * cf.z);
1327 CCField AC = c.ACs, AFX = c.AFXs, AFY = c.AFYs, AFZ = c.AFZs, W = c.W;
1328 CCConst px = c.pxs, py = c.pys, pz = c.pzs, unk = c.unks, mass = c.masss;
1329 ccFor3(
1330 "peclet::flow::smg_coarse_faces_solid", C3{0, 0, 0}, e, KOKKOS_LAMBDA(int x, int y, int z) {
1331 const long i = (long)x + (long)y * e.x + (long)z * (long)e.x * e.y;
1332 AFX(i) = -(wx * px(i));
1333 AFY(i) = -(wy * py(i));
1334 AFZ(i) = -(wz * pz(i));
1335 });
1336 ccFor3(
1337 "peclet::flow::smg_coarse_diag_solid", C3{0, 0, 0}, ci,
1338 KOKKOS_LAMBDA(int icx, int icy, int icz) {
1339 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
1340 const long i = (long)(icx + g) + (long)(icy + g) * sy + (long)(icz + g) * sz;
1341 if (!(unk(i) > 0.5)) {
1342 AC(i) = 1.0;
1343 AFX(i) = AFY(i) = AFZ(i) = 0.0;
1344 W(i) = 0.0;
1345 return;
1346 }
1347 const double tw = wx * px(i), te = wx * px(i + sx);
1348 const double ts = wy * py(i), tn = wy * py(i + sy);
1349 const double tb = wz * pz(i), tt = wz * pz(i + sz);
1350 AC(i) = (mass(i) + (((tw + te) + (ts + tn)) + (tb + tt))) + W(i);
1351 });
1352 }
1353
1355 const int ext[3] = {lv.ext.x, lv.ext.y, lv.ext.z};
1356 for (int a = 0; a < 3; ++a) {
1357 if (per_[a])
1358 continue;
1359 if (touches(lv, 2 * a))
1360 smg::zeroPlanes(f, lv.ext, a, 0, lv.g);
1361 if (touches(lv, 2 * a + 1))
1362 smg::zeroPlanes(f, lv.ext, a, ext[a] - lv.g, ext[a]);
1363 }
1364 }
1365
1366 // ---- reductions ----
1367 void allSum(double* v, int k) const {
1368#ifdef PECLET_FLOW_MPI
1369 if (distributed_) {
1370 std::vector<double> o((std::size_t)k);
1371 MPI_Allreduce(v, o.data(), k, MPI_DOUBLE, MPI_SUM, comm_);
1372 for (int j = 0; j < k; ++j)
1373 v[j] = o[(std::size_t)j];
1374 }
1375#endif
1376 (void)v;
1377 (void)k;
1378 }
1379 double countUnknown(int L) const {
1380 const Level& lv = lv_[L];
1381 const C3 e = lv.ext;
1382 const int g = lv.g;
1383 CCConst unk = unkOf(L);
1384 double s = 0.0;
1385 ccReduce3(
1386 "peclet::flow::smg_count", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
1387 KOKKOS_LAMBDA(int lx, int ly, int lz, double& acc) {
1388 const long i = (long)lx + (long)ly * e.x + (long)lz * (long)e.x * e.y;
1389 acc += unk(i) > 0.5 ? 1.0 : 0.0;
1390 },
1391 Kokkos::Sum<double>(s));
1392 allSum(&s, 1);
1393 return s;
1394 }
1396 void removeMean(int L, CCField f) {
1397 const Level& lv = lv_[L];
1398 if (!(lv.nUnk > 0.0))
1399 return;
1400 const C3 e = lv.ext;
1401 const int g = lv.g;
1402 CCConst unk = unkOf(L);
1403 double s = 0.0;
1404 ccReduce3(
1405 "peclet::flow::smg_mean", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
1406 KOKKOS_LAMBDA(int lx, int ly, int lz, double& acc) {
1407 const long i = (long)lx + (long)ly * e.x + (long)lz * (long)e.x * e.y;
1408 if (unk(i) > 0.5)
1409 acc += f(i);
1410 },
1411 Kokkos::Sum<double>(s));
1412 allSum(&s, 1);
1413 const double mean = s / lv.nUnk;
1414 ccFor3(
1415 "peclet::flow::smg_mean_sub", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
1416 KOKKOS_LAMBDA(int lx, int ly, int lz) {
1417 const long i = (long)lx + (long)ly * e.x + (long)lz * (long)e.x * e.y;
1418 if (unk(i) > 0.5)
1419 f(i) -= mean;
1420 });
1421 }
1422 double norm2(CCField f) const {
1423 const Level& lv = lv_[0];
1424 const C3 e = lv.ext;
1425 const int g = lv.g;
1426 double s = 0.0;
1427 ccReduce3(
1428 "peclet::flow::smg_norm", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
1429 KOKKOS_LAMBDA(int lx, int ly, int lz, double& acc) {
1430 const long i = (long)lx + (long)ly * e.x + (long)lz * (long)e.x * e.y;
1431 acc += f(i) * f(i);
1432 },
1433 Kokkos::Sum<double>(s));
1434 allSum(&s, 1);
1435 return std::sqrt(s);
1436 }
1437
1438 // ---- build ----
1443 Level& l0 = lv_[0];
1444 const C3 e = l0.ext;
1445 const double lx = in.lam * w_[0], ly = in.lam * w_[1], lz = in.lam * w_[2];
1446 CCConst unk = in.unknown, sax = in.sax, say = in.say, saz = in.saz;
1447 CCField AFX = l0.AFX, AFY = l0.AFY, AFZ = l0.AFZ;
1448 ccFor3(
1449 "peclet::flow::smg_faces0", C3{0, 0, 0}, e, KOKKOS_LAMBDA(int x, int y, int z) {
1450 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
1451 const long i = (long)x + (long)y * sy + (long)z * sz;
1452 const bool u = unk(i) > 0.5;
1453 const double tw = (x > 0 && u && unk(i - sx) > 0.5) ? lx * sax(i) : 0.0;
1454 const double ts = (y > 0 && u && unk(i - sy) > 0.5) ? ly * say(i) : 0.0;
1455 const double tb = (z > 0 && u && unk(i - sz) > 0.5) ? lz * saz(i) : 0.0;
1456 AFX(i) = -tw;
1457 AFY(i) = -ts;
1458 AFZ(i) = -tb;
1459 });
1460 }
1461
1470 bool buildCutLists(const Inputs& in) {
1471 const auto& fo = *in.fac;
1472 if (cutKey_ == fo.cutCell.data() && cutKeyN_ == fo.nCut && (int)cutStart_.size() == nUse_)
1473 return false;
1474 const Level& l0 = lv_[0];
1475 const C3 e0 = l0.ext, n0 = l0.inner;
1476 const int g0 = l0.g;
1477 auto hc = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), fo.cutCell);
1478 // the inner cut rows by level-0 linear index (the rows of one cell are unique)
1479 std::vector<std::pair<long, int>> rows;
1480 rows.reserve((std::size_t)fo.nCut);
1481 for (long r = 0; r < fo.nCut; ++r) {
1482 const long i0 = hc(r);
1483 const int x = (int)(i0 % e0.x) - g0, y = (int)((i0 / e0.x) % e0.y) - g0,
1484 z = (int)(i0 / ((long)e0.x * e0.y)) - g0;
1485 if (x >= 0 && x < n0.x && y >= 0 && y < n0.y && z >= 0 && z < n0.z)
1486 rows.emplace_back(i0, (int)r);
1487 }
1488 std::sort(rows.begin(), rows.end());
1489 cutStart_.assign((std::size_t)nUse_, Kokkos::View<int*, CCMem>());
1490 cutRows_.assign((std::size_t)nUse_, Kokkos::View<int*, CCMem>());
1491 for (int L = 1; L < nUse_; ++L) {
1492 const Level& c = lv_[L];
1493 const C3 ci = c.inner, cf = c.cfac;
1494 const long nc = (long)ci.x * ci.y * ci.z;
1495 std::vector<int> start((std::size_t)nc + 1, 0), list(rows.size());
1496 auto coarseOf = [&](long i0) {
1497 const int x = (int)(i0 % e0.x) - g0, y = (int)((i0 / e0.x) % e0.y) - g0,
1498 z = (int)(i0 / ((long)e0.x * e0.y)) - g0;
1499 return (long)(x / cf.x) + (long)(y / cf.y) * ci.x + (long)(z / cf.z) * ci.x * ci.y;
1500 };
1501 for (const auto& q : rows)
1502 ++start[(std::size_t)coarseOf(q.first) + 1];
1503 for (long k = 0; k < nc; ++k)
1504 start[(std::size_t)k + 1] += start[(std::size_t)k];
1505 std::vector<int> pos(start.begin(), start.end() - 1);
1506 for (const auto& q : rows) // stable: increasing level-0 index within each coarse cell
1507 list[(std::size_t)pos[(std::size_t)coarseOf(q.first)]++] = q.second;
1508 Kokkos::View<int*, CCMem> ds("smg_cut_start", (std::size_t)nc + 1),
1509 dl("smg_cut_rows", std::max<std::size_t>(list.size(), 1));
1510 Kokkos::deep_copy(
1511 ds, Kokkos::View<const int*, Kokkos::HostSpace, Kokkos::MemoryTraits<Kokkos::Unmanaged>>(
1512 start.data(), start.size()));
1513 if (!list.empty())
1514 Kokkos::deep_copy(
1515 Kokkos::subview(dl, std::make_pair((std::size_t)0, list.size())),
1516 Kokkos::View<const int*, Kokkos::HostSpace, Kokkos::MemoryTraits<Kokkos::Unmanaged>>(
1517 list.data(), list.size()));
1518 cutStart_[(std::size_t)L] = ds;
1519 cutRows_[(std::size_t)L] = dl;
1520 }
1521 cutKey_ = fo.cutCell.data();
1522 cutKeyN_ = fo.nCut;
1523 return true;
1524 }
1525
1534 bool wallKeep(const Inputs& in) {
1535 const auto& fo = *in.fac;
1536 const bool two = in.twoPhase;
1537 bool keep = wallValid_ && wallTwo_ == two && wallUse_ == nUse_ &&
1538 wallUnk_ == in.unknown.data() && cwSnap_.extent(0) == (std::size_t)fo.n &&
1539 (!two || ccSnap_.extent(0) == (std::size_t)fo.n);
1540 if (keep && fo.n > 0) {
1541 auto a = in.facetW, as = Kokkos::View<const double*, CCMem>(cwSnap_);
1542 auto c = two ? in.facetC : in.facetW,
1543 cs = two ? Kokkos::View<const double*, CCMem>(ccSnap_) : as;
1544 long diff = 0;
1545 Kokkos::parallel_reduce(
1546 "peclet::flow::smg_wall_same", Kokkos::RangePolicy<CCExec>(CCExec(), 0, fo.n),
1547 KOKKOS_LAMBDA(const long f, long& d) {
1548 auto same = [](double u, double w) {
1549 return u == w && Kokkos::signbit(u) == Kokkos::signbit(w);
1550 };
1551 if (!same(a(f), as(f)) || (two && !same(c(f), cs(f))))
1552 ++d;
1553 },
1554 diff);
1555 keep = diff == 0;
1556 }
1557 if (!keep) { // the gather below runs: snapshot its inputs
1558 if (cwSnap_.extent(0) != (std::size_t)fo.n)
1559 cwSnap_ = Kokkos::View<double*, CCMem>("smg_cw_snap", (std::size_t)fo.n);
1560 Kokkos::deep_copy(CCExec(), cwSnap_, in.facetW);
1561 if (two) {
1562 if (ccSnap_.extent(0) != (std::size_t)fo.n)
1563 ccSnap_ = Kokkos::View<double*, CCMem>("smg_cc_snap", (std::size_t)fo.n);
1564 Kokkos::deep_copy(CCExec(), ccSnap_, in.facetC);
1565 }
1566 wallValid_ = true;
1567 wallTwo_ = two;
1568 wallUse_ = nUse_;
1569 wallUnk_ = in.unknown.data();
1570 }
1571 return keep;
1572 }
1573
1576 void buildPlane0(const Inputs& in, int f) {
1577 Level& l0 = lv_[0];
1578 const int a = f / 2, side = f % 2;
1579 int t1, t2;
1580 tangents(a, t1, t2);
1581 const int ext[3] = {l0.ext.x, l0.ext.y, l0.ext.z};
1582 const long st[3] = {1, (long)l0.ext.x, (long)l0.ext.x * l0.ext.y};
1583 const int g = l0.g;
1584 const int n1 = ext[t1] - 2 * g, n2 = ext[t2] - 2 * g;
1585 const long sa = st[a], s1 = st[t1], s2 = st[t2];
1586 const int aInner = side == 0 ? g : ext[a] - g - 1;
1587 if (l0.plane[f].extent(0) != (std::size_t)n1 * n2)
1588 l0.plane[f] = Kokkos::View<double*, CCMem>("smg_plane0", (long)n1 * n2);
1589 auto pl = l0.plane[f];
1590 CCConst unk = in.unknown;
1591 CCConst saA = a == 0 ? in.sax : (a == 1 ? in.say : in.saz);
1592 const double lam = in.lam;
1593 CCExec space;
1594 Kokkos::parallel_for(
1595 "peclet::flow::smg_plane0", MDRange2<CCExec>(space, {0, 0}, {n1, n2}),
1596 KOKKOS_LAMBDA(int j1, int j2) {
1597 const long i = (long)aInner * sa + (long)(j1 + g) * s1 + (long)(j2 + g) * s2;
1598 const double abf = side == 0 ? saA(i) : saA(i + sa);
1599 pl((long)j1 + (long)j2 * n1) = unk(i) > 0.5 ? lam * abf : 0.0;
1600 });
1601 }
1602
1603 static void tangents(int a, int& t1, int& t2) {
1604 t1 = (a == 0) ? 1 : 0;
1605 t2 = (a == 2) ? 1 : 2;
1606 }
1607
1608 void buildCoarse(const Inputs& in, int L) {
1609 Level& c = lv_[L];
1610 Level& fin = lv_[L - 1];
1611 const C3 ratio = fin.ratio;
1612 // pins: max of the children's unknown flags
1613 if (L == 1)
1614 smg::restrictMax(c.unk, in.unknown, c.ext, fin.ext, c.g, fin.g, c.inner, ratio);
1615 else
1616 smg::restrictMax(c.unk, CCConst(fin.unk), c.ext, fin.ext, c.g, fin.g, c.inner, ratio);
1617 c.nUnk = countUnknown(L);
1618 // face products <Lam a>: coarsenOpenAvg's cell body on the fine products
1619 {
1620 CCField px = c.px, py = c.py, pz = c.pz;
1621 const C3 ce = c.ext, fe = fin.ext;
1622 const int gc = c.g, gf = fin.g;
1623 if (L == 1) {
1624 const long sy0 = fe.x, sz0 = (long)fe.x * fe.y;
1625 const smg::GuardedProduct fx{in.sax, in.unknown, in.lam, 1};
1626 const smg::GuardedProduct fy{in.say, in.unknown, in.lam, sy0};
1627 const smg::GuardedProduct fz{in.saz, in.unknown, in.lam, sz0};
1628 ccFor3(
1629 "peclet::flow::smg_coarsen_faces", C3{0, 0, 0}, c.inner,
1630 KOKKOS_LAMBDA(int icx, int icy, int icz) {
1631 coarsenOpenAvgCell(px, py, pz, fx, fy, fz, ce, fe, gc, gf, ratio, icx, icy, icz);
1632 });
1633 } else {
1634 CCConst fx = fin.px, fy = fin.py, fz = fin.pz;
1635 ccFor3(
1636 "peclet::flow::smg_coarsen_faces", C3{0, 0, 0}, c.inner,
1637 KOKKOS_LAMBDA(int icx, int icy, int icz) {
1638 coarsenOpenAvgCell(px, py, pz, fx, fy, fz, ce, fe, gc, gf, ratio, icx, icy, icz);
1639 });
1640 }
1641 fill(L, c.px);
1642 fill(L, c.py);
1643 fill(L, c.pz);
1644 // the low domain face of a non-periodic axis: no coupling toward the pinned ghost
1645 const CCField P[3] = {c.px, c.py, c.pz};
1646 for (int a = 0; a < 3; ++a)
1647 if (!per_[a] && touches(c, 2 * a))
1648 smg::zeroPlanes(P[a], c.ext, a, 0, c.g + 1);
1649 }
1650 // mass
1651 if (L == 1) {
1652 // A2: on the advective path the mass carries the open-face outflow omega_open (the interior
1653 // outflow is Q's); otherwise WO-5's lumped outflow, or nothing (WO-4) — those two verbatim
1654 const bool hasOut = in.advective || in.hasOutflow;
1655 const CCConst out = in.advective ? in.omegaOpen : (in.hasOutflow ? in.outflow : in.kappa);
1656 const smg::MassField m0{in.kappa, in.unknown, out, in.idt, hasOut};
1657 CCField cm = c.mass;
1658 const C3 ce = c.ext, fe = fin.ext, ci = c.inner;
1659 const int gc = c.g, gf = fin.g;
1660 ccFor3(
1661 "peclet::flow::smg_restrict_mass", C3{0, 0, 0}, ci,
1662 KOKKOS_LAMBDA(int icx, int icy, int icz) {
1663 restrictAvgCell(cm, m0, ce, fe, gc, gf, ratio, icx, icy, icz);
1664 });
1665 } else {
1666 restrictAvg(c.mass, CCConst(fin.mass), c.ext, fin.ext, c.g, fin.g, c.inner, ratio);
1667 }
1668 // Dirichlet boundary planes, averaged from the finer level's
1669 for (int f = 0; f < 6; ++f)
1670 if (dirFace_[f] && touches(c, f))
1671 coarsenPlane(L, f);
1672 // the operator
1673 if (two_) {
1674 allocateSolid(c, L);
1675 Kokkos::deep_copy(CCExec(), c.W, 0.0);
1676 }
1677 assembleCoarse(in, L);
1678 if (two_) // WO-7: the solid phase, W gathered by assembleCoarse
1679 buildCoarseSolid(in, L);
1680 if (adv_) { // A2: the coarse advection, then the 7 bands
1681 coarsenAdvection(in, L);
1682 assembleBands(L);
1683 }
1684 }
1685
1692 void coarsenAdvection(const Inputs& in, int L) {
1693 Level& c = lv_[L];
1694 Level& fin = lv_[L - 1];
1695 if (c.qp[0].extent(0) != c.n)
1696 for (int a = 0; a < 3; ++a) {
1697 c.qp[a] = CCField("smg_qp", c.n);
1698 c.qm[a] = CCField("smg_qm", c.n);
1699 }
1700 const C3 ratio = fin.ratio, ce = c.ext, fe = fin.ext;
1701 const int gc = c.g, gf = fin.g;
1702 const double ir[3] = {1.0 / (double)ratio.x, 1.0 / (double)ratio.y, 1.0 / (double)ratio.z};
1703 for (int sgn = 0; sgn < 2; ++sgn) {
1704 CCField qx = sgn == 0 ? c.qp[0] : c.qm[0], qy = sgn == 0 ? c.qp[1] : c.qm[1],
1705 qz = sgn == 0 ? c.qp[2] : c.qm[2];
1706 const double irx = ir[0], iry = ir[1], irz = ir[2];
1707 if (L == 1) {
1708 const long sy0 = fe.x, sz0 = (long)fe.x * fe.y;
1709 const double sg = sgn == 0 ? 1.0 : -1.0;
1710 const smg::GuardedPart fx{in.phi[0], in.unknown, 1, sg};
1711 const smg::GuardedPart fy{in.phi[1], in.unknown, sy0, sg};
1712 const smg::GuardedPart fz{in.phi[2], in.unknown, sz0, sg};
1713 ccFor3(
1714 "peclet::flow::smg_coarsen_adv", C3{0, 0, 0}, c.inner,
1715 KOKKOS_LAMBDA(int icx, int icy, int icz) {
1716 coarsenOpenAvgCell(qx, qy, qz, fx, fy, fz, ce, fe, gc, gf, ratio, icx, icy, icz);
1717 const long ci =
1718 (long)(icx + gc) + (long)(icy + gc) * ce.x + (long)(icz + gc) * (long)ce.x * ce.y;
1719 qx(ci) *= irx;
1720 qy(ci) *= iry;
1721 qz(ci) *= irz;
1722 });
1723 } else {
1724 const CCConst fx = sgn == 0 ? fin.qp[0] : fin.qm[0], fy = sgn == 0 ? fin.qp[1] : fin.qm[1],
1725 fz = sgn == 0 ? fin.qp[2] : fin.qm[2];
1726 ccFor3(
1727 "peclet::flow::smg_coarsen_adv", C3{0, 0, 0}, c.inner,
1728 KOKKOS_LAMBDA(int icx, int icy, int icz) {
1729 coarsenOpenAvgCell(qx, qy, qz, fx, fy, fz, ce, fe, gc, gf, ratio, icx, icy, icz);
1730 const long ci =
1731 (long)(icx + gc) + (long)(icy + gc) * ce.x + (long)(icz + gc) * (long)ce.x * ce.y;
1732 qx(ci) *= irx;
1733 qy(ci) *= iry;
1734 qz(ci) *= irz;
1735 });
1736 }
1737 }
1738 for (int a = 0; a < 3; ++a) {
1739 fill(L, c.qp[a]);
1740 fill(L, c.qm[a]);
1741 }
1742 for (int a = 0; a < 3; ++a)
1743 if (!per_[a] && touches(c, 2 * a)) {
1744 smg::zeroPlanes(c.qp[a], c.ext, a, 0, c.g + 1);
1745 smg::zeroPlanes(c.qm[a], c.ext, a, 0, c.g + 1);
1746 }
1747 }
1748
1753 void assembleBands(int L) {
1754 Level& c = lv_[L];
1755 if (c.AW.extent(0) != c.n) {
1756 c.AW = CCField("smg_aw", c.n);
1757 c.AE = CCField("smg_ae", c.n);
1758 c.AS = CCField("smg_as", c.n);
1759 c.AN = CCField("smg_an", c.n);
1760 c.AB = CCField("smg_ab", c.n);
1761 c.AT = CCField("smg_at", c.n);
1762 }
1763 const C3 e = c.ext;
1764 const int g = c.g;
1765 CCField AC = c.AC, AW = c.AW, AE = c.AE, AS = c.AS, AN = c.AN, AB = c.AB, AT = c.AT;
1766 CCConst AFX = c.AFX, AFY = c.AFY, AFZ = c.AFZ, unk = c.unk;
1767 CCConst qpx = c.qp[0], qpy = c.qp[1], qpz = c.qp[2], qmx = c.qm[0], qmy = c.qm[1],
1768 qmz = c.qm[2];
1769 Kokkos::deep_copy(CCExec(), AW, 0.0);
1770 Kokkos::deep_copy(CCExec(), AE, 0.0);
1771 Kokkos::deep_copy(CCExec(), AS, 0.0);
1772 Kokkos::deep_copy(CCExec(), AN, 0.0);
1773 Kokkos::deep_copy(CCExec(), AB, 0.0);
1774 Kokkos::deep_copy(CCExec(), AT, 0.0);
1775 ccFor3(
1776 "peclet::flow::smg_coarse_bands", C3{g, g, g}, C3{e.x - g, e.y - g, e.z - g},
1777 KOKKOS_LAMBDA(int lx, int ly, int lz) {
1778 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
1779 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
1780 if (!(unk(i) > 0.5))
1781 return;
1782 AC(i) =
1783 AC(i) + ((qmx(i) + qpx(i + sx)) + (qmy(i) + qpy(i + sy))) + (qmz(i) + qpz(i + sz));
1784 AW(i) = AFX(i) - qpx(i);
1785 AE(i) = AFX(i + sx) - qmx(i + sx);
1786 AS(i) = AFY(i) - qpy(i);
1787 AN(i) = AFY(i + sy) - qmy(i + sy);
1788 AB(i) = AFZ(i) - qpz(i);
1789 AT(i) = AFZ(i + sz) - qmz(i + sz);
1790 });
1791 }
1792
1793 void coarsenPlane(int L, int f) {
1794 Level& c = lv_[L];
1795 Level& fin = lv_[L - 1];
1796 const int a = f / 2;
1797 int t1, t2;
1798 tangents(a, t1, t2);
1799 const int cin[3] = {c.inner.x, c.inner.y, c.inner.z};
1800 const int fin3[3] = {fin.inner.x, fin.inner.y, fin.inner.z};
1801 const int rr[3] = {fin.ratio.x, fin.ratio.y, fin.ratio.z};
1802 const int n1 = cin[t1], n2 = cin[t2], f1 = fin3[t1];
1803 const int r1 = rr[t1], r2 = rr[t2];
1804 if (c.plane[f].extent(0) != (std::size_t)n1 * n2)
1805 c.plane[f] = Kokkos::View<double*, CCMem>("smg_plane", (long)n1 * n2);
1806 auto pc = c.plane[f];
1807 auto pf = fin.plane[f];
1808 CCExec space;
1809 Kokkos::parallel_for(
1810 "peclet::flow::smg_coarsen_plane", MDRange2<CCExec>(space, {0, 0}, {n1, n2}),
1811 KOKKOS_LAMBDA(int j1, int j2) {
1812 double s = 0.0;
1813 for (int p = 0; p < r1; ++p)
1814 for (int q = 0; q < r2; ++q)
1815 s += pf((long)(r1 * j1 + p) + (long)(r2 * j2 + q) * f1);
1816 pc((long)j1 + (long)j2 * n1) = s / (double)(r1 * r2);
1817 });
1818 }
1819
1823 void assembleCoarse(const Inputs& in, int L) {
1824 Level& c = lv_[L];
1825 const C3 e = c.ext, ci = c.inner, cf = c.cfac;
1826 const int g = c.g;
1827 const double wx = w_[0] / ((double)cf.x * cf.x), wy = w_[1] / ((double)cf.y * cf.y),
1828 wz = w_[2] / ((double)cf.z * cf.z);
1829 CCField AC = c.AC, AFX = c.AFX, AFY = c.AFY, AFZ = c.AFZ;
1830 CCConst px = c.px, py = c.py, pz = c.pz;
1831 ccFor3(
1832 "peclet::flow::smg_coarse_faces", C3{0, 0, 0}, e, KOKKOS_LAMBDA(int x, int y, int z) {
1833 const long i = (long)x + (long)y * e.x + (long)z * (long)e.x * e.y;
1834 AFX(i) = -(wx * px(i));
1835 AFY(i) = -(wy * py(i));
1836 AFZ(i) = -(wz * pz(i));
1837 });
1838 // the wall term: the plain average of the level-0 wall terms (fine probe distance, A1)
1839 const double invN = 1.0 / ((double)cf.x * cf.y * cf.z);
1840 auto cstart = cutStart_[(std::size_t)L];
1841 auto crows = cutRows_[(std::size_t)L];
1842 auto cutCell = in.fac->cutCell;
1843 CCConst unk0 = in.unknown;
1844 CCConst unk = c.unk, mass = c.mass;
1845 auto cfs = in.fac->cellFacetStart;
1846 auto cw = in.facetW;
1847 // WO-7 (A1 for the interface): the coupling W_c is the plain average of the level-0 cc at the
1848 // fine probe distances, gathered in the same order; it enters the fluid diagonal and is stored
1849 // as the level's W (the solid diagonal and the off-diagonal -W follow in buildCoarseSolid).
1850 const bool two = two_;
1851 auto ccv = in.facetC;
1852 CCField Wl = c.W;
1853 if (c.wallG.extent(0) != c.n) // the gathered W (and W_c) of this level, kept across builds
1854 c.wallG = CCField("smg_wall_gathered", c.n);
1855 if (two && c.wallCG.extent(0) != c.n)
1856 c.wallCG = CCField("smg_wallc_gathered", c.n);
1857 CCField wg = c.wallG, wcg = c.wallCG;
1858 const bool reuse = wallReuse_;
1859 ccFor3(
1860 "peclet::flow::smg_coarse_diag", C3{0, 0, 0}, ci, KOKKOS_LAMBDA(int icx, int icy, int icz) {
1861 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
1862 const long i = (long)(icx + g) + (long)(icy + g) * sy + (long)(icz + g) * sz;
1863 if (!(unk(i) > 0.5)) {
1864 AC(i) = 1.0;
1865 AFX(i) = AFY(i) = AFZ(i) = 0.0;
1866 return;
1867 }
1868 double W = 0.0, Wc = 0.0;
1869 if (reuse) { // wallKeep: the inputs of the last gather are unchanged
1870 W = wg(i);
1871 if (two)
1872 Wc = wcg(i);
1873 } else {
1874 // the cut descendants in increasing level-0 index = the (dz, dy, dx) order
1875 const long ic = (long)icx + (long)icy * ci.x + (long)icz * ci.x * ci.y;
1876 for (int k = cstart(ic); k < cstart(ic + 1); ++k) {
1877 const int row = crows(k);
1878 if (!(unk0(cutCell(row)) > 0.5))
1879 continue;
1880 for (int f = cfs(row); f < cfs(row + 1); ++f) {
1881 W += cw(f);
1882 if (two)
1883 Wc += ccv(f);
1884 }
1885 }
1886 W *= invN;
1887 if (two)
1888 Wc *= invN;
1889 wg(i) = W;
1890 if (two)
1891 wcg(i) = Wc;
1892 }
1893 const double tw = wx * px(i), te = wx * px(i + sx);
1894 const double ts = wy * py(i), tn = wy * py(i + sy);
1895 const double tb = wz * pz(i), tt = wz * pz(i + sz);
1896 if (two) {
1897 AC(i) = ((mass(i) + (((tw + te) + (ts + tn)) + (tb + tt))) + W) + Wc;
1898 Wl(i) = Wc;
1899 } else {
1900 AC(i) = (mass(i) + (((tw + te) + (ts + tn)) + (tb + tt))) + W;
1901 }
1902 });
1903 // Dirichlet domain faces: AC += 2 w_a(L) <Lam a_bf> on the unknown boundary cells
1904 const double wa[3] = {wx, wy, wz};
1905 for (int f = 0; f < 6; ++f) {
1906 if (!(dirFace_[f] && touches(c, f)))
1907 continue;
1908 const int a = f / 2, side = f % 2;
1909 int t1, t2;
1910 tangents(a, t1, t2);
1911 const int ext[3] = {e.x, e.y, e.z};
1912 const long st[3] = {1, (long)e.x, (long)e.x * e.y};
1913 const int n1 = ext[t1] - 2 * g, n2 = ext[t2] - 2 * g;
1914 const long sa = st[a], s1 = st[t1], s2 = st[t2];
1915 const int aInner = side == 0 ? g : ext[a] - g - 1;
1916 const double tw2 = 2.0 * wa[a];
1917 auto pl = c.plane[f];
1918 CCExec space;
1919 Kokkos::parallel_for(
1920 "peclet::flow::smg_coarse_dirichlet", MDRange2<CCExec>(space, {0, 0}, {n1, n2}),
1921 KOKKOS_LAMBDA(int j1, int j2) {
1922 const long i = (long)aInner * sa + (long)(j1 + g) * s1 + (long)(j2 + g) * s2;
1923 if (!(unk(i) > 0.5))
1924 return;
1925 AC(i) += tw2 * pl((long)j1 + (long)j2 * n1);
1926 });
1927 }
1928 }
1929
1930 private:
1931 std::vector<Level> lv_;
1932 Fill fill0_;
1933 bool fill0Wraps_ = false;
1934 CCConst unk0_;
1935 CCConst unkS0_;
1936 bool two_ = false;
1939 std::vector<Kokkos::View<int*, CCMem>> cutStart_, cutRows_;
1940 const void* cutKey_ = nullptr;
1941 long cutKeyN_ = -1;
1944 Kokkos::View<double*, CCMem> cwSnap_, ccSnap_;
1945 bool wallValid_ = false, wallTwo_ = false, wallReuse_ = false;
1946 int wallUse_ = 0;
1947 const void* wallUnk_ = nullptr;
1948 double w_[3] = {1.0, 1.0, 1.0};
1949 double hp_[3] = {1.0, 1.0, 1.0};
1950 bool aniso_ = false;
1951 bool per_[3] = {true, true, true};
1952 bool dirFace_[6] = {false, false, false, false, false, false};
1953 bool singular_ = false;
1954 bool adv_ = false;
1955 int nUse_ = 1;
1956 bool distributed_ = false;
1957#ifdef PECLET_FLOW_MPI
1958 MPI_Comm comm_ = MPI_COMM_NULL;
1959#endif
1960};
1961
1962} // namespace peclet::flow
1963
1964#endif // PECLET_FLOW_SCALAR_MG_HPP
static C3 mgChooseRatio(const double H[3], const bool canA[3], bool aniso, double theta)
static constexpr int kPre
std::function< void(CCField)> Fill
void assembleCoarse(const Inputs &in, int L)
Coarse face form + diagonal: AF_a = -w_a(L) <Lam a> over the full extent; on unknown inner cells AC =...
static constexpr int GC
coarse levels
void buildLevel0SolidFaces(const Inputs &in)
WO-7, level 0: the solid faces AF_a(i) = -(w_a P_a(i)) from the operator's guarded products — the exp...
void sweep(int L, bool fwd)
One full red-black sweep: R -> B forward, B -> R backward.
void sweepColor(int L, int rb)
One colour of red-black Gauss-Seidel on level L: rb = 0 red (global (gx+gy+gz) even),...
static bool can(int d)
void vcycleTwo(int L)
The two-field V-cycle (WO-7): vcycle's structure on both phases — the same sweeps (the 2x2 block upda...
static void tangents(int a, int &t1, int &t2)
void setMetric(const double w[3], const double hp[3])
Per-axis metric (VelocityMG::setMetric): w_a = 1/h_a'^2 and h_a'. Call before init.
void applySurrogateTwo(CCField yf, CCField ys, CCField xf, CCField xs)
y = S x for the two-field surrogate on level 0 (exchanges x first): y_f = S_ff x_f - W x_s,...
bool wallKeep(const Inputs &in)
The gathered coarse wall terms W (and the conjugate W_c) of the last build are reused when their inpu...
bool touches(const Level &lv, int f) const
Does this rank's block on level lv touch global face f (0..5 = -x,+x,-y,+y,-z,+z)?
void setPeriodic(const bool per[3])
Periodicity per axis (the flow's domain faces); a non-periodic axis' ghosts are pinned.
void assembleBands(int L)
A2: the coarse bands from the face form (assembleCoarse) and Q.
int wrapSmooth(int L) const
A red-black pass reads the wrapped neighbour safely only when it has the OTHER colour (so it still ho...
static constexpr int G0
level 0: the scalar's own block
void init(int nx, int ny, int nz, Fill fill0)
Single-rank level table (VelocityMG::init's rule, no depth cap).
void applySurrogate(CCField y, CCField x)
y = S x on level 0 (exchanges x first). For the contraction instrument and tests.
static constexpr int kMaxLevels
no depth cap in practice (the full table)
int levelsUsed() const
Levels the last build set up for the V-cycle (1: level 0 alone, the transient rule).
static constexpr int kBottom
const Level & level(int L) const
void fill(int L, CCField f)
Ghost exchange of a level-L field: level 0 the solver's own; coarse levels the per-level halo (or the...
void zeroNonPeriodicGhosts(Level &lv, CCField f)
C3 chooseRatio(const bool canA[3], C3 cf) const
std::vector< C3 > levelRatios() const
The per-level coarsening ratios (the level table; equal to VelocityMG's on the same grid).
double countUnknownS(int L) const
void coarsenAdvection(const Inputs &in, int L)
A2: Qp/Qm of level L from level L - 1 — level 1 from the guarded positive parts of the level-0 flux,...
CCConst unkSOf(int L) const
static constexpr int kPost
static constexpr double kFullTableKappa
The transient level rule (design §5.2, Amendment A3 + ruling D-WO9-3): kappa_A = 1 + 4 dt' D' sum_a w...
void build(const Inputs &in)
Rebuild the surrogate on every level from the current operator (the level table is kept).
static constexpr double kAspectTheta
void applyTwo(CCField zf, CCField zs, CCField rf, CCField rs)
The two-field z = M^-1 r (WO-7): one V-cycle on the coupled surrogate from z = 0 (level 0 alone: 2 + ...
void apply(CCField z, CCField r)
z = M^-1 r: one V-cycle on the surrogate (level 0 alone: 2 + 2 sweeps) from z = 0.
void restrictZeroX(Level &cs, Level &lv, CCField crhs, CCField fres, CCField cx)
The coarse rhs = restrictAvg of the fine residual, and the coarse iterate x = 0.
void allocate(Level &v, int L)
void removeMeanTwo(int L, CCField f, CCField s)
f, s -= their joint mean over both phases' unknown cells of level L (the singular two-field problem: ...
void buildPlane0(const Inputs &in, int f)
Level-0 Dirichlet boundary plane of face f: <Lam a_bf> = Lam a_bf on unknown boundary cells (sco::dir...
void coarsenPlane(int L, int f)
std::vector< double > contraction(int iters, CCField e, CCField t, CCField z)
Power estimate of the V-cycle's error contraction rho(I - M^-1 S) on the current surrogate (§11 G-ite...
bool buildCutLists(const Inputs &in)
Per coarse level L the overlay rows (cut cells) under each coarse inner cell, in increasing level-0 l...
double countUnknown(int L) const
void setLevel0Wrap(bool on)
Declare that the injected level-0 fill is the single-rank periodic wrap of ALL three axes (Solver::fi...
void allocateSolid(Level &v, int L)
WO-7: the solid-phase arrays of level L (and W on coarse levels), on the first two-field build.
int wrapReads(int L) const
WO-9b, the fused periodic-wrap reads (flow's A3): the axes (bit a) a level-L pass may read across the...
void buildLevel0Faces(const Inputs &in)
Level-0 faces: AF_a(i) = -(Lam w_a a_a(i)) when cells i - st_a and i are both unknowns — the same exp...
void allSum(double *v, int k) const
void buildCoarse(const Inputs &in, int L)
void removeMean(int L, CCField f)
f -= its mean over the unknown cells of level L (CutcellMG::removeMean's form, unweighted).
void buildCoarseSolid(const Inputs &in, int L)
WO-7: the coarse solid phase of level L (after assembleCoarse, which gathered W): pins = max of the c...
CCConst unkOf(int L) const
double norm2(CCField f) const
#define PECLET_FLOW_OMP_SIMD
flow — portable (Kokkos) geometric multigrid for the cut-cell (variable-openness) pressure Poisson.
flow — portable (Kokkos) cut-cell pressure operator + Chorin projection.
Nb nbrsOn(int lx, int ly, int lz, C3 n, int g, long i, long sy, long sz, int wrap)
nbrs in the kernels: on a host backend always the plain i +- s (ScalarMG::wrapReads never asks a host...
void sweepColorFace(CCField x, CCConst rhs, OpV AC, OpV AFX, OpV AFY, OpV AFZ, C3 e, C3 og, int g, int color, C3 n, int wrap)
One colour of the face-form red-black Gauss-Seidel: cutcellSmoothColorFace (mac_pressure....
void residualFace(CCField r, CCConst x, CCConst b, CCField AC, CCField AFX, CCField AFY, CCField AFZ, C3 e, int g, C3 n, int wrap)
r = b - A x (face form, double), inner cells.
void fillShell(CCField f, C3 e, C3 inner, int g, const bool per[3])
The single-rank ghost fill of a level in ONE launch (WO-9b; CutcellMG::fillWrap's form): a ghost cell...
void sweepColorTwo(CCField x, CCField rhs, CCField AC, CCField AFX, CCField AFY, CCField AFZ, CCField AW, CCField AE, CCField AS, CCField AN, CCField AB, CCField AT, CCField xs, CCField rhss, CCField ACs, CCField AFXs, CCField AFYs, CCField AFZs, CCField W, CCConst unkF, C3 e, C3 og, int g, int color, C3 n, int wrap)
One colour of the two-field red-black sweep (same launch forms as cutcellSmoothColorFace).
void zeroPlanes(CCField f, C3 e, int a, int lo, int hi)
f = 0 on the planes [lo, hi) of axis a (full transverse extent).
Nb nbrs(int lx, int ly, int lz, C3 n, int g, long i, long sy, long sz, int wrap)
void residualTwo(CCField r, CCConst x, CCConst b, CCField AC, CCField AFX, CCField AFY, CCField AFZ, CCField AW, CCField AE, CCField AS, CCField AN, CCField AB, CCField AT, CCField rs, CCConst xs, CCConst bs, CCField ACs, CCField AFXs, CCField AFYs, CCField AFZs, CCField W, C3 e, int g, C3 n, int wrap)
The two-field residual: r_f = rhs_f - (S_ff x_f + N_f) + W x_s (N_f from the bands or the faces),...
double bandOff(const XV &x, const BV &AW, const BV &AE, const BV &AS, const BV &AN, const BV &AB, const BV &AT, long i, const Nb &w)
The band-form off-diagonal sum of A2, in its fixed order: ((AW x_W + AE x_E) + (AS x_S + AN x_N)) + (...
double faceOff(const XV &x, const OV &AFX, const OV &AFY, const OV &AFZ, long i, long sx, long sy, long sz, const Nb &w)
The face-form neighbour sum of cutcellSmoothFaceCell, in its order; x's neighbours from nbrs (the coe...
void zeroPinned(CCField x, CCConst unk, C3 e, int g)
x(i) = 0 on the inner cells that are not unknowns (re-zero the pinned cells).
void residualBand(CCField r, CCConst x, CCConst b, CCField AC, CCField AW, CCField AE, CCField AS, CCField AN, CCField AB, CCField AT, C3 e, int g, C3 n, int wrap)
r = b - (AC x + bandOff) over the inner cells (A2's band-form residual).
void twoPhaseCell(const XV &x, const XV &rhs, const BV &AC, const BV &AFX, const BV &AFY, const BV &AFZ, const BV &AW, const BV &AE, const BV &AS, const BV &AN, const BV &AB, const BV &AT, const XV &xs, const XV &rhss, const BV &ACs, const BV &AFXs, const BV &AFYs, const BV &AFZs, const BV &W, const CCConst &unkF, long i, long sx, long sy, long sz, const Nb &w)
The two-field cell update of the conjugate V-cycle (§5.2, WO-7; design A2's WO-7 note): where both ph...
void sweepColorBand(CCField x, CCConst rhs, BV AC, BV AW, BV AE, BV AS, BV AN, BV AB, BV AT, UV unk, C3 e, C3 og, int g, int color, C3 n, int wrap)
One colour of the band-form Gauss-Seidel (A2), on the unknown inner cells of the colour: x_i <- (rhs_...
void restrictMax(CCField coarse, const FV &fine, C3 cext, C3 fext, int gc, int gf, C3 cinner, C3 ratio)
coarse flag = max of the children's flags (1 unknown, 0 pinned), over the coarse inner cells.
void restrictAvgZeroX(CCField coarse, CCField coarseX, CCConst fine, C3 cext, C3 fext, int gc, int gf, C3 cinner, C3 ratio)
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 prolongAdd(CCField fine, CCConst coarse, C3 fext, C3 cext, int gf, int gc, C3 finner, C3 ratio)
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 ccReduce3(const char *name, C3 lo, C3 hi, F f, R &&... reducers)
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 ccFor3(const char *name, C3 lo, C3 hi, F f)
Kokkos::View< double *, CCMem > CCField
void restrictAvg(CCField coarse, CCConst fine, C3 cext, C3 fext, int gc, int gf, C3 cinner, C3 ratio)
void coarsenOpenAvgCell(const CV &oxc, const CV &oyc, const CV &ozc, const FV &oxf, const FV &oyf, const FV &ozf, C3 cext, C3 fext, int gc, int gf, C3 ratio, int icx, int icy, int icz)
Kokkos::DefaultExecutionSpace CCExec
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 restrictAvgCell(const CV &coarse, const FV &fine, C3 cext, C3 fext, int gc, int gf, C3 ratio, int icx, int icy, int icz)
Kokkos::View< const double *, CCMem > CCConst
flow — the multidimensional iteration policies: iteration order follows storage.
Everything build reads from the scalar operator and the geometry record.
bool fullTable
the level rule (false: level 0 alone)
bool advective
A2 (§6.7): steady with advection -> the V-cycle runs on the ADVECTIVE surrogate S_adv on every level ...
CCConst phi[3]
F/V through the low a-face (st.phi, guarded)
double idt
1/dt' (0 steady)
bool twoPhase
Conjugate (WO-7, §5.2): the coupled two-field levels.
Kokkos::View< const double *, CCMem > facetC
bool singular
steady pure-Neumann: the mean is removed on every level
CCField AT
the operator's level-0 bands
const scg::ScalarFacetOverlay * fac
CCConst outflow
WO-5: the lumped implicit-FOU outflow per level-0 cell (§4.3; already inside SAC),...
CCField SAC
level-0 surrogate diagonal (§4.3)
Kokkos::View< const double *, CCMem > facetW
level-0 wall term per facet (cw = alpha G)
CCConst omegaOpen
the open faces' implicit outflow per level-0 cell
C3 gdim
GLOBAL dims of this level.
double nUnkS
global count of solid unknowns
CCField pz
coarse levels: m and the face products <Lam a>
CCField xs
Conjugate (WO-7): the solid phase in face form (level 0: x/rhs the caller's, AC = SAC_s,...
CCField AFZ
the surrogate, face form (level 0: AC = the caller's SAC)
CCField wallG
WO-9b: the gathered wall term W (and the conjugate W_c) of this coarse level, per cell,...
CCField unk
1 unknown / 0 pinned (level 0: the geometry's flag)
CCField AW
Advective path (A2): the 7 bands (level 0: the operator's AW..AT, AC = SAC; coarse: owned,...
CCField res
level 0: x / rhs are the caller's (z, r); res owned
C3 og
block inner origin (global red-black parity); {0,0,0} single-rank
Kokkos::View< double *, CCMem > plane[6]
Dirichlet domain faces: <Lam a_bf> (owning rank)
double nUnk
global count of unknown cells (the singular mean)
The facet overlay (§3.5): SoA, facet index fastest. Offsets are LINEAR offsets on the block.
Level-0 guarded positive part of the signed low-face flux: P(i) = max(sgn phi_a(i),...
double operator()(long i) const
Level-0 guarded face product Lam a (no metric weight): the face between cell i - st and cell i carrie...
Definition scalar_mg.hpp:86
double operator()(long i) const
Definition scalar_mg.hpp:90
Level-0 mass m = kappa idt on unknowns, 0 elsewhere (read by restrictAvgCell).
Definition scalar_mg.hpp:99
double operator()(long i) const
Neighbour indices of an inner cell for the fused periodic-wrap reads (WO-9b; flow's A3,...
static constexpr double AC
Kokkos::View< double *, peclet::flow::CCMem > OpV
Kokkos::View< float *, IMem > FV
static bool same(double a, double b)
static double mean(const std::vector< double > &a)