flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
mac_velocity_mg.hpp
Go to the documentation of this file.
1
18#ifndef PECLET_FLOW_MAC_VELOCITY_MG_HPP
19#define PECLET_FLOW_MAC_VELOCITY_MG_HPP
20
21#include <functional>
22#include <Kokkos_Core.hpp>
23#include <vector>
24
25#include "mac_cutcell_mg.hpp" // restrictAvg, prolongAdd, FPV/FPC
26#include "mac_ibm.hpp" // ibmRbgsStencilColor (pin smoother), MConst
27#include "staggered_advection.hpp" // fou_operator_aniso (upwind-convective coarse op)
28
29namespace peclet::flow {
30
31// pin-aware variable-coefficient residual (mg_residual_var_k): r = 0 at pinned (classified-solid)
32// cells, else b - A x with the float operator accumulated in double.
33inline void residualVarPin(CCField r, CCConst x, CCConst b, FPC AC, FPC AW, FPC AE, FPC AS, FPC AN,
34 FPC AB, FPC AT, CCConst pin, C3 e, int g) {
35 CCExec space;
36 const bool hasPin = (pin.extent(0) != 0);
37 using MD = Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>;
38 Kokkos::parallel_for(
39 "peclet::flow::vmg_resid", MD(space, {g, g, g}, {e.x - g, e.y - g, e.z - g}),
40 KOKKOS_LAMBDA(int lx, int ly, int lz) {
41 const long sx = 1, sy = e.x, sz = (long)e.x * e.y;
42 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
43 if (hasPin && pin(i) > 0.5) {
44 r(i) = 0.0;
45 return;
46 }
47 const double Ax = (double)AC(i) * x(i) + (double)AE(i) * x(i + sx) +
48 (double)AW(i) * x(i - sx) + (double)AN(i) * x(i + sy) +
49 (double)AS(i) * x(i - sy) + (double)AT(i) * x(i + sz) +
50 (double)AB(i) * x(i - sz);
51 r(i) = b(i) - Ax;
52 });
53}
54
55// masked trilinear prolongation (mg_prolong_masked_k): like prolongAdd but does NOT add the coarse
56// correction into a fine cell whose mask < eps (the clean-fluid exclude mask is 0 at IBM cut+solid
57// cells).
58inline void prolongMasked(CCField fine, CCConst coarse, CCConst mask, C3 fext, C3 cext, int g,
59 C3 finner, C3 ratio, double eps) {
60 CCExec space;
61 using MD = Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>;
62 Kokkos::parallel_for(
63 "peclet::flow::vmg_prolong_masked", MD(space, {0, 0, 0}, {finner.x, finner.y, finner.z}),
64 KOKKOS_LAMBDA(int ifx, int ify, int ifz) {
65 const long fi =
66 (long)(ifx + g) + (long)(ify + g) * fext.x + (long)(ifz + g) * (long)fext.x * fext.y;
67 if (mask(fi) < eps)
68 return; // no correction into a cut/solid fine cell
69 const double cx = (ratio.x == 2) ? 0.5 * ifx - 0.25 + g : ifx + g;
70 const double cy = (ratio.y == 2) ? 0.5 * ify - 0.25 + g : ify + g;
71 const double cz = (ratio.z == 2) ? 0.5 * ifz - 0.25 + g : ifz + g;
72 const double fxw = Kokkos::floor(cx), fyw = Kokkos::floor(cy), fzw = Kokkos::floor(cz);
73 const double wx = cx - fxw, wy = cy - fyw, wz = cz - fzw;
74 const int x0 = (int)fxw, y0 = (int)fyw, z0 = (int)fzw;
75 const long sy = cext.x, sz = (long)cext.x * cext.y;
76 auto C = [&](int xx, int yy, int zz) {
77 return coarse((long)xx + (long)yy * sy + (long)zz * sz);
78 };
79 const double c00 = C(x0, y0, z0) * (1 - wx) + C(x0 + 1, y0, z0) * wx;
80 const double c10 = C(x0, y0 + 1, z0) * (1 - wx) + C(x0 + 1, y0 + 1, z0) * wx;
81 const double c01 = C(x0, y0, z0 + 1) * (1 - wx) + C(x0 + 1, y0, z0 + 1) * wx;
82 const double c11 = C(x0, y0 + 1, z0 + 1) * (1 - wx) + C(x0 + 1, y0 + 1, z0 + 1) * wx;
83 const double c0 = c00 * (1 - wy) + c10 * wy, c1 = c01 * (1 - wy) + c11 * wy;
84 fine(fi) += c0 * (1 - wz) + c1 * wz;
85 });
86}
87
88// STAIRCASE coarse operator (mg_build_velocity_op_staircase_k): theta<thresh -> identity (pinned)
89// row; else a plain const-coeff Helmholtz (idiag + 2*(bx+by+bz) diagonal, per-axis -b
90// off-diagonals).
91inline void buildVelocityStaircase(FPV AC, FPV AW, FPV AE, FPV AS, FPV AN, FPV AB, FPV AT,
92 CCConst theta, C3 e, int g, double bx, double by, double bz,
93 double thresh, double idiag) {
94 CCExec space;
95 using MD = Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>;
96 Kokkos::parallel_for(
97 "peclet::flow::vmg_staircase", MD(space, {g, g, g}, {e.x - g, e.y - g, e.z - g}),
98 KOKKOS_LAMBDA(int lx, int ly, int lz) {
99 const long i = (long)lx + (long)ly * e.x + (long)lz * (long)e.x * e.y;
100 if (theta(i) <
101 thresh) { // classified solid -> identity row (smoother/residual pin it to 0)
102 AC(i) = 1.0f;
103 AW(i) = AE(i) = AS(i) = AN(i) = AB(i) = AT(i) = 0.0f;
104 return;
105 }
106 AC(i) = (float)(idiag + 2.0 * (bx + by + bz));
107 AW(i) = (float)(-bx);
108 AE(i) = (float)(-bx);
109 AS(i) = (float)(-by);
110 AN(i) = (float)(-by);
111 AB(i) = (float)(-bz);
112 AT(i) = (float)(-bz);
113 });
114}
115
116// UPWIND-CONVECTIVE coarse operator (build_adv_coarse_stencil_k): anisotropic const-coeff
117// backward-Euler diffusion (per-axis beta bx/by/bz) PLUS first-order-upwind advection from the
118// restricted coarse advecting velocity (scaled by s_a=1/cfac_a per face axis). M-matrix on every
119// level -> stable in the advection- dominated rows. The fine residual + smoother give the exact
120// sharp-IBM answer; this only sets the rate.
121inline void buildAdvCoarse(FPV AC, FPV AW, FPV AE, FPV AS, FPV AN, FPV AB, FPV AT, CCConst U,
122 CCConst V, CCConst W, int comp, C3 e, int g, double bx, double by,
123 double bz, double fouw, double sx, double sy, double sz, double idiag) {
124 CCExec space;
125 using MD = Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>;
126 Kokkos::parallel_for(
127 "peclet::flow::vmg_adv_coarse", MD(space, {g, g, g}, {e.x - g, e.y - g, e.z - g}),
128 KOKKOS_LAMBDA(int x, int y, int z) {
129 const long i = (long)x + (long)y * e.x + (long)z * (long)e.x * e.y;
130 double cC = idiag + 2.0 * (bx + by + bz), cxm = -bx, cxp = -bx, cym = -by, cyp = -by,
131 czm = -bz, czp = -bz;
132 sadv::ViewAcc Ua{U, e.x, e.y}, Va{V, e.x, e.y}, Wa{W, e.x, e.y};
133 sadv::fou_operator_aniso(comp, x, y, z, Ua, Va, Wa, fouw, sx, sy, sz, cC, cxm, cxp, cym,
134 cyp, czm, czp);
135 AC(i) = (float)cC;
136 AW(i) = (float)cxm;
137 AE(i) = (float)cxp;
138 AS(i) = (float)cym;
139 AN(i) = (float)cyp;
140 AB(i) = (float)czm;
141 AT(i) = (float)czp;
142 });
143}
144
145// CONST-COEFF anisotropic Helmholtz A = idiag*I - nu_dt*Lap (mg_const_diffusion_op_aniso_k), over
146// the WHOLE extended block (the domain-BC velocity op; coarse spacing via per-axis beta). For
147// cavity/BFS where the fine op is also const-coeff (no IBM stencil).
148inline void buildConstAniso(FPV AC, FPV AW, FPV AE, FPV AS, FPV AN, FPV AB, FPV AT, C3 e, double bx,
149 double by, double bz, double idiag) {
150 CCExec space;
151 const std::size_t n = (std::size_t)e.x * e.y * e.z;
152 const float c = (float)(idiag + 2.0 * (bx + by + bz)), nx = (float)(-bx), ny = (float)(-by),
153 nz = (float)(-bz);
154 Kokkos::parallel_for(
155 "peclet::flow::vmg_const_aniso", Kokkos::RangePolicy<CCExec>(space, 0, n),
156 KOKKOS_LAMBDA(std::size_t i) {
157 AC(i) = c;
158 AW(i) = nx;
159 AE(i) = nx;
160 AS(i) = ny;
161 AN(i) = ny;
162 AB(i) = nz;
163 AT(i) = nz;
164 });
165}
166
167// No-slip face-fold for the const-coeff MG operator (mg_diffusion_bc_fold_k): at a Dirichlet wall
168// the tangential ghost is 2*u_wall - u_inner -> +beta moves onto the boundary-adjacent inner cell's
169// diagonal (AC += beta); the dropped off-diagonal multiplies a held-0 ghost. Over the perp plane of
170// face (a,s).
171inline void boundaryFold(FPV AC, C3 e, int g, int a, int s, double beta) {
172 CCExec space;
173 int dims[3] = {e.x, e.y, e.z};
174 long st[3] = {1, e.x, (long)e.x * e.y};
175 const int b = (a + 1) % 3, c = (a + 2) % 3;
176 const long sa = st[a], sb = st[b], sc = st[c];
177 const int bic = (s == 0) ? g : (dims[a] - g - 1); // boundary-adjacent inner cell along a
178 Kokkos::parallel_for(
179 "peclet::flow::vmg_bc_fold",
180 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<2>>(space, {0, 0}, {dims[b], dims[c]}),
181 KOKKOS_LAMBDA(int p0, int p1) {
182 const long i = (long)p0 * sb + (long)p1 * sc + (long)bic * sa;
183 AC(i) = (float)((double)AC(i) + beta);
184 });
185}
186
187// Fill a non-periodic boundary ghost of a coarse correction before trilinear prolongation
188// (mg_fill_bc_ghost_k): Dirichlet (outflow) -> ghost 0; Neumann (wall/inflow) -> ghost = nearest
189// inner (zero-gradient). Plane (a,s).
190inline void fillBcGhost(CCField x, C3 e, int g, int a, int s, int dirichlet) {
191 CCExec space;
192 int dims[3] = {e.x, e.y, e.z};
193 long st[3] = {1, e.x, (long)e.x * e.y};
194 const int b = (a + 1) % 3, c = (a + 2) % 3;
195 const long sa = st[a], sb = st[b], sc = st[c];
196 const int na = dims[a];
197 Kokkos::parallel_for(
198 "peclet::flow::vmg_fill_bc_ghost",
199 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<2>>(space, {0, 0}, {dims[b], dims[c]}),
200 KOKKOS_LAMBDA(int p0, int p1) {
201 const long base = (long)p0 * sb + (long)p1 * sc;
202 if (s == 0) {
203 const double v = dirichlet ? 0.0 : x(base + (long)g * sa);
204 for (int ia = 0; ia < g; ++ia)
205 x(base + (long)ia * sa) = v;
206 } else {
207 const double v = dirichlet ? 0.0 : x(base + (long)(na - g - 1) * sa);
208 for (int ia = na - g; ia < na; ++ia)
209 x(base + (long)ia * sa) = v;
210 }
211 });
212}
213
214// zero a field on the plane at index `idx` along `axis` (held-Dirichlet boundary-face residual
215// exclude).
216inline void zeroPlane(CCField m, C3 e, int axis, int idx) {
217 CCExec space;
218 int dims[3] = {e.x, e.y, e.z};
219 long st[3] = {1, e.x, (long)e.x * e.y};
220 const int b = (axis + 1) % 3, c = (axis + 2) % 3;
221 const long sa = st[axis], sb = st[b], sc = st[c];
222 Kokkos::parallel_for(
223 "peclet::flow::vmg_zero_plane",
224 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<2>>(space, {0, 0}, {dims[b], dims[c]}),
225 KOKKOS_LAMBDA(int p0, int p1) { m((long)p0 * sb + (long)p1 * sc + (long)idx * sa) = 0.0; });
226}
227
228inline void thresholdMask(CCField m, CCConst theta,
229 double thresh) { // m = 1 where theta < thresh (solid)
230 CCExec space;
231 std::size_t n = m.extent(0);
232 CCField mm = m;
233 CCConst th = theta;
234 Kokkos::parallel_for(
235 "peclet::flow::vmg_threshold", Kokkos::RangePolicy<CCExec>(space, 0, n),
236 KOKKOS_LAMBDA(std::size_t i) { mm(i) = (th(i) < thresh) ? 1.0 : 0.0; });
237}
238inline void mulMask(CCField r, CCConst m) { // r *= m (clean-fluid residual filter)
239 CCExec space;
240 std::size_t n = r.extent(0);
241 CCField rr = r;
242 CCConst mm = m;
243 Kokkos::parallel_for(
244 "peclet::flow::vmg_mulmask", Kokkos::RangePolicy<CCExec>(space, 0, n),
245 KOKKOS_LAMBDA(std::size_t i) { rr(i) *= mm(i); });
246}
247
248// Velocity (momentum) geometric multigrid with the staircase coarse operator. All levels ghost
249// width G=2.
251 public:
252 static constexpr int G = 2;
253 struct Level {
254 C3 ext, inner, ratio{2, 2, 2}, cfac{1, 1, 1};
255 C3 og{0, 0, 0}; // block inner origin (global red-black parity); {0,0,0} single-rank
256 std::size_t n = 0;
258 CCField advU, advV, advW; // restricted advecting velocity (upwind-convective coarse op; L>=1)
259 FPV AC, AW, AE, AS, AN, AB, AT;
260#ifdef PECLET_FLOW_MPI
261 std::shared_ptr<GridHaloTopology<3>> halo; // per-level topology (decomposed)
262 std::shared_ptr<GridHalo<double>> dev; // per-level ghost exchange (ghost width G=2)
263#endif
264 };
265
266 // periodic uniform hierarchy (halve each axis while even and >=2, capped at nLevels).
267 void init(int nx, int ny, int nz, int nLevels) {
268 lv_.clear();
269 C3 inner{nx, ny, nz}, cf{1, 1, 1};
270 for (int L = 0; L < nLevels; ++L) {
271 Level v;
272 v.inner = inner;
273 v.ext = C3{inner.x + 2 * G, inner.y + 2 * G, inner.z + 2 * G};
274 v.cfac = cf;
275 v.n = (std::size_t)v.ext.x * v.ext.y * v.ext.z;
276 auto can = [&](int d) { return (d % 2 == 0) && (d / 2 >= 2); };
277 C3 next = inner;
278 C3 ratio{1, 1, 1};
279 if (L + 1 < nLevels) {
280 if (can(inner.x)) {
281 ratio.x = 2;
282 next.x = inner.x / 2;
283 }
284 if (can(inner.y)) {
285 ratio.y = 2;
286 next.y = inner.y / 2;
287 }
288 if (can(inner.z)) {
289 ratio.z = 2;
290 next.z = inner.z / 2;
291 }
292 }
293 v.ratio = ratio;
294 v.x = CCField("vmg_x", v.n);
295 v.rhs = CCField("vmg_rhs", v.n);
296 v.res = CCField("vmg_res", v.n);
297 v.theta = CCField("vmg_th", v.n);
298 v.pin = CCField("vmg_pin", v.n);
299 if (L > 0) { // coarse advecting velocity for the upwind-convective coarse op
300 v.advU = CCField("vmg_au", v.n);
301 v.advV = CCField("vmg_av", v.n);
302 v.advW = CCField("vmg_aw", v.n);
303 }
304 for (FPV* p : {&v.AC, &v.AW, &v.AE, &v.AS, &v.AN, &v.AB, &v.AT})
305 *p = FPV("vmg_A", v.n);
306 lv_.push_back(v);
307 if (next.x == inner.x && next.y == inner.y && next.z == inner.z)
308 break;
309 inner = next;
310 cf = C3{cf.x * ratio.x, cf.y * ratio.y, cf.z * ratio.z};
311 }
312 lv_[0].resMask =
313 CCField("vmg_resmask0", lv_[0].n); // level 0 only (clean-fluid exclude, staircase path)
314 }
315#ifdef PECLET_FLOW_MPI
316 // Multi-rank velocity-MG: coarsen the GLOBAL grid 2:1 per level; each level gets its own G=2 core
317 // halo. No global reductions here (the velocity op is non-singular -> no mean removal, no
318 // Krylov), so the fold is just fill()->exchange + the block-origin red-black parity. Single-rank
319 // (size 1) == init().
320 void initMpi(int gnx, int gny, int gnz, int nLevels, MPI_Comm comm) {
321 lv_.clear();
322 distributed_ = true;
323 int rank = 0, size = 1;
324 MPI_Comm_rank(comm, &rank);
325 MPI_Comm_size(comm, &size);
326 std::array<bool, 3> per{true, true, true};
327 C3 gs{gnx, gny, gnz}, cf{1, 1, 1};
328 auto can = [&](int d) { return (d % 2 == 0) && (d / 2 >= 2); };
329 for (int L = 0; L < nLevels; ++L) {
330 Level v;
331 v.halo = std::make_shared<GridHaloTopology<3>>();
332 peclet::core::decomp::BlockDecomposer<3> dec(static_cast<std::size_t>(size),
333 peclet::core::IVec<3>{gs.x, gs.y, gs.z});
334 v.halo->buildTopology(dec, rank, G, per, comm);
335 v.dev = std::make_shared<GridHalo<double>>();
336 v.dev->init(*v.halo);
337 const auto& idx = v.halo->indexer();
338 const auto eg = idx.sizeInclGhost(), ino = idx.sizeInner(), oig = idx.originInclGhost();
339 v.ext = {(int)eg[0], (int)eg[1], (int)eg[2]};
340 v.inner = {(int)ino[0], (int)ino[1], (int)ino[2]};
341 v.og = {(int)oig[0] + G, (int)oig[1] + G, (int)oig[2] + G};
342 v.cfac = cf;
343 v.n = idx.numCellsInclGhost();
344 C3 next = gs, ratio{1, 1, 1};
345 if (L + 1 < nLevels) {
346 if (can(gs.x)) {
347 ratio.x = 2;
348 next.x = gs.x / 2;
349 }
350 if (can(gs.y)) {
351 ratio.y = 2;
352 next.y = gs.y / 2;
353 }
354 if (can(gs.z)) {
355 ratio.z = 2;
356 next.z = gs.z / 2;
357 }
358 }
359 v.ratio = ratio;
360 v.x = CCField("vmg_x", v.n);
361 v.rhs = CCField("vmg_rhs", v.n);
362 v.res = CCField("vmg_res", v.n);
363 v.theta = CCField("vmg_th", v.n);
364 v.pin = CCField("vmg_pin", v.n);
365 if (L > 0) {
366 v.advU = CCField("vmg_au", v.n);
367 v.advV = CCField("vmg_av", v.n);
368 v.advW = CCField("vmg_aw", v.n);
369 }
370 for (FPV* p : {&v.AC, &v.AW, &v.AE, &v.AS, &v.AN, &v.AB, &v.AT})
371 *p = FPV("vmg_A", v.n);
372 lv_.push_back(v);
373 if (next.x == gs.x && next.y == gs.y && next.z == gs.z)
374 break;
375 gs = next;
376 cf = C3{cf.x * ratio.x, cf.y * ratio.y, cf.z * ratio.z};
377 }
378 lv_[0].resMask = CCField("vmg_resmask0", lv_[0].n);
379 }
380#endif
381 int nLevels() const { return (int)lv_.size(); }
382 Level& level(int L) { return lv_[L]; }
383
384 // level-0 fine operator = the external IBM stencil (7 float arrays on the same G=2 block).
385 void setFineStencil(FPC AC, FPC AW, FPC AE, FPC AS, FPC AN, FPC AB, FPC AT) {
386 Level& f = lv_[0];
387 Kokkos::deep_copy(f.AC, AC);
388 Kokkos::deep_copy(f.AW, AW);
389 Kokkos::deep_copy(f.AE, AE);
390 Kokkos::deep_copy(f.AS, AS);
391 Kokkos::deep_copy(f.AN, AN);
392 Kokkos::deep_copy(f.AB, AB);
393 Kokkos::deep_copy(f.AT, AT);
394 }
395
396 // staircase coarse op: level-0 pin = fine solid mask, resMask = clean-fluid mask; coarse levels
397 // classify by restricted theta and build a const-coeff Helmholtz. nu_dt = mu, idiag = rho/dt, h0
398 // = 1.
400 double thresh) {
401 usePin_ = true;
402 useResMask_ = true; // staircase: pin classified-solid cells + exclude the IBM band
403 Level& f = lv_[0];
404 Kokkos::deep_copy(f.theta, theta0);
405 Kokkos::deep_copy(f.pin, solid0);
406 Kokkos::deep_copy(f.resMask, resmask0);
407 for (int L = 1; L < (int)lv_.size(); ++L) {
408 Level& c = lv_[L];
409 Level& fin = lv_[L - 1];
410 restrictAvg(c.theta, CCConst(fin.theta), c.ext, fin.ext, G, c.inner,
411 fin.ratio); // coarse theta = avg
412 thresholdMask(c.pin, CCConst(c.theta), thresh);
413 const double bx = nu_dt / (double)(c.cfac.x * c.cfac.x),
414 by = nu_dt / (double)(c.cfac.y * c.cfac.y),
415 bz = nu_dt / (double)(c.cfac.z * c.cfac.z);
416 buildVelocityStaircase(c.AC, c.AW, c.AE, c.AS, c.AN, c.AB, c.AT, CCConst(c.theta), c.ext, G,
417 bx, by, bz, thresh, idiag);
418 }
419 }
420
421 // UPWIND-CONVECTIVE coarse op (implicit-FOU): restrict the advecting velocity u/v/w (level-0
422 // block) to every coarse level (8:1 average; numerical diffusion is welcome -> keeps the
423 // M-matrix). Call ONCE per Picard iteration, before buildUpwindCoarse for the 3 components (the
424 // velocity is shared/frozen at u^k).
426 for (int L = 1; L < (int)lv_.size(); ++L) {
427 Level& cs = lv_[L];
428 Level& fin = lv_[L - 1];
429 CCConst fu = (L == 1) ? u0 : CCConst(fin.advU), fv = (L == 1) ? v0 : CCConst(fin.advV),
430 fw = (L == 1) ? w0 : CCConst(fin.advW);
431 restrictAvg(cs.advU, fu, cs.ext, fin.ext, G, cs.inner, fin.ratio);
432 fill(cs, cs.advU);
433 restrictAvg(cs.advV, fv, cs.ext, fin.ext, G, cs.inner, fin.ratio);
434 fill(cs, cs.advV);
435 restrictAvg(cs.advW, fw, cs.ext, fin.ext, G, cs.inner, fin.ratio);
436 fill(cs, cs.advW);
437 }
438 }
439 // Build the coarse operators for component comp = aniso const-coeff diffusion + dt*FOU from the
440 // restricted advecting velocity (level 0 stays the fine As_[comp] set by setFineStencil). No pin
441 // / no exclude mask -- the upwind M-matrix is stable; the fine residual gives the exact sharp
442 // answer. Per Picard iter, per comp.
443 void buildUpwindCoarse(int comp, double nu_dt, double idiag, double fouw) {
444 usePin_ = false;
445 useResMask_ = false; // upwind path: pure variable-coeff MG (no pin/exclude)
446 for (int L = 1; L < (int)lv_.size(); ++L) {
447 Level& c = lv_[L];
448 const double bx = nu_dt / (double)(c.cfac.x * c.cfac.x),
449 by = nu_dt / (double)(c.cfac.y * c.cfac.y),
450 bz = nu_dt / (double)(c.cfac.z * c.cfac.z);
451 const double sx = 1.0 / (double)c.cfac.x, sy = 1.0 / (double)c.cfac.y,
452 sz = 1.0 / (double)c.cfac.z;
453 buildAdvCoarse(c.AC, c.AW, c.AE, c.AS, c.AN, c.AB, c.AT, CCConst(c.advU), CCConst(c.advV),
454 CCConst(c.advW), comp, c.ext, G, bx, by, bz, fouw, sx, sy, sz, idiag);
455 }
456 }
457
458 // DOMAIN-BC const-coeff path (cavity/BFS): per-face BC types {-x,+x,-y,+y,-z,+z}
459 // (0=periodic,1=wall, 2=inflow,3=outflow). Enables the non-periodic fill (periodic axes wrap;
460 // non-periodic ghosts left as the caller / correction set them) + the Dirichlet/Neumann
461 // prolongation ghosts.
462 void setBC(const int bc[6]) {
463 bcMode_ = false;
464 for (int i = 0; i < 6; ++i) {
465 bc_[i] = bc[i];
466 if (bc[i])
467 bcMode_ = true;
468 }
469 }
470 // Re-impose the full velocity BC on the level-0 iterate before each smoother colour + the
471 // residual (exactly as the RB-GS path does via fillVelGhosts(c,1)): the const-coeff smoother
472 // updates the held Dirichlet faces, so without this the boundary corners drift (~2% vs RB-GS, as
473 // the CUDA vmg also does). With it the vel-MG converges to the RB-GS fixed point. IbmSolver
474 // supplies this per component before the solve.
475 void setBcApplyL0(std::function<void(CCField)> fn) { bcApplyL0_ = std::move(fn); }
476 // const-coeff aniso operator + no-slip/inflow/outflow boundary fold for component comp, on EVERY
477 // level. nu_dt = mu, idiag = rho/dt, h0 = 1. Rebuilt per component (the fold is
478 // component-dependent). No pin. useResMask_: exclude the HELD normal-Dirichlet boundary face
479 // (a==comp, -side, wall/inflow) from coarsening -- that cell's value is pinned by the BC
480 // re-imposition, so its (nonzero) residual would drive a spurious coarse correction into the
481 // boundary (the ~2% drift CUDA's domain-BC vmg leaves). Excluding it makes the V-cycle converge
482 // to the RB-GS fixed point (analogue of the IBM clean-fluid exclude).
483 void setDomainBcOp(int comp, double nu_dt, double idiag) {
484 usePin_ = false;
485 Level& f = lv_[0];
486 Kokkos::deep_copy(f.resMask, 1.0);
487 useResMask_ = false;
488 for (int s = 0; s < 1;
489 ++s) { // only the -side face index G lands inside the smoother range [G, ext-G)
490 const int t = bc_[2 * comp + s];
491 if (t == 1 || t == 2) {
492 zeroPlane(f.resMask, f.ext, comp, G);
493 useResMask_ = true;
494 }
495 }
496 for (int L = 0; L < (int)lv_.size(); ++L) {
497 Level& c = lv_[L];
498 const double bx = nu_dt / (double)(c.cfac.x * c.cfac.x),
499 by = nu_dt / (double)(c.cfac.y * c.cfac.y),
500 bz = nu_dt / (double)(c.cfac.z * c.cfac.z);
501 buildConstAniso(c.AC, c.AW, c.AE, c.AS, c.AN, c.AB, c.AT, c.ext, bx, by, bz, idiag);
502 for (int f = 0; f < 6; ++f) {
503 const int a = f / 2, s = f % 2;
504 const double ba = (a == 0) ? bx : (a == 1) ? by : bz;
505 double dval;
506 if (bc_[f] == 3)
507 dval = -ba; // outflow zero-gradient: every component
508 else if ((bc_[f] == 1 || bc_[f] == 2) && a != comp)
509 dval = ba; // wall/inflow: tangential fold
510 else
511 continue; // periodic, or the normal comp at a wall
512 boundaryFold(c.AC, c.ext, G, a, s, dval);
513 }
514 }
515 }
516
517 // solve A x = b (b,x on the level-0 G=2 block): nvc V-cycles. Solution left in x.
518 void solve(CCConst b, CCField x, int nvc, int pre, int post, int bottom) {
519 pre_ = pre;
520 post_ = post;
521 bottom_ = bottom;
522 Level& l0 = lv_[0];
523 Kokkos::deep_copy(l0.rhs, b);
524 Kokkos::deep_copy(l0.x, x);
525 for (int v = 0; v < nvc; ++v)
526 vcycle(0);
527 Kokkos::deep_copy(x, l0.x);
528 }
529
530 public: // (public for nvcc extended-lambda)
531 void vcycle(int L) {
532 Level& lv = lv_[L];
533 const bool l0 = (L == 0);
534 if (L + 1 == (int)lv_.size()) {
535 smooth(lv, bottom_, l0);
536 return;
537 } // velocity op non-singular -> no mean removal
538 smooth(lv, pre_, l0);
539 fill(lv, lv.x);
540 if (l0 && bcApplyL0_)
541 bcApplyL0_(lv.x); // domain-BC: re-impose the velocity BC before the level-0 residual
542 residualVarPin(lv.res, CCConst(lv.x), CCConst(lv.rhs), FPC(lv.AC), FPC(lv.AW), FPC(lv.AE),
543 FPC(lv.AS), FPC(lv.AN), FPC(lv.AB), FPC(lv.AT),
544 usePin_ ? CCConst(lv.pin) : empty_, lv.ext, G);
545 const bool masked =
546 useResMask_ && (lv.resMask.extent(0) == lv.n); // level 0: exclude the IBM cut-cell band
547 if (masked)
548 mulMask(lv.res, CCConst(lv.resMask));
549 Level& cs = lv_[L + 1];
550 restrictAvg(cs.rhs, CCConst(lv.res), cs.ext, lv.ext, G, cs.inner, lv.ratio);
551 Kokkos::deep_copy(cs.x, 0.0);
552 vcycle(L + 1);
553 fill(cs, cs.x);
555 cs); // non-periodic boundary ghosts the trilinear prolong samples (domain-BC mode)
556 if (masked)
557 prolongMasked(lv.x, CCConst(cs.x), CCConst(lv.resMask), lv.ext, cs.ext, G, lv.inner, lv.ratio,
558 0.5);
559 else
560 prolongAdd(lv.x, CCConst(cs.x), lv.ext, cs.ext, G, lv.inner, lv.ratio);
561 smooth(lv, post_, l0);
562 }
563 void smooth(Level& lv, int sweeps, bool isL0) {
564 const C3 og = lv.og; // global red-black parity (block inner origin); {0,0,0} single-rank
565 CCConst pin = usePin_ ? CCConst(lv.pin) : empty_;
566 for (int k = 0; k < sweeps; ++k)
567 for (int color = 0; color < 2; ++color) {
568 fill(lv, lv.x);
569 if (isL0 && bcApplyL0_)
570 bcApplyL0_(lv.x); // re-impose the velocity BC (held Dirichlet faces) per colour
571 ibmRbgsStencilColor(lv.x, CCConst(lv.rhs), MConst(lv.AC), MConst(lv.AW), MConst(lv.AE),
572 MConst(lv.AS), MConst(lv.AN), MConst(lv.AB), MConst(lv.AT), pin, lv.ext,
573 og, G, color);
574 }
575 }
576 // periodic ghost fill; in domain-BC mode only the periodic axes wrap (non-periodic boundary
577 // ghosts are left as the caller / correction set them -- the boundary fold + held ghost represent
578 // the wall). Distributed (periodic IBM path): the per-level core halo (cross-rank + periodic in
579 // one call).
580 void fill(Level& lv, CCField f) {
581#ifdef PECLET_FLOW_MPI
582 if (distributed_ && !bcMode_) {
583 lv.dev->exchange(f);
584 return;
585 }
586#endif
587 for (int a = 0; a < 3; ++a)
588 if (!bcMode_ || (bc_[2 * a] == 0 && bc_[2 * a + 1] == 0))
589 fillAxis(lv, f, a);
590 }
591 // non-periodic boundary ghosts of a coarse correction before trilinear prolong (Dirichlet outflow
592 // -> 0, Neumann wall/inflow -> zero-gradient).
594 if (!bcMode_)
595 return;
596 for (int a = 0; a < 3; ++a)
597 for (int s = 0; s < 2; ++s) {
598 const int t = bc_[2 * a + s];
599 if (t == 0)
600 continue;
601 fillBcGhost(lv.x, lv.ext, G, a, s, t == 3 ? 1 : 0);
602 }
603 }
604 void fillAxis(Level& lv, CCField f, int axis) {
606 C3 e = lv.ext;
607 int N3[3] = {lv.inner.x, lv.inner.y, lv.inner.z};
608 int dims[3] = {e.x, e.y, e.z};
609 long st[3] = {1, e.x, (long)e.x * e.y};
610 const int a = axis, b = (axis + 1) % 3, c = (axis + 2) % 3;
611 const long sa = st[a], sb = st[b], sc = st[c];
612 const int N = N3[a];
613 CCField ff = f;
614 Kokkos::parallel_for(
615 "peclet::flow::vmg_pfill",
616 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<2>>(space, {0, 0}, {dims[b], dims[c]}),
617 KOKKOS_LAMBDA(int p0, int p1) {
618 const long base = (long)p0 * sb + (long)p1 * sc;
619 for (int gl = 0; gl < G; ++gl) {
620 ff(base + (long)gl * sa) = ff(base + (long)(gl + N) * sa);
621 ff(base + (long)(G + N + gl) * sa) = ff(base + (long)(G + gl) * sa);
622 }
623 });
624 }
625
626 private:
627 std::vector<Level> lv_;
628 int pre_ = 2, post_ = 2, bottom_ = 8;
629 bool usePin_ = true,
630 useResMask_ = true; // staircase: pin + clean-fluid exclude; upwind/domain-BC: neither
631 bool bcMode_ = false;
632 int bc_[6] = {0, 0, 0, 0, 0, 0}; // domain-BC (non-periodic) mode
633 std::function<void(CCField)> bcApplyL0_; // re-impose the velocity BC on level 0 (domain-BC mode)
634 CCConst empty_; // zero-extent View -> "no pin / no mask" to the kernels
635 bool distributed_ = false; // multi-rank (initMpi); periodic IBM path -> fill() exchanges
636};
637
638} // namespace peclet::flow
639
640#endif // PECLET_FLOW_MAC_VELOCITY_MG_HPP
void setFineStencil(FPC AC, FPC AW, FPC AE, FPC AS, FPC AN, FPC AB, FPC AT)
void fillProlongBcGhosts(Level &lv)
void buildUpwindCoarse(int comp, double nu_dt, double idiag, double fouw)
void fillAxis(Level &lv, CCField f, int axis)
void setBcApplyL0(std::function< void(CCField)> fn)
void setDomainBcOp(int comp, double nu_dt, double idiag)
void smooth(Level &lv, int sweeps, bool isL0)
void solve(CCConst b, CCField x, int nvc, int pre, int post, int bottom)
void restrictAdvVelocities(CCConst u0, CCConst v0, CCConst w0)
void fill(Level &lv, CCField f)
void setStaircase(CCConst theta0, CCConst solid0, CCConst resmask0, double nu_dt, double idiag, double thresh)
void setBC(const int bc[6])
void init(int nx, int ny, int nz, int nLevels)
flow — portable (Kokkos) geometric multigrid for the cut-cell (variable-openness) pressure Poisson.
flow — portable (Kokkos) IBM geometric fields + variable-coefficient RB-GS smoother.
void zeroPlane(CCField m, C3 e, int axis, int idx)
void buildConstAniso(FPV AC, FPV AW, FPV AE, FPV AS, FPV AN, FPV AB, FPV AT, C3 e, double bx, double by, double bz, double idiag)
void buildVelocityStaircase(FPV AC, FPV AW, FPV AE, FPV AS, FPV AN, FPV AB, FPV AT, CCConst theta, C3 e, int g, double bx, double by, double bz, double thresh, double idiag)
void thresholdMask(CCField m, CCConst theta, double thresh)
Kokkos::View< const MReal *, CCMem > FPC
void prolongAdd(CCField fine, CCConst coarse, C3 fext, C3 cext, int g, C3 finner, C3 ratio)
void residualVarPin(CCField r, CCConst x, CCConst b, FPC AC, FPC AW, FPC AE, FPC AS, FPC AN, FPC AB, FPC AT, CCConst pin, C3 e, int g)
void boundaryFold(FPV AC, C3 e, int g, int a, int s, double beta)
void ibmRbgsStencilColor(CCField x, CCConst b, MConst AC, MConst AW, MConst AE, MConst AS, MConst AN, MConst AB, MConst AT, CCConst solidmask, C3 ext, C3 og, int g, int color)
Definition mac_ibm.hpp:120
void fillBcGhost(CCField x, C3 e, int g, int a, int s, int dirichlet)
void restrictAvg(CCField coarse, CCConst fine, C3 cext, C3 fext, int g, C3 cinner, C3 ratio)
Kokkos::View< double *, CCMem > CCField
Kokkos::View< MReal *, CCMem > FPV
void mulMask(CCField r, CCConst m)
Kokkos::DefaultExecutionSpace CCExec
Kokkos::View< const float *, CCMem > MConst
void prolongMasked(CCField fine, CCConst coarse, CCConst mask, C3 fext, C3 cext, int g, C3 finner, C3 ratio, double eps)
void buildAdvCoarse(FPV AC, FPV AW, FPV AE, FPV AS, FPV AN, FPV AB, FPV AT, CCConst U, CCConst V, CCConst W, int comp, C3 e, int g, double bx, double by, double bz, double fouw, double sx, double sy, double sz, double idiag)
Kokkos::View< const double *, CCMem > CCConst
void fou_operator_aniso(int comp, int x, int y, int z, A U, A V, A W, double dt, double sx, double sy, double sz, double &cC, double &cxm, double &cxp, double &cym, double &cyp, double &czm, double &czp)
flow — portable (Kokkos) staggered MAC momentum advection (Koren TVD + FOU).
std::unique_ptr< GridHalo< double > > dev
std::unique_ptr< GridHaloTopology< kDim > > halo
static constexpr double AC
static constexpr int N