core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
velocity_mg.hpp
Go to the documentation of this file.
1// core — device (Kokkos) REDISCRETIZED velocity multigrid for the AMR momentum solve.
2//
3// The rediscretized counterpart of the Galerkin MomentumMG, mirroring flow's VelocityMG
4// (mac_velocity_mg.hpp) on the octree. The fine level is the *sharp* cut-cell operator (the
5// assembled FaceCsrOp = MomentumOp the BiCGStab matvec uses); the coarse levels are a
6// **staircase rediscretization** of the geometry rather than a Galerkin R·A·P of the fine
7// operator:
8// * coarsen the cell fluid-fraction κ to each level (average of children);
9// * classify κ < ½ → solid (identity row, pinned to 0 by the smoother), else fluid;
10// * a fluid cell is the per-axis const-coeff Helmholtz diag = ρ/dt + 6μ/H², off = −μ/H² to
11// each of its 6 (periodic) face neighbours (H = level cell width) — a face into a classified
12// solid cell is a no-slip wall implicitly (the neighbour is pinned to 0). This is
13// ε-solid-on-coarse for free, exactly as flow's buildVelocityStaircase.
14// Transfers: average restriction + masked piecewise-constant prolongation (no correction into a
15// solid fine cell). Smoother: weighted Jacobi (jacobiMom) — P5 swaps in multicolor-GS.
16//
17// Used as the momentum BiCGStab preconditioner exactly like MomentumMG; selectable so the
18// two coarse-operator strategies can be benchmarked head-to-head on the AMR. The implicit-FOU
19// advection on coarse levels (mirror buildUpwindCoarse) is a follow-up — the viscous staircase is
20// the diffusion preconditioner. Requires a Kokkos build + the morton checkout
21// (PECLET_CORE_HAVE_MORTON).
22#ifndef PECLET_CORE_AMR_VELOCITY_MG_HPP
23#define PECLET_CORE_AMR_VELOCITY_MG_HPP
24
25#ifdef PECLET_CORE_HAVE_MORTON
26
27#include <algorithm>
28#include <cmath>
29#include <memory>
30#include <utility>
31#include <vector>
32
37
38namespace peclet::core::amr {
39
40template <unsigned Bits = 21u>
42 public:
44 using M = typename Octree::M;
45 using Code = typename Octree::Code;
47
58 void build(const Octree& finest, double h0, double idiag, double mu, const MomentumOp& fineOp,
59 const std::vector<double>& kappa, const std::vector<char>& fluid,
60 const std::vector<char>& cut, Index minCoarse = 256) {
61 hmg_ = std::make_unique<AmrMultigrid<3, Bits>>();
62 hmg_->build(finest, h0); // octree hierarchy + per-level AmrPoisson (periodicNeighbor etc.)
63 std::size_t nl = hmg_->numLevels();
64 while (nl > 1 && hmg_->op(nl - 1).octree().numLeaves() < minCoarse)
65 --nl; // pore-scale cap
66 levels_.clear();
67 levels_.resize(nl);
68
69 // Per-level κ (level 0 = fine; coarser = average of children) for the staircase classification.
70 std::vector<std::vector<double>> kap(nl);
71 kap[0] = kappa;
72 for (std::size_t L = 0; L + 1 < nl; ++L) {
73 const Octree& f = hmg_->op(L).octree();
74 const Octree& c = hmg_->op(L + 1).octree();
75 const Index nf = f.numLeaves(), nc = c.numLeaves();
76 std::vector<double> ks(static_cast<std::size_t>(nc), 0.0),
77 kn(static_cast<std::size_t>(nc), 0.0);
78 for (Index i = 0; i < nf; ++i) {
79 Code par = M::from_code(f.code(i)).ancestor(f.level(i) + 1).code();
80 Index p = c.find(par);
81 if (p >= 0) {
82 ks[static_cast<std::size_t>(p)] += kap[L][static_cast<std::size_t>(i)];
83 kn[static_cast<std::size_t>(p)] += 1.0;
84 }
85 }
86 for (Index p = 0; p < nc; ++p)
87 ks[static_cast<std::size_t>(p)] /=
88 (kn[static_cast<std::size_t>(p)] > 0 ? kn[static_cast<std::size_t>(p)] : 1.0);
89 kap[L + 1] = std::move(ks);
90 }
91
92 // Level 0: the sharp fine operator + the clean-fluid EXCLUDE mask (= cut OR solid). This is
93 // flow VelocityMG's Phase-3 fix (doc/velocity_mg_plan.md): the cut cells' D_rescale-scaled
94 // residuals and the solid cells are excluded from the coarse defect (zeroed before restriction
95 // + skipped in prolongation), so the inconsistent sharp-IBM rows never reach the coarse grid —
96 // the fine smoother owns the cut band, the coarse grid solves the clean interior. Without it
97 // the staircase V-cycle diverges at large dt.
98 levels_[0].op = fineOp;
99 {
100 std::vector<char> excl(fluid.size());
101 for (std::size_t i = 0; i < fluid.size(); ++i)
102 excl[i] = (!fluid[i] || cut[i]) ? 1 : 0;
103 levels_[0].solid = toDevice(excl, "vmg_excl0");
104 allocScratch(levels_[0], static_cast<Index>(fluid.size()));
105 // Colour the sharp fine operator (its face graph) for the optional GS smoother — copy the
106 // face CSR off the device once.
107 std::vector<Index> hs(static_cast<std::size_t>(fineOp.n) + 1), hn(fineOp.faceNbr.extent(0));
108 auto ms = Kokkos::create_mirror_view(fineOp.faceStart);
109 auto mn = Kokkos::create_mirror_view(fineOp.faceNbr);
110 Kokkos::deep_copy(ms, fineOp.faceStart);
111 Kokkos::deep_copy(mn, fineOp.faceNbr);
112 for (std::size_t i = 0; i < hs.size(); ++i)
113 hs[i] = ms(static_cast<Index>(i));
114 for (std::size_t i = 0; i < hn.size(); ++i)
115 hn[i] = mn(static_cast<Index>(i));
116 levels_[0].col = greedyColoring(hs, hn, fineOp.n);
117 }
118 // Coarse levels: rediscretized staircase operator + classified solid mask.
119 for (std::size_t L = 1; L < nl; ++L)
120 buildStaircase(L, idiag, mu, kap[L]);
121
122 // Transfer maps L → L+1 (child→parent + child CSR), as in MomentumMG.
123 for (std::size_t L = 0; L + 1 < nl; ++L)
124 buildTransfer(L);
125 }
126
127 std::size_t numLevels() const { return levels_.size(); }
128 Index numLeaves(std::size_t L = 0) const { return levels_[L].op.n; }
129 View<double> x(std::size_t L = 0) { return levels_[L].x; }
130 View<double> b(std::size_t L = 0) { return levels_[L].b; }
132 void setFineOp(const MomentumOp& fineOp) { levels_[0].op = fineOp; }
136 void setGaussSeidel(bool on) { useGS_ = on; }
137
141 void vcycle(int pre = 2, int post = 2, int bottom = 30, double omega = 0.7, std::size_t L = 0) {
142 Level& lv = levels_[L];
144 if (L + 1 == levels_.size()) {
145 smooth(lv, bottom, omega);
146 return;
147 }
148 smooth(lv, pre, omega);
149 residualMom(lv.op, View<const double>(lv.x), bc, lv.res);
150 // Clean-fluid exclude: zero the residual at cut/solid cells before restriction so the
151 // inconsistent sharp-IBM cut-cell residuals never pollute the coarse defect (the fix for the
152 // large-dt staircase divergence — flow VelocityMG mg_mul_mask).
153 zeroMasked(lv.res, View<const char>(lv.solid), lv.op.n);
154 Level& cl = levels_[L + 1];
155 restrictField(lv.childStart, lv.childIdx, View<const double>(lv.res), cl.b, cl.op.n);
156 Kokkos::deep_copy(cl.x, 0.0);
157 vcycle(pre, post, bottom, omega, L + 1);
159 smooth(lv, post, omega);
160 }
161
162 private:
163 struct Level {
164 MomentumOp op;
165 View<char> solid;
166 View<double> x, b, res, tmp;
167 View<Index> c2p, childStart, childIdx;
168 Coloring col;
169 };
170 void smooth(Level& lv, int sweeps, double omega) {
172 if (useGS_)
173 // Undamped GS (omega=1.0): stable on the staircase Helmholtz diagonal, and the passed omega
174 // is Jacobi's damping limit (~0.7) which would needlessly weaken the GS smoothing.
175 for (int s = 0; s < sweeps; ++s)
176 multicolorGSMom(lv.op, lv.x, bc, lv.col, 1.0);
177 else
178 for (int s = 0; s < sweeps; ++s)
179 jacobiMom(lv.op, lv.x, bc, lv.tmp, omega);
180 }
181
182 void allocScratch(Level& lv, Index n) {
183 lv.x = View<double>("vmg_x", static_cast<std::size_t>(n));
184 lv.b = View<double>("vmg_b", static_cast<std::size_t>(n));
185 lv.res = View<double>("vmg_res", static_cast<std::size_t>(n));
186 lv.tmp = View<double>("vmg_tmp", static_cast<std::size_t>(n));
187 }
188
189 // Rediscretized staircase operator on coarse level L from the coarsened κ.
190 void buildStaircase(std::size_t L, double idiag, double mu, const std::vector<double>& kap) {
191 const Poisson& ap = hmg_->op(L);
192 const Octree& oct = ap.octree();
193 const Index n = oct.numLeaves();
194 // Shift floor on the coarse reaction: at large dt (idiag→0) a small immersed object can vanish
195 // from the binary κ classification on the coarsest levels, leaving a singular periodic
196 // Laplacian whose bottom solve diverges. Flooring idiag to μ/L² (the slowest diffusion mode)
197 // keeps every coarse level non-singular (Galerkin avoids this by inheriting the sharp
198 // operator).
199 const double Ldom = ap.h0() * static_cast<double>(oct.brick()[0] * (Index(1) << oct.lmax()));
200 const double id = std::max(idiag, mu / (Ldom * Ldom));
201 std::vector<double> diag(static_cast<std::size_t>(n), 1.0);
202 std::vector<char> solid(static_cast<std::size_t>(n), 1);
203 std::vector<Index> start(static_cast<std::size_t>(n) + 1, 0);
204 std::vector<std::vector<std::pair<Index, double>>> rows(static_cast<std::size_t>(n));
205 for (Index i = 0; i < n; ++i) {
206 if (kap[static_cast<std::size_t>(i)] < 0.5)
207 continue; // classified solid: identity row
208 solid[static_cast<std::size_t>(i)] = 0;
209 const double H = ap.cellWidth(i); // h0·2^L (uniform per level)
210 const double coef = mu / (H * H); // μ·area/dist/V = μ/H² (isotropic coarse cell)
211 double dsum = id;
212 for (int axis = 0; axis < 3; ++axis)
213 for (int dir = -1; dir <= 1; dir += 2) {
214 Index j = ap.periodicNeighbor(i, axis, dir); // periodic wrap; same-level neighbour
215 if (j < 0)
216 continue; // domain boundary (non-periodic): wall
217 rows[static_cast<std::size_t>(i)].emplace_back(j, -coef);
218 dsum += coef; // every face counts toward the diagonal (a wall to a solid nbr too)
219 }
220 diag[static_cast<std::size_t>(i)] = dsum;
221 }
222 for (Index i = 0; i < n; ++i)
223 start[static_cast<std::size_t>(i) + 1] =
224 start[static_cast<std::size_t>(i)] +
225 static_cast<Index>(rows[static_cast<std::size_t>(i)].size());
226 const Index nnz = start[static_cast<std::size_t>(n)];
227 std::vector<Index> nbr(static_cast<std::size_t>(nnz));
228 std::vector<double> coef(static_cast<std::size_t>(nnz));
229 for (Index i = 0; i < n; ++i) {
230 Index k = start[static_cast<std::size_t>(i)];
231 for (auto& e : rows[static_cast<std::size_t>(i)]) {
232 nbr[static_cast<std::size_t>(k)] = e.first;
233 coef[static_cast<std::size_t>(k)] = e.second;
234 ++k;
235 }
236 }
237 Level& lv = levels_[L];
238 lv.op.n = n;
239 lv.op.diag = toDevice(diag, "vmg_diag");
240 lv.op.faceStart = toDevice(start, "vmg_fstart");
241 lv.op.faceNbr = toDevice(nbr, "vmg_fnbr");
242 lv.op.faceCoef = toDevice(coef, "vmg_fcoef");
243 lv.solid = toDevice(solid, "vmg_solid");
244 lv.col = greedyColoring(start, nbr, n); // for the optional GS smoother
245 allocScratch(lv, n);
246 }
247
248 void buildTransfer(std::size_t L) {
249 const Octree& f = hmg_->op(L).octree();
250 const Octree& c = hmg_->op(L + 1).octree();
251 const Index nf = f.numLeaves(), nc = c.numLeaves();
252 std::vector<Index> c2p(static_cast<std::size_t>(nf));
253 std::vector<Index> cnt(static_cast<std::size_t>(nc), 0);
254 for (Index i = 0; i < nf; ++i) {
255 Code par = M::from_code(f.code(i)).ancestor(f.level(i) + 1).code();
256 Index p = c.find(par);
257 c2p[static_cast<std::size_t>(i)] = p;
258 if (p >= 0)
259 ++cnt[static_cast<std::size_t>(p)];
260 }
261 std::vector<Index> cstart(static_cast<std::size_t>(nc) + 1, 0);
262 for (Index p = 0; p < nc; ++p)
263 cstart[static_cast<std::size_t>(p) + 1] =
264 cstart[static_cast<std::size_t>(p)] + cnt[static_cast<std::size_t>(p)];
265 std::vector<Index> cidx(static_cast<std::size_t>(nf));
266 std::vector<Index> cur(cstart.begin(), cstart.end() - 1);
267 for (Index i = 0; i < nf; ++i) {
268 Index p = c2p[static_cast<std::size_t>(i)];
269 if (p >= 0)
270 cidx[static_cast<std::size_t>(cur[static_cast<std::size_t>(p)]++)] = i;
271 }
272 levels_[L].c2p = toDevice(c2p, "vmg_c2p");
273 levels_[L].childStart = toDevice(cstart, "vmg_cstart");
274 levels_[L].childIdx = toDevice(cidx, "vmg_cidx");
275 }
276
277 std::unique_ptr<AmrMultigrid<3, Bits>> hmg_;
278 std::vector<Level> levels_;
279 bool useGS_ = false; // multicolour Gauss–Seidel smoother (opt-in; default weighted Jacobi)
280};
281
282} // namespace peclet::core::amr
283
284#endif // PECLET_CORE_HAVE_MORTON
285#endif // PECLET_CORE_AMR_VELOCITY_MG_HPP
morton::Morton< Dim, Bits > M
typename M::code_type Code
void setFineOp(const MomentumOp &fineOp)
Re-point level 0 at the (possibly FOU-updated) fine operator before a solve.
BlockOctree< 3, Bits > Octree
Index numLeaves(std::size_t L=0) const
View< double > b(std::size_t L=0)
void build(const Octree &finest, double h0, double idiag, double mu, const MomentumOp &fineOp, const std::vector< double > &kappa, const std::vector< char > &fluid, const std::vector< char > &cut, Index minCoarse=256)
Build the rediscretized hierarchy.
View< double > x(std::size_t L=0)
void setGaussSeidel(bool on)
Opt-in: use multicolour Gauss–Seidel as the smoother (per-level colouring) instead of Jacobi — the st...
void vcycle(int pre=2, int post=2, int bottom=30, double omega=0.7, std::size_t L=0)
One V-cycle (correction scheme) solving the fine operator.
AmrPoisson< 3, Bits > Poisson
typename Octree::Code Code
std::size_t numLevels() const
void residualMom(const MomentumOp &op, View< const double > u, View< const double > b, View< double > res)
res = b − A u.
Definition momentum.hpp:84
void multicolorGSMom(const MomentumOp &op, View< double > u, View< const double > b, const Coloring &col, double omega)
One symmetric multicolour Gauss–Seidel sweep of A u = b in place (momentum operator: diag + face CSR ...
Definition momentum.hpp:225
void zeroMasked(View< double > v, View< const char > excl, Index n)
Zero v at excluded cells (excl != 0).
Coloring greedyColoring(const std::vector< Index > &start, const std::vector< Index > &nbr, Index n)
Greedy colouring of the face CSR (start/nbr, host): each cell gets the smallest colour not used by an...
Definition momentum.hpp:155
void jacobiMom(const MomentumOp &op, View< double > u, View< const double > b, View< double > tmp, double omega)
One weighted-Jacobi sweep of A u = b (in place).
Definition momentum.hpp:94
void restrictField(View< const Index > childStart, View< const Index > childIdx, View< const double > fine, View< double > coarse, Index nCoarse)
Restrict: coarse(p) = mean over p's children (CSR fixed order ⇒ deterministic).
Definition multigrid.hpp:45
void prolongAddMasked(View< const Index > c2p, View< const double > coarse, View< const char > excl, View< double > fine, Index nFine)
Masked piecewise-constant prolong + correct: fine(i) += coarse(c2p(i)) only on non-excluded fine cell...
Definition multigrid.hpp:93
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
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15
A graph colouring of a face CSR: cells grouped by colour.
Definition momentum.hpp:138
Assembled momentum operator on the device: (A u)_i = diag_i u_i + Σ coef·u[nbr], with an optional imp...
Definition momentum.hpp:43