core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
flow.hpp
Go to the documentation of this file.
1// core — device (Kokkos) collocated incompressible Stokes step on a BlockOctree.
2//
3// The device counterpart of oracle::AmrFlow (flow.hpp): the whole cut-cell IBM projection step
4// runs in Kokkos kernels instead of the host-serial gaussSeidel + host projection the drag
5// study found to be the bottleneck. It reuses the host AmrCutCell / AmrPoisson to *build*
6// the operators (geometry, openness, cut stencils — done once), then drives the time step
7// entirely on the device:
8// * momentum predictor — MomentumOp (assembled cut-cell operator) solved with the
9// parallel BiCGStab of momentum.hpp;
10// * pressure projection — the openness Poisson on Multigrid / PCG;
11// * divergence, ABC gradient correction, rotational pressure update — face-CSR kernels
12// (FaceGeom) that mirror AmrPoisson::forEachFaceFull (same 2:1 sub-faces +
13// openness), so D / G / L stay consistent exactly as in the host collocated coupling.
14//
15// Stokes (advection off) and Navier–Stokes (setAdvection): implicit-FOU + explicit SOU/Koren
16// deferred correction, advected by the divergence-free face field uf (built each projection;
17// falls back to ½(u_i+u_j) until the first projection) — conservative (∇·uf = 0).
18//
19// Requires a Kokkos build + the morton checkout (PECLET_CORE_HAVE_MORTON).
20#ifndef PECLET_CORE_AMR_FLOW_HPP
21#define PECLET_CORE_AMR_FLOW_HPP
22
23#ifdef PECLET_CORE_HAVE_MORTON
24
25#include <array>
26#include <cmath>
27#include <cstdlib>
28#include <vector>
29
30#include "peclet/core/amr/advect_recon.hpp" // shared high-order face reconstruction (host+device)
33#include "peclet/core/amr/face_geom.hpp" // FaceGeom (shared with the device assembler)
34#include "peclet/core/amr/facegeom_assembly.hpp" // assembleFaceGeom (D4/D6)
36#include "peclet/core/amr/momentum_assembly.hpp" // assembleMomentum (D3/D6)
43
44namespace peclet::core::amr {
45
46// FaceGeom (the collocated projection's static face-geometry CSR) now lives in face_geom.hpp so the
47// device assembler and this driver share the type without a circular include.
48
50template <int Dim, unsigned Bits, class FluidFn>
52 const Index n = ap.octree().numLeaves();
53 std::vector<Index> start(static_cast<std::size_t>(n) + 1, 0);
54 for (Index i = 0; i < n; ++i) {
55 Index cnt = 0;
56 ap.forEachFaceFull(i, [&](Index, int, int, double, double, double) { ++cnt; });
57 start[static_cast<std::size_t>(i) + 1] = start[static_cast<std::size_t>(i)] + cnt;
58 }
59 const Index nf = start[static_cast<std::size_t>(n)];
60 std::vector<Index> nbr(static_cast<std::size_t>(nf));
61 std::vector<int> axis(static_cast<std::size_t>(nf)), dir(static_cast<std::size_t>(nf));
62 std::vector<double> aArea(static_cast<std::size_t>(nf)), rArea(static_cast<std::size_t>(nf)),
63 dist(static_cast<std::size_t>(nf)), alpha(static_cast<std::size_t>(nf));
64 std::vector<Index> upupI(static_cast<std::size_t>(nf)), upupJ(static_cast<std::size_t>(nf));
65 std::vector<double> invVol(static_cast<std::size_t>(n));
66 std::vector<char> fluid(static_cast<std::size_t>(n));
67 for (Index i = 0; i < n; ++i) {
68 invVol[static_cast<std::size_t>(i)] = 1.0 / ap.cellVolume(i);
69 fluid[static_cast<std::size_t>(i)] = isFluid(i) ? 1 : 0;
70 Index k = start[static_cast<std::size_t>(i)];
71 ap.forEachFaceFull(i, [&](Index j, int ax, int dr, double area, double d, double al) {
72 nbr[static_cast<std::size_t>(k)] = j;
73 axis[static_cast<std::size_t>(k)] = ax;
74 dir[static_cast<std::size_t>(k)] = dr;
75 aArea[static_cast<std::size_t>(k)] = al * area;
76 rArea[static_cast<std::size_t>(k)] = area;
77 dist[static_cast<std::size_t>(k)] = d;
78 alpha[static_cast<std::size_t>(k)] = al;
79 // SOU upstream-of-upwind probes (point neighbour one cell further upstream): if i is the
80 // upwind cell the upstream is across i's −dir face; if j is upwind, across j's +dir face.
81 upupI[static_cast<std::size_t>(k)] = ap.periodicNeighbor(i, ax, -dr);
82 upupJ[static_cast<std::size_t>(k)] = ap.periodicNeighbor(j, ax, dr);
83 ++k;
84 });
85 }
86 FaceGeom g;
87 g.n = n;
88 g.start = toDevice(start, "fg_start");
89 g.nbr = toDevice(nbr, "fg_nbr");
90 g.axis = toDevice(axis, "fg_axis");
91 g.dir = toDevice(dir, "fg_dir");
92 g.alphaArea = toDevice(aArea, "fg_aarea");
93 g.rawArea = toDevice(rArea, "fg_rarea");
94 g.dist = toDevice(dist, "fg_dist");
95 g.alpha = toDevice(alpha, "fg_alpha");
96 g.upupI = toDevice(upupI, "fg_upupi");
97 g.upupJ = toDevice(upupJ, "fg_upupj");
98 g.invVol = toDevice(invVol, "fg_invvol");
99 g.fluid = toDevice(fluid, "fg_fluid");
100 return g;
101}
102
108 auto st = g.start;
109 auto nb = g.nbr;
110 auto ax = g.axis;
111 auto dr = g.dir;
112 auto aA = g.alphaArea;
113 auto iv = g.invVol;
114 auto fl = g.fluid;
115 Kokkos::parallel_for(
116 "amr::flow_div", g.n, KOKKOS_LAMBDA(const Index i) {
117 if (!fl(i)) {
118 div(i) = 0.0;
119 return;
120 }
121 double d = 0.0;
122 for (Index k = st(i); k < st(i + 1); ++k) {
123 const int a = ax(k);
124 const double ui = (a == 0) ? u0(i) : (a == 1) ? u1(i) : u2(i);
125 const Index j = nb(k);
126 const double uj = (a == 0) ? u0(j) : (a == 1) ? u1(j) : u2(j);
127 d += aA(k) * dr(k) * 0.5 * (ui + uj);
128 }
129 div(i) = d * iv(i);
130 });
131}
132
141 auto st = g.start;
142 auto nb = g.nbr;
143 auto ax = g.axis;
144 auto dr = g.dir;
145 auto di = g.dist;
146 Kokkos::parallel_for(
147 "amr::flow_buildface", g.n, KOKKOS_LAMBDA(const Index i) {
148 for (Index k = st(i); k < st(i + 1); ++k) {
149 const int a = ax(k);
150 const Index j = nb(k);
151 const double ui = (a == 0) ? u0(i) : (a == 1) ? u1(i) : u2(i);
152 const double uj = (a == 0) ? u0(j) : (a == 1) ? u1(j) : u2(j);
153 const double gphi = (dr(k) > 0) ? (phi(j) - phi(i)) / di(k) : (phi(i) - phi(j)) / di(k);
154 uf(k) = 0.5 * (ui + uj) - gphi;
155 }
156 });
157}
158
161inline double divFaceNorm(const FaceGeom& g, View<const double> uf) {
162 auto st = g.start;
163 auto dr = g.dir;
164 auto aA = g.alphaArea;
165 auto iv = g.invVol;
166 auto fl = g.fluid;
167 double s = 0.0;
168 Kokkos::parallel_reduce(
169 "amr::flow_divface", g.n,
170 KOKKOS_LAMBDA(const Index i, double& acc) {
171 if (!fl(i))
172 return;
173 double d = 0.0;
174 for (Index k = st(i); k < st(i + 1); ++k)
175 d += aA(k) * dr(k) * uf(k);
176 d *= iv(i);
177 acc += d * d;
178 },
179 s);
180 return std::sqrt(s);
181}
182
188 auto st = g.start;
189 auto nb = g.nbr;
190 auto ax = g.axis;
191 auto dr = g.dir;
192 auto di = g.dist;
193 auto al = g.alpha;
194 auto fl = g.fluid;
195 Kokkos::parallel_for(
196 "amr::flow_grad3", g.n, KOKKOS_LAMBDA(const Index i) {
197 if (!fl(i)) {
198 gx(i) = gy(i) = gz(i) = 0.0;
199 return;
200 }
201 const double fi = f(i);
202 double gp[3] = {0, 0, 0}, gm[3] = {0, 0, 0};
203 int np[3] = {0, 0, 0}, nm[3] = {0, 0, 0};
204 for (Index k = st(i); k < st(i + 1); ++k) {
205 if (al(k) <= 1e-12)
206 continue;
207 const int a = ax(k);
208 const double gg = (dr(k) > 0) ? (f(nb(k)) - fi) / di(k) : (fi - f(nb(k))) / di(k);
209 if (dr(k) > 0) {
210 gp[a] += gg;
211 ++np[a];
212 } else {
213 gm[a] += gg;
214 ++nm[a];
215 }
216 }
217 double out[3];
218 for (int a = 0; a < 3; ++a) {
219 const double a1 = np[a] ? gp[a] / np[a] : 0.0;
220 const double a2 = nm[a] ? gm[a] / nm[a] : 0.0;
221 out[a] = 0.5 * (a1 + a2);
222 }
223 gx(i) = out[0];
224 gy(i) = out[1];
225 gz(i) = out[2];
226 });
227}
228
234 View<const double> rscale, View<const char> fluid, double idiag, double fc,
235 View<double> b, Index n) {
236 Kokkos::parallel_for(
237 "amr::flow_momrhs", n, KOKKOS_LAMBDA(const Index i) {
238 b(i) = fluid(i) ? (idiag * uc(i) + fc - gradP(i) - adv(i)) * rscale(i) : 0.0;
239 });
240}
241
253 bool useFace) {
254 auto st = g.start;
255 auto nb = g.nbr;
256 auto ax = g.axis;
257 auto dr = g.dir;
258 auto ra = g.rawArea;
259 auto iv = g.invVol;
260 auto fl = g.fluid;
261 Kokkos::parallel_for(
262 "amr::flow_buildfou", g.n, KOKKOS_LAMBDA(const Index i) {
263 if (!fl(i)) {
264 advDiag(i) = 0.0;
265 for (Index k = st(i); k < st(i + 1); ++k)
266 advCoef(k) = 0.0;
267 return;
268 }
269 const double rs = rscale(i);
270 double dsum = 0.0;
271 for (Index k = st(i); k < st(i + 1); ++k) {
272 const Index j = nb(k);
273 if (!fl(j)) {
274 advCoef(k) = 0.0;
275 continue;
276 }
277 const int a = ax(k);
278 const double ui = (a == 0) ? u0(i) : (a == 1) ? u1(i) : u2(i);
279 const double uj = (a == 0) ? u0(j) : (a == 1) ? u1(j) : u2(j);
280 const double velOut = useFace ? dr(k) * uf(k) : dr(k) * 0.5 * (ui + uj);
281 const double w = rs * rho * ra(k) * velOut * iv(i);
282 if (velOut < 0.0) {
283 advCoef(k) = w; // inflow → off-diagonal toward upstream neighbour j
284 } else {
285 advCoef(k) = 0.0;
286 dsum += w; // outflow → diagonal
287 }
288 }
289 advDiag(i) = dsum;
290 });
291}
292
301 View<const double> u2, int comp, double rho, int advScheme,
303 auto st = g.start;
304 auto nb = g.nbr;
305 auto ax = g.axis;
306 auto dr = g.dir;
307 auto ra = g.rawArea;
308 auto iv = g.invVol;
309 auto fl = g.fluid;
310 auto uiP = g.upupI;
311 auto ujP = g.upupJ;
312 Kokkos::parallel_for(
313 "amr::flow_defsou", g.n, KOKKOS_LAMBDA(const Index i) {
314 if (!fl(i)) {
315 defc(i) = 0.0;
316 return;
317 }
318 auto fld = [&](Index c) { return (comp == 0) ? u0(c) : (comp == 1) ? u1(c) : u2(c); };
319 double sou = 0.0, fou = 0.0;
320 for (Index k = st(i); k < st(i + 1); ++k) {
321 const Index j = nb(k);
322 if (!fl(j))
323 continue;
324 const int a = ax(k);
325 const double uai = (a == 0) ? u0(i) : (a == 1) ? u1(i) : u2(i);
326 const double uaj = (a == 0) ? u0(j) : (a == 1) ? u1(j) : u2(j);
327 const double velOut = useFace ? dr(k) * uf(k) : dr(k) * 0.5 * (uai + uaj);
328 const Index up = (velOut > 0.0) ? i : j;
329 const Index down = (velOut > 0.0) ? j : i;
330 const Index upup = (velOut > 0.0) ? uiP(k) : ujP(k);
331 const double phiUp = fld(up);
332 const double phiUpUp = (upup >= 0 && fl(upup)) ? fld(upup) : phiUp;
333 const double phiDown = fld(down);
334 const double phiFace = hoFaceValue(phiUpUp, phiUp, phiDown, advScheme); // shared recon
335 sou += ra(k) * velOut * phiFace;
336 fou += ra(k) * velOut * fld(up); // FOU flux = velOut · upwind value
337 }
338 defc(i) = rho * iv(i) * (sou - fou); // ρ·(SOU − FOU), unscaled
339 });
340}
341
345 View<const double> u2, int comp, double rho, int advScheme,
347 auto st = g.start;
348 auto nb = g.nbr;
349 auto ax = g.axis;
350 auto dr = g.dir;
351 auto ra = g.rawArea;
352 auto iv = g.invVol;
353 auto fl = g.fluid;
354 auto uiP = g.upupI;
355 auto ujP = g.upupJ;
356 Kokkos::parallel_for(
357 "amr::flow_advexpl", g.n, KOKKOS_LAMBDA(const Index i) {
358 if (!fl(i)) {
359 defc(i) = 0.0;
360 return;
361 }
362 auto fld = [&](Index c) { return (comp == 0) ? u0(c) : (comp == 1) ? u1(c) : u2(c); };
363 double sou = 0.0;
364 for (Index k = st(i); k < st(i + 1); ++k) {
365 const Index j = nb(k);
366 if (!fl(j))
367 continue;
368 const int a = ax(k);
369 const double uai = (a == 0) ? u0(i) : (a == 1) ? u1(i) : u2(i);
370 const double uaj = (a == 0) ? u0(j) : (a == 1) ? u1(j) : u2(j);
371 const double velOut = useFace ? dr(k) * uf(k) : dr(k) * 0.5 * (uai + uaj);
372 const Index up = (velOut > 0.0) ? i : j;
373 const Index down = (velOut > 0.0) ? j : i;
374 const Index upup = (velOut > 0.0) ? uiP(k) : ujP(k);
375 const double phiUp = fld(up);
376 const double phiUpUp = (upup >= 0 && fl(upup)) ? fld(upup) : phiUp;
377 const double phiDown = fld(down);
378 const double phiFace = hoFaceValue(phiUpUp, phiUp, phiDown, advScheme); // shared recon
379 sou += ra(k) * velOut * phiFace;
380 }
381 defc(i) = rho * sou * iv(i); // ρ·SOU (fully explicit)
382 });
383}
384
387 Kokkos::parallel_for(
388 "amr::flow_correct", n, KOKKOS_LAMBDA(const Index i) {
389 if (fluid(i))
390 uc(i) -= gphi(i);
391 });
392}
393
396 View<const char> fluid, double rho_dt, double mu, Index n) {
397 Kokkos::parallel_for(
398 "amr::flow_presupd", n, KOKKOS_LAMBDA(const Index i) {
399 if (fluid(i))
400 p(i) += rho_dt * phi(i) - mu * div(i);
401 });
402}
403
404// ===========================================================================
405// AmrFlow — collocated Stokes projection step, fully on device.
406// ===========================================================================
407template <unsigned Bits = 21u>
408class AmrFlow {
409 public:
411
412 void init(const Octree& t, Real h0, Vec<3> origin = Vec<3>{}) {
413 t_ = &t;
414 h0_ = h0;
415 origin_ = origin;
416 }
417 void setDensity(double rho) { rho_ = rho; }
418 void setViscosity(double mu) { mu_ = mu; }
419 void setDt(double dt) { dt_ = dt; }
420 void setBodyForce(double fx, double fy, double fz) { f_ = {fx, fy, fz}; }
422 void setPressurePCG(bool on) { presPCG_ = on; }
429 void setAdvection(bool on) { advect_ = on; }
432 void setImplicitAdvection(bool on) { implicitFou_ = on; }
434 void setAdvectionScheme(int s) { advScheme_ = s; }
444 void setMomentumTol(double tol) { momTol_ = tol; }
452 void setMomentumMG(bool on) { momMGon_ = on; }
453
458 void setVelocityMGStaircase(bool on) { useStaircaseMG_ = on; }
463 void setVelocityMGMinCoarse(Index m) { mgMinCoarse_ = m; }
467 void setMomentumGS(bool on) { momGS_ = on; }
468
481 void setMomentumMGSolver(bool on) { momMGSolver_ = on; }
482
488 void setOuterIterations(int n, double tol = 1e-6) {
489 outerIters_ = (n < 1) ? 1 : n;
490 outerTol_ = tol;
491 }
492
495 template <class SdfFn>
497 const Index n = t_->numLeaves();
498 // Host operator build (geometry, openness, cut stencils) — same as oracle::AmrFlow::setSolid.
499 mom_.init(*t_, h0_, origin_);
500 mom_.build(sdfFn, /*idiag=*/rho_ / dt_, /*beta=*/mu_ / (h0_ * h0_));
501 pres_.init(*t_, h0_);
502 pres_.setOrigin(origin_);
503 auto openFn = [&](const Vec<3>& fc, int axis) { return faceFrac(sdfFn, fc, axis); };
504 pres_.buildOpenness(openFn);
505 presMG_.build(*t_, h0_, openFn, /*periodic=*/true);
506 presMG_.setRemoveMean(true); // singular periodic pressure: per-level nullspace projection
507
508 // Device assembly (D6): the static cut-cell momentum operator CSR and the collocated face
509 // geometry are assembled ON THE DEVICE (assembleMomentum / assembleFaceGeom), and the
510 // pressure MG operators are device-assembled per level (Multigrid D5) — so no host CSR walk and
511 // no operator round-trip. Each is bit-for-bit identical to the host assembler on OpenMP (locked
512 // in test_amr_momentum / test_amr_facegeom), so the flow result is unchanged. A shared device
513 // octree view backs both assemblers.
515 ov.upload(*t_);
516 momOp_ = assembleMomentum<Bits>(mom_, ov); // static Stokes operator (hasAdv stays false)
517 // Velocity multigrid (momentum preconditioner): the Galerkin hierarchy A_c = R·A·P built
518 // directly from the exact assembled momentum CSR. Consistent with the fine cut-cell
519 // operator by construction (inherits the ξ-overlay + D_rescale row scaling; a coarse cell
520 // of all-solid children stays an identity row). It only changes the preconditioner (the
521 // BiCGStab matvec is the exact operator) ⇒ same converged solution, but the iteration
522 // count stays ~flat with N instead of growing like the Jacobi-preconditioned BiCGStab.
523 // Build the chosen momentum-MG: Galerkin (MomentumMG) by default, or the rediscretized
524 // staircase (VelocityMG) — both from the static Stokes operator, once.
525 if (momMGon_) {
526 if (useStaircaseMG_) {
527 std::vector<double> kap(static_cast<std::size_t>(n));
528 std::vector<char> fl(static_cast<std::size_t>(n)), cu(static_cast<std::size_t>(n));
529 for (Index i = 0; i < n; ++i) {
530 kap[static_cast<std::size_t>(i)] = mom_.kappa(i);
531 fl[static_cast<std::size_t>(i)] = mom_.isFluid(i) ? 1 : 0;
532 cu[static_cast<std::size_t>(i)] = mom_.isCut(i) ? 1 : 0;
533 }
534 velMG_.build(*t_, h0_, rho_ / dt_, mu_, momOp_, kap, fl, cu, mgMinCoarse_);
535 velMG_.setGaussSeidel(momGS_);
536 } else {
537 // The Galerkin RAP hierarchy is a host triple-product over the fine CSR, so it needs the
538 // operator on the host; assemble it there for the MG build only (bit-identical to momOp_).
539 auto A = mom_.assembleOperator();
540 momMG_.build(*t_, A.diag, A.start, A.nbr, A.coef);
541 momMG_.setGaussSeidel(momGS_);
542 }
543 }
544 std::vector<char> fluidVec(static_cast<std::size_t>(n));
545 for (Index i = 0; i < n; ++i)
546 fluidVec[static_cast<std::size_t>(i)] = mom_.isFluid(i) ? 1 : 0;
547 geom_ = assembleFaceGeom<Bits>(pres_, fluidVec, ov);
548 std::vector<double> rs(static_cast<std::size_t>(n));
549 for (Index i = 0; i < n; ++i)
550 rs[static_cast<std::size_t>(i)] = mom_.rhsScale(i);
551 rscale_ = toDevice(rs, "df_rscale");
552 fluid_ = geom_.fluid;
553
554 // Device state.
555 for (int c = 0; c < 3; ++c) {
556 u_[c] = View<double>("df_u", static_cast<std::size_t>(n));
557 gx_[c] = View<double>("df_g", static_cast<std::size_t>(n));
558 Kokkos::deep_copy(u_[c], 0.0);
559 }
560 p_ = View<double>("df_p", static_cast<std::size_t>(n));
561 phi_ = View<double>("df_phi", static_cast<std::size_t>(n));
562 div_ = View<double>("df_div", static_cast<std::size_t>(n));
563 uf_ = View<double>("df_uf",
564 geom_.nbr.extent(0)); // ABC divergence-free face field (per CSR face)
565 faceFieldBuilt_ = false;
566 bmom_ = View<double>("df_bmom", static_cast<std::size_t>(n));
567 Kokkos::deep_copy(p_, 0.0);
568 // Implicit-FOU advection state. The momentum operator + its velocity-MG are rebuilt each
569 // step from the *full* operator (viscous + FOU) so the MG is advection-aware (the viscous-
570 // only MG diverges on the advection operator at cut cells); the FOU is baked into the CSR
571 // (momOp_.hasAdv stays false). defc holds the device-computed explicit ρ(SOU−FOU)
572 // deferred correction. uadvHost_ caches u^n on the host for the per-step operator rebuild.
573 for (int c = 0; c < 3; ++c) {
574 defc_[c] = View<double>("df_defc", static_cast<std::size_t>(n));
575 Kokkos::deep_copy(defc_[c], 0.0);
576 // uⁿ snapshot for the backward-Euler mass term (frozen across Picard outer iters) + the
577 // previous outer iterate for the outer-loop convergence test.
578 u0_[c] = View<double>("df_u0", static_cast<std::size_t>(n));
579 uprev_[c] = View<double>("df_uprev", static_cast<std::size_t>(n));
580 }
581 // Device-resident implicit-FOU advection: the FOU operator (advDiag + per-face advCoef over the
582 // face-geometry CSR) is rebuilt on device each step from uⁿ and added to the static Stokes
583 // operator in the matvec (no host round-trip). The static operator + Galerkin velocity-MG are
584 // built once (above); the advection is a perturbation the static MG still preconditions.
585 advDiag_ = View<double>("df_advdiag", static_cast<std::size_t>(n));
586 advCoef_ = View<double>("df_advcoef", geom_.nbr.extent(0));
587 momOp_.advStart = geom_.start;
588 momOp_.advNbr = geom_.nbr;
589 momOp_.advDiag = advDiag_;
590 momOp_.advCoef = advCoef_;
591 momOp_.hasAdv = false; // set per-step when advection is on
592 momSolver_.setJacobi(2, 0.7);
593 // Generic MG preconditioner: dispatch the chosen hierarchy's V-cycle (z = M⁻¹ r), decoupling
594 // the solver from the MG type so Galerkin and staircase are interchangeable.
595 if (momMGon_) {
596 if (useStaircaseMG_)
597 momSolver_.setPreconditioner(
598 [this](View<const double> r, View<double> z) { runMgVcycle(velMG_, r, z); });
599 else
600 momSolver_.setPreconditioner(
601 [this](View<const double> r, View<double> z) { runMgVcycle(momMG_, r, z); });
602 }
603 pcg_.setVcycle(2, 2, 60, 0.8);
604 pcg_.setSingular(true);
605 n_ = n;
606 }
607
611 void step(int momIters = 100, int presIters = 60) {
612 const Index n = n_;
613 const double idiag = rho_ / dt_;
614 lastMomIters_ = 0;
615 lastOuterIters_ = 1;
616 // Freeze the time-level uⁿ for the backward-Euler mass term + warm start; it stays anchored
617 // across the Picard outer iterations (only the advecting velocity re-lags). For outerIters_==1
618 // this is just a copy of uⁿ ⇒ bit-identical to the single lagged step.
619 for (int c = 0; c < 3; ++c)
620 Kokkos::deep_copy(u0_[c], View<const double>(u_[c]));
621 // −∇p^n is constant across the outer iterations (pressure is projected once, after the loop,
622 // like flow's single per-step projection) ⇒ hoist it out.
623 grad3(geom_, View<const double>(p_), gx_[0], gx_[1], gx_[2]);
624 // Picard outer loop over the lagged advection only (the momentum nonlinearity); for
625 // outerIters_==1 this is the single lagged predictor, then one projection — bit-identical to
626 // before.
627 for (int outer = 0; outer < outerIters_; ++outer) {
628 // --- advection lagged to the *current* predictor iterate (uⁿ on the first pass):
629 // implicit-FOU operator + explicit ρ(SOU−FOU) deferred correction. The matvec stays linear
630 // during each solve (the advecting velocity is frozen in advDiag_/advCoef_). With advection
631 // OFF the operator and RHS are identical every pass, so a second pass reproduces the first ⇒
632 // instant early-stop. ---
633 if (advect_) {
634 momOp_.hasAdv = implicitFou_;
635 // Advect with the divergence-free face field uf (from the previous projection); fall back
636 // to ½(u_i+u_j) before the first projection has built it. The implicit FOU and the explicit
637 // SOU/FOU deferred correction use the SAME velocity ⇒ the FOU cancels at steady state
638 // (host-parity).
639 const View<const double> ufv(uf_);
640 if (implicitFou_)
641 buildFou(geom_, View<const double>(u_[0]), View<const double>(u_[1]),
642 View<const double>(u_[2]), rho_, View<const double>(rscale_), advDiag_, advCoef_,
643 ufv, faceFieldBuilt_);
644 for (int c = 0; c < 3; ++c) {
645 if (implicitFou_)
647 View<const double>(u_[2]), c, rho_, advScheme_, defc_[c], ufv,
648 faceFieldBuilt_);
649 else
651 View<const double>(u_[2]), c, rho_, advScheme_, defc_[c], ufv,
652 faceFieldBuilt_);
653 }
654 // The staircase MG's fine level mirrors the sharp operator; refresh it so it picks up the
655 // current advection state (hasAdv). (The Galerkin MG is the static viscous operator.)
656 if (momMGon_ && useStaircaseMG_)
657 velMG_.setFineOp(momOp_);
658 }
659 // --- predictor: incremental BE viscous (+ implicit-FOU) solve per component, RHS carries
660 // −∇p^n and −ρ(SOU−FOU); the mass term is anchored at uⁿ (u0_), the solve warm-starts at the
661 // current iterate. ---
662 for (int c = 0; c < 3; ++c) {
664 View<const double>(rscale_), View<const char>(fluid_), idiag, f_[c], bmom_, n);
665 // P4 (opt-in): the velocity-MG used as the *solver* — MG-preconditioned defect correction,
666 // no Krylov (the flow RB-GS/velocity-MG mirror; cannot break down on the non-symmetric
667 // operator) — vs the default MG-preconditioned BiCGStab. Both reach the same solution (the
668 // matvec is the exact operator); the choice only trades robustness for convergence rate.
669 lastMomIters_ +=
670 (momMGSolver_ ? momSolver_.solveDefectCorrection(
671 momOp_, u_[c], View<const double>(bmom_), momIters, momTol_)
672 : momSolver_.solveBiCGStab(momOp_, u_[c], View<const double>(bmom_),
673 momIters, momTol_))
674 .iters;
675 }
676 // Outer-loop convergence on the predictor velocity (skipped for the default outerIters_==1,
677 // so that path is untouched). With advection off the second iterate equals the first ⇒ stops
678 // at 2.
679 if (outerIters_ > 1) {
680 lastOuterIters_ = outer + 1;
681 if (outer > 0) {
682 double dmax = 0.0;
683 for (int c = 0; c < 3; ++c)
684 dmax = std::max(
686 if (dmax < outerTol_)
687 break;
688 }
689 for (int c = 0; c < 3; ++c)
690 Kokkos::deep_copy(uprev_[c], View<const double>(u_[c]));
691 }
692 }
693 project(presIters); // single pressure projection per step (flow structure)
694 }
695
697 void project(int presIters = 60) {
698 const Index n = n_;
700 View<const double>(u_[2]), div_);
701 Kokkos::deep_copy(phi_, 0.0);
702 // Two selectable pressure drivers, like flow's CutcellMG (MG-PCG single-rank default,
703 // standalone V-cycle the robust multi-rank default): MG-PCG for the near-steady, divergence-
704 // compatible Stokes case (faster), and the stationary V-cycle for the large transient
705 // divergence of advection, which excites near-nullspace modes of the cut-cell openness Poisson
706 // (near-isolated fluid cells) that CG amplifies — the V-cycle is bounded. (Per-level mean
707 // removal is on for both; making MG-PCG robust enough to also cover advection needs flow's
708 // near-isolated-cell pinning/classification — a follow-up.)
709 if (presPCG_ && !advect_) {
710 lastPresIters_ = pcg_.solve(presMG_, phi_, View<const double>(div_), presIters, 1e-10).iters;
711 } else {
712 Kokkos::deep_copy(presMG_.b(0), div_);
713 Kokkos::deep_copy(presMG_.x(0), 0.0);
714 for (int it = 0; it < presIters; ++it)
715 presMG_.vcycle(2, 2, 60, 0.8);
716 Kokkos::deep_copy(phi_, presMG_.x(0));
717 lastPresIters_ = presIters;
718 }
719 // Build the divergence-free FACE field from u* (still in u_) + φ, before the cell correction.
721 View<const double>(u_[2]), View<const double>(phi_), uf_);
722 faceFieldBuilt_ = true;
723 grad3(geom_, View<const double>(phi_), gx_[0], gx_[1], gx_[2]);
724 for (int c = 0; c < 3; ++c)
725 correct(u_[c], View<const double>(gx_[c]), View<const char>(fluid_), n);
727 rho_ / dt_, mu_, n);
728 }
729
732 std::vector<double> debugSou(int comp) {
733 View<double> s("dbg_sou", static_cast<std::size_t>(n_));
735 View<const double>(u_[2]), comp, 1.0, advScheme_, s, View<const double>(uf_),
736 false);
737 std::vector<double> h(static_cast<std::size_t>(n_));
738 auto m = Kokkos::create_mirror_view(s);
739 Kokkos::deep_copy(m, s);
740 for (Index i = 0; i < n_; ++i)
741 h[static_cast<std::size_t>(i)] = m(i);
742 return h;
743 }
747 template <class MG>
749 Kokkos::deep_copy(mg.b(0), r);
750 Kokkos::deep_copy(mg.x(0), 0.0);
751 mg.vcycle(mgVcPre_, mgVcPre_, mgVcBottom_, 0.7);
752 Kokkos::deep_copy(z, mg.x(0));
753 }
756 double m = 0.0;
757 Kokkos::parallel_reduce(
758 "amr::flow_maxdiff", n,
759 KOKKOS_LAMBDA(const Index i, double& lm) {
760 double d = a(i) - b(i);
761 if (d < 0.0)
762 d = -d;
763 if (d > lm)
764 lm = d;
765 },
766 Kokkos::Max<double>(m));
767 return m;
768 }
770 void copyToHost(const View<double>& d, std::vector<double>& h) const {
771 auto m = Kokkos::create_mirror_view(d);
772 Kokkos::deep_copy(m, d);
773 for (Index i = 0; i < n_; ++i)
774 h[static_cast<std::size_t>(i)] = m(i);
775 }
777 void setVelocity(int c, const std::vector<double>& h) {
778 auto m = Kokkos::create_mirror_view(u_[c]);
779 for (Index i = 0; i < n_; ++i)
780 m(i) = h[static_cast<std::size_t>(i)];
781 Kokkos::deep_copy(u_[c], m);
782 }
784 std::vector<double> velocity(int c) const { return peclet::core::toVector(u_[c]); }
787 std::vector<double> pressure() const { return peclet::core::toVector(p_); }
788
793 std::vector<double> velocities() const {
794 View<double> packed(Kokkos::view_alloc("amr::vel_packed", Kokkos::WithoutInitializing),
795 static_cast<std::size_t>(n_) * 3);
796 for (int c = 0; c < 3; ++c) {
797 auto uc = u_[c];
798 auto p = packed;
799 const int cc = c;
800 Kokkos::parallel_for(
801 "amr::pack_vel", n_, KOKKOS_LAMBDA(const Index i) { p(i * 3 + cc) = uc(i); });
802 }
804 }
806 double divNormL2() {
808 View<const double>(u_[2]), div_);
809 return std::sqrt(dotPlain(View<const double>(div_), View<const double>(div_), n_));
810 }
813 double divNormFace() { return divFaceNorm(geom_, View<const double>(uf_)); }
816 std::vector<double> faceField() const { return peclet::core::toVector(uf_); }
817 Index numLeaves() const { return n_; }
819 bool isFluid(Index i) const { return mom_.isFluid(i); }
821 int lastMomIters() const { return lastMomIters_; }
823 int lastPresIters() const { return lastPresIters_; }
825 int lastOuterIters() const { return lastOuterIters_; }
826
827 private:
828 // flow ccFractionCore aperture (verbatim from oracle::AmrFlow::faceFrac).
829 template <class SdfFn>
830 double faceFrac(SdfFn&& sdfFn, const Vec<3>& fc, int axis) const {
831 double sd = sdfFn(fc);
832 if (sd <= 0.0)
833 return 0.0;
834 Vec<3> g{};
835 for (int d = 0; d < 3; ++d) {
836 Vec<3> pp = fc, pm = fc;
837 pp[d] += h0_;
838 pm[d] -= h0_;
839 g[d] = (sdfFn(pp) - sdfFn(pm)) / (2.0 * h0_);
840 }
841 double gmag = std::sqrt(g[0] * g[0] + g[1] * g[1] + g[2] * g[2]);
842 if (gmag < 1e-6)
843 gmag = 1e-6;
844 int t1 = (axis + 1) % 3, t2 = (axis + 2) % 3;
845 double denom = (std::fabs(g[t1]) + std::fabs(g[t2])) / gmag * h0_;
846 if (denom < 1e-9)
847 denom = 1e-9;
848 double frac = 0.5 + sd / denom;
849 return frac < 0.0 ? 0.0 : (frac > 1.0 ? 1.0 : frac);
850 }
851
852 const Octree* t_ = nullptr;
853 Real h0_ = 1.0;
854 Vec<3> origin_{};
855 double rho_ = 1.0, mu_ = 1.0, dt_ = 1e6;
856 Vec<3> f_{};
857 bool presPCG_ = true;
858 bool momMGon_ = true; // velocity-MG momentum preconditioner (scalable; see setMomentumMG)
859 bool useStaircaseMG_ = false; // false = Galerkin (MomentumMG), true = staircase (VelocityMG)
860 int mgVcPre_ = 2, mgVcBottom_ = 30; // momentum-MG V-cycle pre/post sweeps + bottom sweeps
861 Index mgMinCoarse_ = 256; // staircase velocity-MG pore-scale cap (coarsest cell count)
862 bool momGS_ = false; // opt-in: multicolour Gauss–Seidel smoother in the momentum MG
863 bool momMGSolver_ =
864 false; // opt-in (P4): velocity-MG as the solver (defect correction), not BiCGStab
865 int outerIters_ = 1; // Picard outer iterations over the lagged advection (default 1)
866 double outerTol_ = 1e-6; // outer-loop early-stop tolerance on max|Δu|
867 double momTol_ = 1e-8; // per-step momentum BiCGStab relative tolerance (Phase-0 knob)
868 bool advect_ = false; // momentum advection ∇·(u u) (off ⇒ Stokes)
869 bool implicitFou_ = true; // implicit-FOU deferred-correction (stable) vs fully-explicit
870 int advScheme_ = 0; // high-order flux: 0 = SOU (default), 1 = Koren TVD
871 Index n_ = 0;
872 int lastMomIters_ = 0, lastPresIters_ = 0, lastOuterIters_ = 1;
873
874 AmrCutCell<Bits> mom_;
875 AmrPoisson<3, Bits> pres_;
876 Multigrid<3, Bits> presMG_;
877 MomentumMG<Bits> momMG_; // Galerkin velocity multigrid (momentum preconditioner)
878 VelocityMG<Bits> velMG_; // rediscretized staircase velocity multigrid (alternative)
879 MomentumOp momOp_;
880 MomentumSolver<Bits> momSolver_;
881 PCG<3, Bits> pcg_;
882 std::array<View<double>, 3> defc_; // explicit ρ(SOU−FOU) deferred correction per component
883 View<double> advDiag_, advCoef_; // device-resident implicit-FOU operator (rebuilt each step)
884 FaceGeom geom_;
885 View<double> rscale_;
886 View<char> fluid_;
887 std::array<View<double>, 3> u_, gx_;
888 std::array<View<double>, 3> u0_,
889 uprev_; // frozen uⁿ (BE mass term) + previous Picard outer iterate
890 View<double> p_, phi_, div_, bmom_;
891 View<double> uf_; // ABC/Basilisk divergence-free face field (one per CSR (sub)face)
892 bool faceFieldBuilt_ =
893 false; // uf_ populated by a projection (else advection falls back to ½(u_i+u_j))
894};
895
896} // namespace peclet::core::amr
897
898#endif // PECLET_CORE_HAVE_MORTON
899#endif // PECLET_CORE_AMR_FLOW_HPP
void setMomentumGS(bool on)
Opt-in: use the multicolour Gauss–Seidel smoother in the momentum MG (Galerkin or staircase) instead ...
Definition flow.hpp:467
void setViscosity(double mu)
Definition flow.hpp:418
void runMgVcycle(MG &mg, View< const double > r, View< double > z)
Run one V-cycle of a momentum MG as a preconditioner: z = M⁻¹ r.
Definition flow.hpp:748
std::vector< double > faceField() const
Copy the divergence-free face field to host (one value per CSR (sub)face, forEachFaceFull order).
Definition flow.hpp:816
void setVelocityMGStaircase(bool on)
Choose the momentum-MG coarse-operator strategy: false (default) = Galerkin (MomentumMG,...
Definition flow.hpp:458
void setAdvectionScheme(int s)
High-order advection scheme: 0 = second-order upwind (SOU, default), 1 = Koren TVD.
Definition flow.hpp:434
std::vector< double > pressure() const
Copy the pressure field back to host (single D2H), (num_leaves,) — the incremental-rotational p.
Definition flow.hpp:787
void copyToHost(const View< double > &d, std::vector< double > &h) const
Copy a device View into a host vector (sized n_).
Definition flow.hpp:770
void setMomentumMG(bool on)
Use the Galerkin velocity multigrid (MomentumMG) as the momentum BiCGStab preconditioner.
Definition flow.hpp:452
std::vector< double > velocity(int c) const
Copy a velocity component back to host (single D2H, no host loop — S2a).
Definition flow.hpp:784
BlockOctree< 3, Bits > Octree
Definition flow.hpp:410
void setVelocity(int c, const std::vector< double > &h)
Set a velocity component from host (testing / initial conditions).
Definition flow.hpp:777
std::vector< double > velocities() const
All three velocity components interleaved as a flat (n,3) row-major host buffer (out[i*3+c]) with a s...
Definition flow.hpp:793
void setBodyForce(double fx, double fy, double fz)
Definition flow.hpp:420
double divNormFace()
L2 norm of the divergence of the ABC face field uf_ (built each project()): the φ-solve residual,...
Definition flow.hpp:813
void setMomentumTol(double tol)
Relative tolerance for the per-step momentum BiCGStab solve (default 1e-8).
Definition flow.hpp:444
bool isFluid(Index i) const
Per-leaf fluid mask (false inside the solid) — for host-side post-processing / bindings.
Definition flow.hpp:819
std::vector< double > debugSou(int comp)
DEBUG: the raw high-order advection ∇·(u u_comp) per cell from the current velocity (== host oracle::...
Definition flow.hpp:732
Index numLeaves() const
Definition flow.hpp:817
void setVelocityMGMinCoarse(Index m)
Pore-scale cap for the staircase velocity-MG: the coarsest level keeps ≥ this many cells,...
Definition flow.hpp:463
static double maxAbsDiff(View< const double > a, View< const double > b, Index n)
Max |a − b| over all cells (the Picard outer-loop convergence measure).
Definition flow.hpp:755
int lastMomIters() const
Total momentum BiCGStab iterations (summed over the 3 components) of the last step.
Definition flow.hpp:821
double divNormL2()
L2 norm of the (openness-weighted) divergence of the current velocity.
Definition flow.hpp:806
void setMomentumMGSolver(bool on)
Opt-in (P4): solve the momentum predictor with the velocity multigrid used as the solver — MG-precond...
Definition flow.hpp:481
void setDensity(double rho)
Definition flow.hpp:417
void project(int presIters=60)
Pressure projection of the current velocity in place.
Definition flow.hpp:697
void setOuterIterations(int n, double tol=1e-6)
Optional Picard outer loop over the lagged advection (mirror of flow's outerIters_): each outer itera...
Definition flow.hpp:488
void setImplicitAdvection(bool on)
Implicit-FOU deferred correction (default ON).
Definition flow.hpp:432
void init(const Octree &t, Real h0, Vec< 3 > origin=Vec< 3 >{})
Definition flow.hpp:412
int lastPresIters() const
Pressure PCG iterations of the last step.
Definition flow.hpp:823
void setSolid(SdfFn &&sdfFn)
Build the cut-cell operators (host) + upload all device structures.
Definition flow.hpp:496
void step(int momIters=100, int presIters=60)
One incompressible step on device (Stokes, or Navier–Stokes with setAdvection).
Definition flow.hpp:611
void setPressurePCG(bool on)
Use MG-preconditioned CG for the pressure solve (default) vs plain V-cycles.
Definition flow.hpp:422
void setDt(double dt)
Definition flow.hpp:419
int lastOuterIters() const
Picard outer iterations actually run in the last step (1 unless setOuterIterations(>1)).
Definition flow.hpp:825
void setAdvection(bool on)
Enable momentum advection ∇·(u u) (default OFF ⇒ Stokes).
Definition flow.hpp:429
void init(const Octree &t, Real h0)
Definition poisson.hpp:53
void setOrigin(const Vec< Dim > &o)
Definition poisson.hpp:62
void buildOpenness(OpenFn &&openFn)
Build face openness from a geometry callable openFn(faceCentreWorld, axis) -> [0,1] (1 = fully fluid,...
Definition poisson.hpp:86
void presUpdate(View< double > p, View< const double > phi, View< const double > div, View< const char > fluid, double rho_dt, double mu, Index n)
Rotational incremental pressure update: p += (ρ/dt)φ − μ·div, on fluid cells.
Definition flow.hpp:395
double divFaceNorm(const FaceGeom &g, View< const double > uf)
L2 norm of the divergence of the FACE field uf (the div-free flux diagnostic / host-parity check).
Definition flow.hpp:161
void deferredSou(const FaceGeom &g, View< const double > u0, View< const double > u1, View< const double > u2, int comp, double rho, int advScheme, View< double > defc, View< const double > uf, bool useFace)
Deferred-correction advection term for component comp: defc = ρ·SOU − ρ·FOU (UNSCALED; the predictor ...
Definition flow.hpp:300
void advectExplicit(const FaceGeom &g, View< const double > u0, View< const double > u1, View< const double > u2, int comp, double rho, int advScheme, View< double > defc, View< const double > uf, bool useFace)
Fully-explicit high-order advection for component comp: defc = ρ·SOU (no implicit FOU; the setImplici...
Definition flow.hpp:344
void correct(View< double > uc, View< const double > gphi, View< const char > fluid, Index n)
u_c -= gradPhi_c on fluid cells (the projection velocity correction).
Definition flow.hpp:386
void buildFaceField(const FaceGeom &g, View< const double > u0, View< const double > u1, View< const double > u2, View< const double > phi, View< double > uf)
Build the ABC/Basilisk divergence-free FACE field: uf(k) = ½(u^axis_i+u^axis_j) − (φ₊−φ₋)/dist for fa...
Definition flow.hpp:139
void momRhs(View< const double > uc, View< const double > gradP, View< const double > adv, View< const double > rscale, View< const char > fluid, double idiag, double fc, View< double > b, Index n)
Momentum RHS for one component: b_i = fluid ? (idiag·u_i + f_c − gradP_i − adv_i)·rscale_i : 0 (== Am...
Definition flow.hpp:233
void buildFou(const FaceGeom &g, View< const double > u0, View< const double > u1, View< const double > u2, double rho, View< const double > rscale, View< double > advDiag, View< double > advCoef, View< const double > uf, bool useFace)
Build the implicit-FOU advection operator from the lagged velocity u0..2 (uⁿ) entirely on device,...
Definition flow.hpp:250
void grad3(const FaceGeom &g, View< const double > f, View< double > gx, View< double > gy, View< double > gz)
ABC cell-gradient of a scalar field f: gx/gy/gz = ½(g⁻+g⁺) of the adjacent face gradients along each ...
Definition flow.hpp:186
double dotPlain(View< const double > a, View< const double > b, Index n)
Plain (unweighted) dot product.
Definition momentum.hpp:109
FaceGeom buildFaceGeom(const AmrPoisson< Dim, Bits > &ap, FluidFn &&isFluid)
Build FaceGeom from a built AmrPoisson (openness set) + a fluid predicate.
Definition flow.hpp:51
void divergence(const FaceGeom &g, View< const double > u0, View< const double > u1, View< const double > u2, View< double > div)
Openness-weighted FV divergence: div_i = invVol_i Σ_faces α·area·dir·½(u^axis_i+u^axis_j),...
Definition flow.hpp:106
MORTON_HD double hoFaceValue(double upup, double up, double down, int scheme)
High-order advected face value from the two upwind cells (upup, up) and the downwind cell (down).
View< T > toDevice(const std::vector< T > &h, const std::string &label)
Upload a host std::vector into a freshly-sized device View (empty vector => empty view).
Definition view.hpp:44
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
double Real
Default host floating type. Device kernels may use float; conversions happen at the boundary.
Definition types.hpp:18
std::vector< std::remove_const_t< typename V::value_type > > toVector(const V &view)
Copy a (host- or device-resident) Kokkos View of any rank into a contiguous host std::vector,...
Definition view.hpp:63
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15
void upload(const Host &t)
(Re)upload the host octree's current leaf set to the device.
View< Index > upupI
upstream-of-i probe (periodicNeighbor(i,axis,−dir)) — SOU, size nFaces
Definition face_geom.hpp:29
View< double > dist
face-normal distance (physical) per face, size nFaces
Definition face_geom.hpp:27
View< Index > upupJ
upstream-of-j probe (periodicNeighbor(j,axis,+dir)) — SOU, size nFaces
Definition face_geom.hpp:30
View< double > rawArea
raw face area (physical, no openness) per face — advection flux
Definition face_geom.hpp:26
View< int > axis
face axis 0/1/2, size nFaces
Definition face_geom.hpp:23
View< char > fluid
per-cell fluid flag, size n
Definition face_geom.hpp:32
View< double > alphaArea
α·area (physical) per face, size nFaces
Definition face_geom.hpp:25
View< Index > start
CSR row offsets, size n+1.
Definition face_geom.hpp:21
View< double > invVol
1/V_i per cell, size n
Definition face_geom.hpp:31
View< double > alpha
openness per face (gradient gate), size nFaces
Definition face_geom.hpp:28
View< Index > nbr
neighbour leaf per face, size nFaces
Definition face_geom.hpp:22
View< int > dir
face direction +1/-1, size nFaces
Definition face_geom.hpp:24
View< Index > advStart
face-geom CSR row offsets, size n+1
Definition momentum.hpp:52
View< Index > advNbr
face-geom neighbour per face, size nFaces
Definition momentum.hpp:53
View< double > advDiag
per-cell outflow (diagonal) advection weight, size n
Definition momentum.hpp:51
View< double > advCoef
per-face inflow advection coefficient (0 on outflow/solid faces)
Definition momentum.hpp:54