peclet-amr 0.2.0
Block-local-Morton AMR octree + collocated cut-cell Navier–Stokes solver (header-only C++20, Kokkos + MPI)
Loading...
Searching...
No Matches
multigrid.hpp
Go to the documentation of this file.
1// core — device (Kokkos) geometric-multigrid V-cycle on a BlockOctree,
2// with the *consistent* graded (cut-cell-ready) operator.
3//
4// The device compute path's capstone: a full MG V-cycle running entirely in device
5// kernels over the leaf Views, on a genuinely graded octree. The hierarchy is the
6// octree coarsened uniformly one level at a time (same as host AmrMultigrid); each
7// level's operator is the conservative two-point FV Laplacian
8// (L u)_i = invVol_i · Σ_f w_f (u[nbr_f] − u_i), w_f = openness·A_f/d_f,
9// captured as a precomputed face CSR (built on the host from AmrPoisson's exact
10// forEachFaceNeighbor enumeration, so the 2:1 sub-faces and coefficients match the
11// host operator bit-for-bit). This replaces the earlier *plain* (u_j−u_i)/h0²
12// operator, which ignored 2:1-interface geometry and so stalled the graded V-cycle.
13//
14// Every stage is deterministic / order-independent ⇒ bit-identical to the same
15// Jacobi V-cycle on the host (validated in test_amr_device_multigrid_kokkos):
16// * smoother — weighted Jacobi over the face CSR (reads only the prev iterate);
17// * restriction — each coarse cell sums its children in fixed CSR order (no atomics);
18// * prolongation— piecewise-constant, per fine cell.
19//
20// 2nd-order at 2:1 interfaces: solveQuad() wraps the V-cycle in deferred correction
21// with the quadratic coarse-fine flux (AmrPoisson::coarseStar), evaluated on device
22// as a second precomputed CSR (quadDelta). Openness (cut-cell) flows in for
23// free: build with an AmrPoisson that has openness set and w_f carries it.
24//
25#ifndef PECLET_AMR_MULTIGRID_HPP
26#define PECLET_AMR_MULTIGRID_HPP
27
28#include <cmath>
29#include <memory>
30#include <string>
31#include <utility>
32#include <vector>
33
34#include "peclet/amr/amg_bottom.hpp" // the exact coarsest-level solve (docs/amr_mg_depth.md §6.6)
35#include "peclet/amr/assembly.hpp" // assembleFv (device per-level operator rebuild, D5)
37#include "peclet/amr/common.hpp"
38#include "peclet/amr/fv_op.hpp"
40#include "peclet/core/common/host_parallel.hpp"
41#include "peclet/core/common/view.hpp"
42
43namespace peclet::amr {
44
46inline void restrictField(View<const Index> childStart, View<const Index> childIdx,
48 Kokkos::parallel_for(
49 "amr::device_restrict", nCoarse, KOKKOS_LAMBDA(const Index p) {
50 const Index a = childStart(p), z = childStart(p + 1);
51 double s = 0.0;
52 for (Index k = a; k < z; ++k)
53 s += fine(childIdx(k));
54 coarse(p) = (z > a) ? s / static_cast<double>(z - a) : 0.0;
55 });
56}
57
64inline void restrictKappa(View<const Index> childStart, View<const Index> childIdx,
66 Index nCoarse) {
67 Kokkos::parallel_for(
68 "amr::device_restrict_kappa", nCoarse, KOKKOS_LAMBDA(const Index p) {
69 const Index a = childStart(p), z = childStart(p + 1);
70 double sw = 0.0, swv = 0.0;
71 for (Index k = a; k < z; ++k) {
72 const Index ch = childIdx(k);
73 const double w = kappa(ch);
74 sw += w;
75 swv += w * fine(ch);
76 }
77 coarse(p) = (sw > 0.0) ? swv / sw : 0.0;
78 });
79}
80
83 Index nFine) {
84 Kokkos::parallel_for(
85 "amr::device_prolong", nFine, KOKKOS_LAMBDA(const Index i) {
86 const Index p = c2p(i);
87 if (p >= 0)
88 fine(i) += coarse(p);
89 });
90}
91
96 Kokkos::parallel_for(
97 "amr::device_prolong_masked", nFine, KOKKOS_LAMBDA(const Index i) {
98 if (excl(i))
99 return;
100 const Index p = c2p(i);
101 if (p >= 0)
102 fine(i) += coarse(p);
103 });
104}
105
110 Kokkos::parallel_for(
111 "amr::device_zero_masked", n, KOKKOS_LAMBDA(const Index i) {
112 if (excl(i))
113 v(i) = 0.0;
114 });
115}
116
117template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
119 public:
121 using M = typename Octree::M;
122 using Code = typename Octree::Code;
124
130
136 void build(const Octree& finest, double h0, bool liftRoot = true, Index bottomExtent = 4) {
137 build(finest, detail::filledVec<Dim>(h0), liftRoot, bottomExtent);
138 }
142 void build(const Octree& finest, const Vec<Dim>& h0, bool liftRoot = true,
143 Index bottomExtent = 4) {
144 bottomExtent_ = bottomExtent;
145 hmg_ = std::make_unique<AmrMultigrid<Dim, Bits>>();
146 hmg_->build(finest, h0, liftRoot, bottomExtent);
147 buildFromHostMg();
148 }
149
154 template <class OpenFn>
155 void build(const Octree& finest, double h0, OpenFn&& openFn, bool periodic = true,
156 bool immersedWall = false, bool liftRoot = true, Index bottomExtent = 4) {
157 build(finest, detail::filledVec<Dim>(h0), std::forward<OpenFn>(openFn), periodic, immersedWall,
158 liftRoot, bottomExtent);
159 }
164 template <class OpenFn>
165 void build(const Octree& finest, const Vec<Dim>& h0, OpenFn&& openFn, bool periodic = true,
166 bool immersedWall = false, bool liftRoot = true, Index bottomExtent = 4) {
167 bottomExtent_ = bottomExtent;
168 hmg_ = std::make_unique<AmrMultigrid<Dim, Bits>>();
169 hmg_->build(finest, h0, liftRoot, bottomExtent);
170 hmg_->setOpenness(std::forward<OpenFn>(openFn));
171 hmg_->setPeriodic(periodic); // non-periodic ⇒ homogeneous Dirichlet domain walls
172 hmg_->setImmersedWall(immersedWall); // true ⇒ velocity operator (solid faces = no-slip walls)
173 buildFromHostMg();
174 }
175
182 void buildRaw(const Octree& finest, const Vec<Dim>& h0, std::vector<double> alphaRaw,
183 bool periodic = true, bool liftRoot = true, Index bottomExtent = 4) {
184 bottomExtent_ = bottomExtent;
185 hmg_ = std::make_unique<AmrMultigrid<Dim, Bits>>();
186 hmg_->build(finest, h0, liftRoot, bottomExtent);
187 hmg_->setOpennessRaw(std::move(alphaRaw));
188 hmg_->setPeriodic(periodic);
189 buildFromHostMg();
190 }
191
194 void setKappaRestrict(bool on) { kappaRestrict_ = on; }
195
201 void setRemoveMean(bool on) { removeMean_ = on; }
202
208 void setHelmholtz(double c0, double cD) {
209 for (auto& lv : levels_) {
210 lv.op.c0 = c0;
211 lv.op.cD = cD;
212 }
213 // The exact bottom assembles the pure FV Laplacian from the host AmrPoisson, which carries no
214 // reaction term — so a Helmholtz hierarchy falls back to the Jacobi bottom rather than solving
215 // the wrong operator. (docs/amr_mg_depth.md §6.6 scopes the exact bottom to the pressure.)
216 if (c0 != 0.0 || cD != 1.0)
217 amgOn_ = false;
218 }
219
227 bottomKind_ = b;
228 buildBottom();
229 }
231 Bottom bottom() const { return bottomKind_; }
233 std::string bottomName() const { return amgOn_ ? "amg" : "jacobi"; }
235 Index bottomExtent() const { return bottomExtent_; }
237 Index bottomSize() const { return amg_ ? amg_->size() : 0; }
239 Index bottomIdentityRows() const { return amg_ ? amg_->numIdentityRows() : 0; }
241 int bottomComponents() const { return amg_ ? amg_->numComponents() : 0; }
243 int bottomIters() const { return amg_ ? amg_->lastIters() : 0; }
245 bool bottomOnDevice() const { return amg_ && amg_->onDevice(); }
246
250 void setBottomCheck(bool on) { bottomCheck_ = on; }
252 double bottomResidual() const { return bottomRel_; }
253
254 std::size_t numLevels() const { return levels_.size(); }
255 Index numLeaves(std::size_t L = 0) const { return levels_[L].n; }
256 Code octreeCode(std::size_t L, Index i) const { return hmg_->op(L).octree().code(i); }
257 View<double> x(std::size_t L = 0) { return levels_[L].x; }
258 View<double> b(std::size_t L = 0) { return levels_[L].b; }
259 const FvOp& op(std::size_t L = 0) const { return levels_[L].op; }
260 View<Index> quadStart() const { return qStart_; }
261 View<Index> quadSlot() const { return qSlot_; }
262 View<double> quadCoef() const { return qCoef_; }
263
266 void vcycle(int pre = 2, int post = 2, int bottom = 40, double omega = 0.8, std::size_t L = 0) {
267 Level& lv = levels_[L];
269 if (L + 1 == levels_.size()) {
270 if (amgOn_) {
271 bottomSolveExact(lv); // §6.6: the agglomerated GraphAMG-PCG solve
272 } else {
273 for (int s = 0; s < bottom; ++s)
274 jacobiFv(lv.op, lv.x, bc, lv.tmp, omega);
275 }
276 if (removeMean_)
277 removeMeanFv(lv.op, lv.x);
278 return;
279 }
280 for (int s = 0; s < pre; ++s)
281 jacobiFv(lv.op, lv.x, bc, lv.tmp, omega);
282 residualFv(lv.op, View<const double>(lv.x), bc, lv.res);
283 Level& cl = levels_[L + 1];
284 if (kappaRestrict_)
285 restrictKappa(lv.childStart, lv.childIdx, View<const double>(lv.res),
286 View<const double>(lv.kappa), cl.b, cl.n);
287 else
288 restrictField(lv.childStart, lv.childIdx, View<const double>(lv.res), cl.b, cl.n);
289 Kokkos::deep_copy(cl.x, 0.0);
290 vcycle(pre, post, bottom, omega, L + 1);
291 prolongAdd(lv.c2p, View<const double>(cl.x), lv.x, lv.n);
292 for (int s = 0; s < post; ++s)
293 jacobiFv(lv.op, lv.x, bc, lv.tmp, omega);
294 if (removeMean_)
295 removeMeanFv(lv.op, lv.x);
296 }
297
303 double solveQuad(int outer = 20, int cyclesPerOuter = 1, int pre = 2, int post = 2,
304 int bottom = 40, double omega = 0.8) {
305 Level& lv = levels_[0];
306 Kokkos::deep_copy(b0true_, lv.b); // true rhs
307 auto dq = dq_;
308 auto b0 = b0true_;
309 auto bb = lv.b;
310 double r = 0.0;
311 for (int o = 0; o < outer; ++o) {
313 View<const double>(lv.x), dq_, lv.n);
314 Kokkos::parallel_for(
315 "amr::quad_rhsp", lv.n, KOKKOS_LAMBDA(const Index i) { bb(i) = b0(i) - dq(i); });
316 for (int c = 0; c < cyclesPerOuter; ++c)
317 vcycle(pre, post, bottom, omega, 0);
318 }
319 // r = || b0true − (L_std + quad) x ||
322 View<const double>(lv.x), dq_, lv.n);
323 auto res = lv.res;
324 double s = 0.0;
325 Kokkos::parallel_reduce(
326 "amr::quad_resnorm", lv.n,
327 KOKKOS_LAMBDA(const Index i, double& acc) {
328 double rr = res(i) - dq(i);
329 acc += rr * rr;
330 },
331 s);
332 r = std::sqrt(s);
333 Kokkos::deep_copy(lv.b, b0true_); // restore rhs
334 return r;
335 }
336
337 private:
338 struct Level {
339 FvOp op;
340 Index n = 0;
341 View<double> x, b, res, tmp;
342 View<double> kappa; // per-cell fluid-fraction weight (κ restriction)
343 View<Index> c2p, childStart, childIdx; // describe L → L+1 (unused on coarsest)
344 };
345
346 // Upload the device hierarchy from the host AmrMultigrid hmg_ (which already holds
347 // the uniform-coarsened octrees + per-level operators with coarsened openness). Each
348 // level's face CSR is built from hmg_->op(L), so the device operator == that host
349 // operator bit-for-bit on every level.
350 void buildFromHostMg() {
351 const std::size_t nl = hmg_->numLevels();
352 levels_.clear();
353 levels_.resize(nl);
354 for (std::size_t L = 0; L < nl; ++L) {
355 const Poisson& ap = hmg_->op(L);
356 const Octree& oct = ap.octree();
357 Level& lv = levels_[L];
358 lv.n = oct.numLeaves();
359 buildFaceCsr(ap, oct, lv);
360 // Per-cell κ weight for the optional κ-weighted restriction = mean face aperture
361 // (1 fully open ⇒ reduces to plain average; <1 near cuts).
362 std::vector<double> kap(static_cast<std::size_t>(lv.n));
363 hostParFor(lv.n, [&](Index i) { // per-cell disjoint (rung 2); the 2·Dim sum stays in-row
364 double s = 0.0;
365 for (int axis = 0; axis < Dim; ++axis)
366 for (int dir = -1; dir <= 1; dir += 2)
367 s += ap.faceOpenness(i, axis, dir);
368 kap[static_cast<std::size_t>(i)] = s / static_cast<double>(2 * Dim);
369 });
370 lv.kappa = toDevice(kap, "mg_kappa");
371 lv.x = View<double>("mg_x", static_cast<std::size_t>(lv.n));
372 lv.b = View<double>("mg_b", static_cast<std::size_t>(lv.n));
373 lv.res = View<double>("mg_res", static_cast<std::size_t>(lv.n));
374 lv.tmp = View<double>("mg_tmp", static_cast<std::size_t>(lv.n));
375 }
376 for (std::size_t L = 0; L + 1 < nl; ++L) {
377 const Octree& f = hmg_->op(L).octree();
378 const Octree& c = hmg_->op(L + 1).octree();
379 const Index nf = f.numLeaves(), nc = c.numLeaves();
380 std::vector<Index> c2p(static_cast<std::size_t>(nf));
381 std::vector<Index> cnt(static_cast<std::size_t>(nc), 0);
382 // Covering-leaf construction: identical to ancestor(level+1)+find for merged children,
383 // and CORRECT (identity) for cells already at root level in mixed-depth ladders — the
384 // ancestor form maps those to the 2^(lmax+1)-aligned corner root cell instead (measured:
385 // 49/56 root rows mis-parented on a graded 16^3 ladder), and its alignment depends on
386 // the block origin, which would break the distributed WORLD==SELF transfer contract.
387 // Rung 2: the `find` (the expensive half) is per-fine-row disjoint and runs host-parallel;
388 // the child HISTOGRAM is many-to-one, so it stays serial — integer counts, unchanged.
389 hostParFor(nf, [&](Index i) { c2p[static_cast<std::size_t>(i)] = c.find(f.code(i)); });
390 for (Index i = 0; i < nf; ++i) {
391 const Index p = c2p[static_cast<std::size_t>(i)];
392 if (p >= 0)
393 ++cnt[static_cast<std::size_t>(p)];
394 }
395 std::vector<Index> start(static_cast<std::size_t>(nc) + 1, 0);
396 for (Index p = 0; p < nc; ++p)
397 start[static_cast<std::size_t>(p) + 1] =
398 start[static_cast<std::size_t>(p)] + cnt[static_cast<std::size_t>(p)];
399 std::vector<Index> idx(static_cast<std::size_t>(nf));
400 std::vector<Index> cur(start.begin(), start.end() - 1);
401 for (Index i = 0; i < nf; ++i) {
402 Index p = c2p[static_cast<std::size_t>(i)];
403 if (p >= 0)
404 idx[static_cast<std::size_t>(cur[static_cast<std::size_t>(p)]++)] = i;
405 }
406 levels_[L].c2p = toDevice(c2p, "mg_c2p");
407 levels_[L].childStart = toDevice(start, "mg_cstart");
408 levels_[L].childIdx = toDevice(idx, "mg_cidx");
409 }
410 // Quadratic coarse-fine correction CSR on the finest level (for solveQuad);
411 // openness (if any) flows in through hmg_->op(0)'s aperture + coarseStar gating.
412 buildQuadCsr(hmg_->op(0), hmg_->op(0).octree());
413 const Index n0 = levels_[0].n;
414 dq_ = View<double>("mg_dq", static_cast<std::size_t>(n0));
415 b0true_ = View<double>("mg_b0", static_cast<std::size_t>(n0));
416 buildBottom(); // §6.6: the exact coarsest-level solve, if the ladder stopped short
417 }
418
419 // Build the consistent face CSR (+ invVol/bcDiag) for one level by assembling it ON THE DEVICE
420 // (D5/D2): upload this level's leaf set, then assembleFv reproduces AmrPoisson's exact
421 // forEachFaceNeighbor enumeration + openness·A_f/d_f weights — bit-for-bit identical to the host
422 // walk on OpenMP (validated in test_amr_assembly), but with no host CSR build / no
423 // re-upload. The Helmholtz c0/cD of this level are preserved (assembleFv returns the pure-L
424 // defaults; setHelmholtz / a prior value is re-applied), so a reassemble after the geometry moves
425 // keeps the momentum-preconditioner form.
429 void buildBottom() {
430 amgOn_ = false;
431 amg_.reset();
432 if (!hmg_ || levels_.empty())
433 return;
434 const std::size_t Lc = levels_.size() - 1;
435 const Poisson& ap = hmg_->op(Lc);
436 Index E = 0;
437 for (int d = 0; d < Dim; ++d)
438 E = std::max(E, ap.octree().brick()[d]);
439 const bool want =
440 (bottomKind_ == Bottom::Agglomerated) || (bottomKind_ == Bottom::Auto && E > bottomExtent_);
441 if (!want)
442 return;
443 // Singular iff the operator carries no Dirichlet term anywhere — read from the assembly, so it
444 // does not depend on whether setRemoveMean has been called yet.
445 const auto A = ap.assembleFv();
446 bool singular = true;
447 for (double d : A.bcDiag)
448 if (d != 0.0) {
449 singular = false;
450 break;
451 }
452 amg_ = std::make_unique<AmgBottom<Dim, Bits>>();
453 amg_->build(ap, singular);
454 amgOn_ = true;
455 }
456
459 void bottomSolveExact(Level& lv) {
460 amg_->solve(View<const double>(lv.b), lv.x);
461 if (!bottomCheck_)
462 return;
464 auto res = lv.res;
465 auto bb = lv.b;
466 double rm = 0.0, bm = 0.0;
467 Kokkos::parallel_reduce(
468 "amr::bottom_check", lv.n,
469 KOKKOS_LAMBDA(const Index i, double& a, double& c) {
470 const double r = res(i) < 0.0 ? -res(i) : res(i);
471 const double b = bb(i) < 0.0 ? -bb(i) : bb(i);
472 if (r > a)
473 a = r;
474 if (b > c)
475 c = b;
476 },
477 Kokkos::Max<double>(rm), Kokkos::Max<double>(bm));
478 bottomRel_ = (bm > 0.0) ? rm / bm : 0.0;
479 }
480
481 void buildFaceCsr(const Poisson& ap, const Octree& t, Level& lv) {
482 const double c0 = lv.op.c0, cD = lv.op.cD;
483 BlockOctreeView<Dim, Bits> ov;
484 ov.upload(t);
485 lv.op = assembleFv<Dim, Bits>(ap, ov);
486 lv.op.c0 = c0;
487 lv.op.cD = cD;
488 }
489
490 public:
497 const std::size_t nl = hmg_->numLevels();
498 for (std::size_t L = 0; L < nl && L < levels_.size(); ++L)
499 buildFaceCsr(hmg_->op(L), hmg_->op(L).octree(), levels_[L]);
500 buildBottom(); // the exact bottom is assembled FROM that operator, so it is stale otherwise
501 }
502
503 private:
504 // Build the quadratic coarse-fine correction CSR: dq_i = Σ coef·u[slot] equals
505 // (L_quad − L_std)u (AmrPoisson::coarseStar, expressed as a linear stencil).
506 void buildQuadCsr(const Poisson& ap, const Octree& t) {
507 const Index n = t.numLeaves();
508 std::vector<std::vector<std::pair<Index, double>>> per(static_cast<std::size_t>(n));
509 const Vec<Dim>& h0 = ap.h0(); // Phase 3: per-axis
510 hostParFor(n, [&](Index i) { // per[i]-disjoint (rung 2)
511 const unsigned Li = t.level(i);
512 const double invV = 1.0 / ap.cellVolume(i);
513 ap.forEachFaceNeighbor(i, [&](Index j, Real c, int axis, double a) {
514 const unsigned Lj = t.level(j);
515 if (Lj == Li)
516 return;
517 const Index coarse = (Lj > Li) ? j : i;
518 const Index fine = (Lj > Li) ? i : j;
519 const double scale = invV * (a * c) * ((Lj > Li) ? 1.0 : -1.0);
520 addCoarseStarStencil(ap, t, per[static_cast<std::size_t>(i)], coarse, fine, axis, scale,
521 h0);
522 });
523 });
524 std::vector<Index> qs(static_cast<std::size_t>(n) + 1, 0);
525 for (Index i = 0; i < n; ++i)
526 qs[static_cast<std::size_t>(i) + 1] =
527 qs[static_cast<std::size_t>(i)] +
528 static_cast<Index>(per[static_cast<std::size_t>(i)].size());
529 const Index nq = qs[static_cast<std::size_t>(n)];
530 std::vector<Index> slot(static_cast<std::size_t>(nq));
531 std::vector<double> coef(static_cast<std::size_t>(nq));
532 Index k = 0;
533 for (Index i = 0; i < n; ++i)
534 for (auto& e : per[static_cast<std::size_t>(i)]) {
535 slot[static_cast<std::size_t>(k)] = e.first;
536 coef[static_cast<std::size_t>(k)] = e.second;
537 ++k;
538 }
539 qStart_ = toDevice(qs, "mg_qstart");
540 qSlot_ = toDevice(slot, "mg_qslot");
541 qCoef_ = toDevice(coef, "mg_qcoef");
542 }
543
544 // coarseStar(coarse,fine,axis) − u[coarse] as a linear stencil over the coarse
545 // cell + its tangential coarse neighbours, scaled, appended to `out`. Mirrors
546 // AmrPoisson::coarseStar exactly (same gating, same coefficients).
547 static void addCoarseStarStencil(const Poisson& ap, const Octree& t,
548 std::vector<std::pair<Index, double>>& out, Index coarse,
549 Index fine, int axis, double scale, const Vec<Dim>& h0) {
550 auto bc = t.bounds(coarse);
551 auto bf = t.bounds(fine);
552 const double sc = static_cast<double>(Index(1) << t.level(coarse));
553 const double sf = static_cast<double>(Index(1) << t.level(fine));
554 for (int tt = 0; tt < Dim; ++tt) {
555 if (tt == axis)
556 continue;
557 // Phase 3: the tangential offset AND the differencing width belong to axis `tt` — they
558 // were one `H` when the cells were cubes. Mirrors AmrPoisson::coarseStar exactly.
559 const double H = ap.cellWidth(coarse, tt);
560 const double dt = ((static_cast<double>(bf[0][tt]) + 0.5 * sf) -
561 (static_cast<double>(bc[0][tt]) + 0.5 * sc)) *
562 h0[tt];
563 Index cp = ap.periodicNeighbor(coarse, tt, +1);
564 Index cm = ap.periodicNeighbor(coarse, tt, -1);
565 if (cp < 0 || cm < 0)
566 continue;
567 if (t.level(cp) != t.level(coarse) || t.level(cm) != t.level(coarse))
568 continue;
569 if (ap.faceOpenness(coarse, tt, +1) < 0.5 || ap.faceOpenness(coarse, tt, -1) < 0.5)
570 continue;
571 const double cUp = dt / (2.0 * H) + 0.5 * dt * dt / (H * H);
572 const double cUm = -dt / (2.0 * H) + 0.5 * dt * dt / (H * H);
573 const double cUc = -dt * dt / (H * H);
574 out.emplace_back(coarse, scale * cUc);
575 out.emplace_back(cp, scale * cUp);
576 out.emplace_back(cm, scale * cUm);
577 }
578 }
579
580 // Owns the host hierarchy + per-level operators (with coarsened openness). The
581 // operators hold pointers into its octrees, so keep it stable (unique_ptr) for the
582 // lifetime of this object.
583 std::unique_ptr<AmrMultigrid<Dim, Bits>> hmg_;
584 std::vector<Level> levels_;
585 bool kappaRestrict_ = false; // default: plain volume-average restriction
586 bool removeMean_ = false; // default off; per-level nullspace projection for singular MG-PCG
587 Bottom bottomKind_ = Bottom::Auto;
588 Index bottomExtent_ = 4;
589 bool amgOn_ = false, bottomCheck_ = false;
590 double bottomRel_ = 0.0;
591 std::unique_ptr<AmgBottom<Dim, Bits>> amg_;
592 View<Index> qStart_, qSlot_; // finest-level quadratic correction CSR
593 View<double> qCoef_;
594 View<double> dq_, b0true_; // finest-level deferred-correction scratch
595};
596
597} // namespace peclet::amr
598
599#endif // PECLET_AMR_MULTIGRID_HPP
morton::Morton< Dim, Bits > M
BlockOctree< Dim, Bits > Octree
const FvOp & op(std::size_t L=0) const
View< Index > quadSlot() const
AmrPoisson< Dim, Bits > Poisson
View< Index > quadStart() const
void setHelmholtz(double c0, double cD)
Turn every level's operator into the Helmholtz form H = c0·I + cD·L (default c0=0,...
Code octreeCode(std::size_t L, Index i) const
int bottomIters() const
Inner CG iterations of the last exact bottom solve (0 without it).
Bottom bottom() const
The selection in force (Auto unless setBottom said otherwise).
void reassembleOperators()
Re-assemble every level's operator ON THE DEVICE from the (host) hierarchy's current geometry — the d...
Index numLeaves(std::size_t L=0) const
Index bottomExtent() const
The extent (cells per axis) the last build stopped the ladder at and Auto compares with.
int bottomComponents() const
Connected fluid components of the coarsest level (0 without the exact bottom).
View< double > b(std::size_t L=0)
double solveQuad(int outer=20, int cyclesPerOuter=1, int pre=2, int post=2, int bottom=40, double omega=0.8)
Solve L_quad u = rhs (the 2nd-order graded operator) by deferred correction: each outer step solves L...
typename Octree::Code Code
void build(const Octree &finest, const Vec< Dim > &h0, OpenFn &&openFn, bool periodic=true, bool immersedWall=false, bool liftRoot=true, Index bottomExtent=4)
The per-axis form of the openness build (see the scalar-h0 overload above).
void vcycle(int pre=2, int post=2, int bottom=40, double omega=0.8, std::size_t L=0)
One V-cycle on level L (default finest) of the standard (consistent conservative) operator,...
double bottomResidual() const
max|b − L x| / max|b| of the last checked bottom solve (0 unless setBottomCheck(true)).
void build(const Octree &finest, const Vec< Dim > &h0, bool liftRoot=true, Index bottomExtent=4)
The per-axis form: h0 is the finest spacing (dx, dy, dz); every level inherits the root aspect ratio ...
void build(const Octree &finest, double h0, bool liftRoot=true, Index bottomExtent=4)
Build + upload the hierarchy from a finest octree (uniform coarsening), openness- free.
void build(const Octree &finest, double h0, OpenFn &&openFn, bool periodic=true, bool immersedWall=false, bool liftRoot=true, Index bottomExtent=4)
Build with cut-cell openness openFn(faceCentreWorld, axis) → [0,1], coarsened to every level by area-...
Bottom
What solves the coarsest level (docs/amr_mg_depth.md §6.6).
void setBottom(Bottom b)
Select what solves the coarsest level (§6.6).
typename Octree::M M
View< double > x(std::size_t L=0)
void setBottomCheck(bool on)
Opt-in §6.6 consistency gate (flow's AGMG_DEBUG): after every exact bottom solve, recompute |b − L x|...
void buildRaw(const Octree &finest, const Vec< Dim > &h0, std::vector< double > alphaRaw, bool periodic=true, bool liftRoot=true, Index bottomExtent=4)
Build from a finest octree whose face openness is handed over ALREADY EVALUATED — alphaRaw in the Amr...
std::string bottomName() const
"jacobi" | "amg" — what the coarsest level ACTUALLY runs.
void setRemoveMean(bool on)
Opt-in (default off): project the correction to mean-zero over fluid cells at every V-cycle level — t...
Index bottomIdentityRows() const
Fully closed coarsest cells the exact bottom solves as identity rows (0 without it).
bool bottomOnDevice() const
Whether the exact bottom's per-V-cycle solve runs on the device (§5.5's 10^4-row rule).
std::size_t numLevels() const
View< double > quadCoef() const
Index bottomSize() const
Rows of the exact bottom's matrix (0 when the sweeps run instead).
void setKappaRestrict(bool on)
Opt-in: use κ-weighted (fluid-fraction) restriction instead of the default plain volume-average.
void zeroMasked(View< double > v, View< const char > excl, Index n)
Zero v at excluded cells (excl != 0).
void removeMeanFv(const FvOp &op, View< double > u)
Project u to volume-weighted-mean-zero over the ACTIVE (fluid) cells of the operator — the constant-n...
Definition fv_op.hpp:149
void residualFv(const FvOp &op, View< const double > u, View< const double > rhs, View< double > res)
res = rhs − H u.
Definition fv_op.hpp:119
void restrictKappa(View< const Index > childStart, View< const Index > childIdx, View< const double > fine, View< const double > kappa, View< double > coarse, Index nCoarse)
Restrict, κ-weighted: coarse(p) = Σ_child κ_c·fine_c / Σ_child κ_c, with κ the fine cell's fluid-frac...
Definition multigrid.hpp:64
void jacobiFv(const FvOp &op, View< double > u, View< const double > rhs, View< double > tmp, double omega)
One weighted-Jacobi sweep of H u = rhs (in place).
Definition fv_op.hpp:133
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:94
void quadDelta(View< const Index > qStart, View< const Index > qSlot, View< const double > qCoef, View< const double > u, View< double > dq, Index n)
dq = (L_quad − L_std) u, the quadratic coarse-fine correction as its own SpMV over a precomputed CSR ...
Definition fv_op.hpp:189
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:46
void prolongAdd(View< const Index > c2p, View< const double > coarse, View< double > fine, Index nFine)
Prolong (piecewise-constant) + correct: fine(i) += coarse(c2p(i)).
Definition multigrid.hpp:82
std::vector< std::array< double, Dim > > transferFieldGradients(const BlockOctree< Dim, Bits > &oldT, const std::vector< double > &oldF)
Per-old-leaf minmod prolongation gradients (per fine-coordinate unit) — transferField's stencil,...
Definition adapt.hpp:51