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
distributed_flow_mg.hpp
Go to the documentation of this file.
1// core — distributed device openness multigrid for the AMR flow pressure (rung 3 of
2// docs/amr_distributed_flow.md): the distributed counterpart of Multigrid (multigrid.hpp),
3// i.e. the aperture-path pressure hierarchy AmrFlow::presMG_ runs — graded octree, cut-cell
4// openness area-averaged down the ladder, Jacobi smoothing, per-level nullspace projection —
5// with every level a ghost-slot face CSR + its own LeafHalo.
6//
7// Structure (all proven pieces composed):
8// * Level ladder: per-rank coarsenIf of the LOCAL octree (same as AmrMultigrid::build),
9// each level a COPY of the flow's DistributedOctree with the coarsened local() — the copy
10// carries the decomposition, so a rebalanced (weighted-ORB) flow octree keeps correct
11// owner lookups. Ranks reaching their root brick early PAD to the global max level count
12// (Allreduce MAX) by repeating the root brick: the padded levels' transfers are identity
13// (the covering c2p maps a leaf to itself) and their smoothing exactly mirrors what the
14// whole-domain COMM_SELF ladder does in its already-coarse regions (the
15// GradedDistributedMultigrid argument) — and the level counts must match anyway for the
16// per-level halo point-to-points to pair up.
17// * Per level: an AmrPoisson with the LeafHalo resolver seam (setResolver/setGhosts/
18// setFrameShift — probes that exit the block resolve to ghost slots; world-coordinate
19// evaluations in the GLOBAL frame), built to the miss-collect fixpoint, then host
20// assembleFv → device FvOp whose faceNbr may reference the ghost tail.
21// * Openness: level 0 α from the world-coord openFn (ghost rows sampled locally — the same
22// world points as the owner, bit-identical); level L>0 LOCAL α by the exact
23// AmrMultigrid::coarsenOpenness child-face averaging (children of a local coarse cell are
24// always local, visited in the same relative Z-order as COMM_SELF ⇒ bit-identical), and
25// GHOST α rows exchanged from the owner once per build (exact by construction — never
26// re-derived).
27// * V-cycle: jacobiFv with a ghost refresh before every sweep, local restrict/prolong
28// (parents never cross blocks), Allreduce'd volume-weighted mean removal (removeMean).
29// * Below the in-place ladder: a STAGE (mg_stage.hpp, docs/amr_mg_depth.md §5.6/§6.5) moves the
30// coarsest level onto a new decomposition of its own grid and continues the ladder there. The
31// V-cycle contains no MPI of its own for this — every message lives behind MgStage::apply:
32// the replicated tail (mg_stage.hpp) and the sibling-merge / repartition `DistributedStage`
33// below, whose continued ladder is another DistributedFlowMultigrid on the stage's
34// sub-communicator (WO4b; the movement is core's RedistributeTopology).
35//
36// Bit-exactness: at np=1 every probe resolves locally (zero ghosts) and the whole cycle is
37// the single-rank Multigrid arithmetic verbatim. Across ranks the smoother/transfers are
38// order-independent ⇒ WORLD==SELF bit-exact with removeMean OFF; the mean removal (a global
39// reduction) and PCG dots are np-invariant only to reduction order ⇒ tolerance, the suite's
40// Krylov contract.
41//
42// Kokkos + MPI header (include in device TUs; the AmrFlow oracle stays single-rank).
43#ifndef PECLET_AMR_DISTRIBUTED_FLOW_MG_HPP
44#define PECLET_AMR_DISTRIBUTED_FLOW_MG_HPP
45
46#include <algorithm>
47#include <array>
48#include <memory>
49#include <stdexcept>
50#include <string>
51#include <vector>
52
53#include "peclet/amr/amg_bottom.hpp" // the exact coarsest-level solve (docs/amr_mg_depth.md §6.6)
54#include "peclet/amr/common.hpp"
56#include "peclet/amr/fv_op.hpp"
58#include "peclet/amr/mg_predict.hpp" // PressureStagePolicy, amrStageLiftable (shared with predict)
59#include "peclet/amr/mg_stage.hpp" // the telescoping primitive (docs/amr_mg_depth.md §5.6/§6.5)
60#include "peclet/amr/multigrid.hpp" // restrictField / prolongAdd (shared transfer kernels)
62#include "peclet/core/common/mpi.hpp"
63#include "peclet/core/common/view.hpp"
64#include "peclet/core/decomp/redistribute_topology.hpp"
65#include "peclet/core/decomp/stage_comm.hpp"
66#include "peclet/core/decomp/stage_target.hpp"
67
68namespace peclet::amr {
69
72inline void removeMeanFvDist(const FvOp& op, View<double> u, MPI_Comm comm) {
73 auto invVol = op.invVol;
74 auto fs = op.faceStart;
75 auto fw = op.faceW;
76 auto bc = op.bcDiag;
77 double sx = 0.0, sv = 0.0;
78 Kokkos::parallel_reduce(
79 "amr::fv_rmean", op.n,
80 KOKKOS_LAMBDA(const Index i, double& an, double& ad) {
81 double d = bc(i);
82 for (Index k = fs(i); k < fs(i + 1); ++k)
83 d += fw(k);
84 if (d > 1e-30) {
85 an += u(i) / invVol(i);
86 ad += 1.0 / invVol(i);
87 }
88 },
89 sx, sv);
90 double loc[2] = {sx, sv}, glob[2] = {0.0, 0.0};
92 if (glob[1] <= 0.0)
93 return;
94 const double m = glob[0] / glob[1];
95 Kokkos::parallel_for(
96 "amr::fv_rmean_sub", op.n, KOKKOS_LAMBDA(const Index i) {
97 double d = bc(i);
98 for (Index k = fs(i); k < fs(i + 1); ++k)
99 d += fw(k);
100 if (d > 1e-30)
101 u(i) -= m;
102 });
103}
104
105template <int Dim, unsigned Bits>
106class DistributedStage; // the sibling-merge / repartition stage, defined below the multigrid
107
108template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
110 public:
112 using Octree = typename DO::Octree;
114 using M = typename Octree::M;
115 using Code = typename Octree::Code;
116
130 template <class OpenFn>
131 void build(const DO& finest, double h0, OpenFn&& openFn,
132 const LeafHalo<Dim, Bits>* shared0 = nullptr, bool liftRoot = true,
133 Index bottomExtent = 4) {
134 build(finest, detail::filledVec<Dim>(h0), std::forward<OpenFn>(openFn), shared0, liftRoot,
135 bottomExtent);
136 }
138 template <class OpenFn>
139 void build(const DO& finest, const Vec<Dim>& h0, OpenFn&& openFn,
140 const LeafHalo<Dim, Bits>* shared0 = nullptr, bool liftRoot = true,
141 Index bottomExtent = 4) {
142 buildImpl(finest, h0, shared0, liftRoot, bottomExtent);
143 // Openness ladder: finest level directly from the world-coord openFn (local + ghost rows,
144 // both exact); coarser levels by the exact single-rank child-face averaging for local rows
145 // + a one-time owner exchange for ghost rows.
146 levels_[0]->ap.buildOpenness(openFn);
147 for (std::size_t L = 0; L + 1 < levels_.size(); ++L)
148 coarsenOpennessTo(L);
149 finishOps();
150 buildStage(); // §6.5: below the in-place ladder the level MOVES to a new decomposition
151 }
152
159 void buildRaw(const DO& finest, const Vec<Dim>& h0, const std::vector<double>& alphaLocal,
160 bool liftRoot = true, Index bottomExtent = 4) {
161 buildImpl(finest, h0, nullptr, liftRoot, bottomExtent);
162 setOpennessLocalRows(0, alphaLocal);
163 for (std::size_t L = 0; L + 1 < levels_.size(); ++L)
164 coarsenOpennessTo(L);
165 finishOps();
166 buildStage();
167 }
168
175 void setStagePolicy(const StagePolicy& p) { stagePolicy_ = p; }
176 const StagePolicy& stagePolicy() const { return stagePolicy_; }
177
183 void setRemoveMean(bool on) {
184 removeMean_ = on;
185 if (stage_)
186 stage_->setRemoveMean(on);
187 }
188
189 std::size_t numLevels() const { return levels_.size(); }
193 std::size_t numInPlaceLevels() const { return levels_.size(); }
195 Index liftDepth() const { return liftDepth_; }
202 bool hasStage() const { return stage_ != nullptr; }
204 const char* stageKind() const { return stage_ ? stage_->kind() : "none"; }
207 std::size_t numStageLevels() const { return stage_ ? stage_->numLevels() : 0u; }
210 Index stageLeaves(std::size_t L = 0) const { return stage_->numLeaves(L); }
212 const IVec<Dim>& stageFrom() const { return stageFrom_; }
215 View<double> stageSolution() { return stage_->targetX(); }
217 MgStage<Dim, Bits>& stage() { return *stage_; }
221 std::string bottomName() const {
222 if (stage_)
223 return stage_->bottomName() + stage_->diagnosticSuffix();
224 return bottomAmg_ ? std::string("amg") : std::string("jacobi");
225 }
226
238 bottomKind_ = b;
239 if (stage_)
240 stage_->setBottom(b);
241 else
242 buildStage(); // re-decide the local exact bottom (single-rank distributed runs)
243 }
244 Index numLeaves(std::size_t L = 0) const { return levels_[L]->n; }
245 Index extendedSize(std::size_t L = 0) const { return levels_[L]->nExt; }
246 View<double> x(std::size_t L = 0) { return levels_[L]->x; }
247 View<double> b(std::size_t L = 0) { return levels_[L]->b; }
248 const FvOp& op(std::size_t L = 0) const { return levels_[L]->op; }
249 MPI_Comm comm() const { return comm_; }
250 const Poisson& poisson(std::size_t L = 0) const { return levels_[L]->ap; }
251 const LeafHalo<Dim, Bits>& halo(std::size_t L = 0) const { return *levels_[L]->hp; }
252
254 void sync(std::size_t L, View<double> v) const { levels_[L]->ex.exchange(v); }
255
259 void vcycle(int pre = 2, int post = 2, int bottom = 40, double omega = 0.8, std::size_t L = 0) {
260 Level& lv = *levels_[L];
262 if (L + 1 == levels_.size()) {
263 if (stage_) {
264 // §6.5: the level MOVES onto the stage's decomposition, the ladder continues there, and
265 // the CORRECTION comes back — the residual goes up and the correction is added, which is
266 // what makes the stage legal even at L = 0 (nothing lifted in place, so the moved level is
267 // the finest one and its iterate must survive). No MPI in this function: every message
268 // lives behind the stage.
269 lv.ex.exchange(lv.x);
270 residualFv(lv.op, View<const double>(lv.x), bc, lv.res);
271 stage_->apply(View<const double>(lv.res), lv.tmp, lv.n, pre, post, bottom, omega);
272 auto x = lv.x;
273 auto c = lv.tmp;
274 Kokkos::parallel_for(
275 "amr::stage_add", lv.n, KOKKOS_LAMBDA(const Index i) { x(i) += c(i); });
276 } else if (bottomAmg_) {
277 bottomAmg_->solve(bc, lv.x); // §6.6, single-rank: the level is already whole, here
278 } else {
279 for (int s = 0; s < bottom; ++s) {
280 lv.ex.exchange(lv.x);
281 jacobiFv(lv.op, lv.x, bc, lv.tmp, omega);
282 }
283 }
284 if (removeMean_)
285 removeMeanFvDist(lv.op, lv.x, lv.comm);
286 return;
287 }
288 for (int s = 0; s < pre; ++s) {
289 lv.ex.exchange(lv.x);
290 jacobiFv(lv.op, lv.x, bc, lv.tmp, omega);
291 }
292 lv.ex.exchange(lv.x);
293 residualFv(lv.op, View<const double>(lv.x), bc, lv.res);
294 Level& cl = *levels_[L + 1];
295 restrictField(lv.childStart, lv.childIdx, View<const double>(lv.res), cl.b, cl.n);
296 Kokkos::deep_copy(cl.x, 0.0);
297 vcycle(pre, post, bottom, omega, L + 1);
298 prolongAdd(lv.c2p, View<const double>(cl.x), lv.x, lv.n);
299 for (int s = 0; s < post; ++s) {
300 lv.ex.exchange(lv.x);
301 jacobiFv(lv.op, lv.x, bc, lv.tmp, omega);
302 }
303 if (removeMean_)
304 removeMeanFvDist(lv.op, lv.x, lv.comm);
305 }
306
307 private:
308 struct Level {
309 DO d; // coarsened copy of the flow octree (carries the decomposition)
310 Poisson ap;
311 LeafHalo<Dim, Bits> halo; // own registry (unused when hp aliases shared0)
312 const LeafHalo<Dim, Bits>* hp = nullptr; // the registry in force (own or the shared one)
314 FvOp op;
315 Index n = 0, nExt = 0;
320 MPI_Comm comm = MPI_COMM_NULL;
321 View<double> x, b, res, tmp; // x/b sized nExt (PCG deep_copies match); res/tmp local
322 View<Index> c2p, childStart, childIdx;
323 std::vector<Index> c2pHost; // kept for the openness coarsening
324 };
325
339 void buildStage() {
340 stage_.reset();
341 bottomAmg_.reset();
342 if (levels_.empty())
343 return;
344 Level& lt = *levels_.back();
345 stageFrom_ = lt.d.globalRootSize();
346 int size = 1;
347 MPI_Comm_size(comm_, &size);
348 Index mx = 0;
349 for (int a = 0; a < Dim; ++a)
350 mx = std::max(mx, stageFrom_[a]);
351 const bool want = (bottomKind_ == Multigrid<Dim, Bits>::Bottom::Agglomerated) ||
352 (bottomKind_ == Multigrid<Dim, Bits>::Bottom::Auto && mx > bottomExtent_);
353 if (!want)
354 return; // the 60 damped-Jacobi sweeps are exact at this extent (§6.6)
355 if (size <= 1) {
356 // One rank: the coarsest level is already whole, so no stage is needed to reach an exact
357 // bottom — §6.2's `10^3 root, np=1` row is two levels and a GraphAMG bottom, not a tail, and
358 // this is what keeps the np=1 distributed ladder identical to the single-rank `Multigrid`.
359 const auto A = lt.ap.assembleFv();
360 bool singular = true;
361 for (double d : A.bcDiag)
362 if (d != 0.0) {
363 singular = false;
364 break;
365 }
366 bottomAmg_ = std::make_unique<AmgBottom<Dim, Bits>>();
367 bottomAmg_->build(lt.ap, singular);
368 return;
369 }
370 if (resolved_.enabled && mx > bottomExtent_) {
371 namespace cd = core::decomp;
372 auto liftable = [](const cd::BlockDecomposer<Dim>& dec) {
374 };
375 const cd::StageTarget<Dim> t = cd::chooseStageTarget(
376 lt.d.decomposition(), stageFrom_, liftable, resolved_.minExtent, resolved_.maxBlockCells);
377 if (t.kind == cd::StageKind::SiblingMerge || t.kind == cd::StageKind::Repartition) {
378 using Topo = cd::RedistributeTopology<Dim, double>;
379 const int id =
380 static_cast<int>((levels_.size() - 1) % static_cast<std::size_t>(Topo::kTagSpan));
381 auto st = std::make_unique<DistributedStage<Dim, Bits>>();
382 st->build(lt.d, h0_, lt.ap, lt.n, t, id, bottomExtent_, resolved_, bottomKind_,
383 removeMean_);
384 stage_ = std::move(st);
385 return;
386 }
387 }
388 auto tail = std::make_unique<ReplicatedTailStage<Dim, Bits>>();
389 tail->build(lt.d, h0_, lt.ap, lt.n, lt.nExt, bottomExtent_, lt.comm);
390 tail->setRemoveMean(removeMean_);
391 tail->setBottom(bottomKind_);
392 stage_ = std::move(tail);
393 }
394
398 void setOpennessLocalRows(std::size_t L, const std::vector<double>& alphaLocal) {
399 Level& lv = *levels_[L];
400 const int F = 2 * Dim;
401 const Index nl = lv.n;
402 const Index ng = lv.hp->numGhosts();
403 if (static_cast<Index>(alphaLocal.size()) != nl * F)
404 throw std::invalid_argument(
405 "amr::DistributedFlowMultigrid::buildRaw: alpha must hold numLeaves()*2*Dim local rows");
406 std::vector<double> ca(static_cast<std::size_t>(nl + ng) * F, 1.0);
407 std::copy(alphaLocal.begin(), alphaLocal.end(), ca.begin());
408 std::vector<double> col(static_cast<std::size_t>(lv.nExt), 0.0);
409 for (int fi = 0; fi < F; ++fi) {
410 for (Index i = 0; i < nl; ++i)
411 col[static_cast<std::size_t>(i)] = ca[static_cast<std::size_t>(i) * F + fi];
412 lv.hp->exchangeHost(col);
413 for (Index g = 0; g < ng; ++g)
414 ca[static_cast<std::size_t>(nl + g) * F + fi] = col[static_cast<std::size_t>(nl + g)];
415 }
416 lv.ap.setOpennessRaw(std::move(ca));
417 }
418
419 void buildImpl(const DO& finest, const Vec<Dim>& h0, const LeafHalo<Dim, Bits>* shared0,
420 bool liftRoot, Index bottomExtent) {
421 comm_ = finest.comm();
422 h0_ = h0;
423 bottomExtent_ = bottomExtent;
424 levels_.clear();
425 resolved_ = stagePolicy_;
426 if (resolved_.maxBlockCells < 0) { // §9.6: the largest finest block's LEAF count
427 long long nl = static_cast<long long>(finest.local().numLeaves()), mx = 0;
428 MPI_Allreduce(&nl, &mx, 1, MPI_LONG_LONG, MPI_MAX, comm_);
429 resolved_.maxBlockCells = static_cast<Index>(mx);
430 }
431 // Ladder of coarsened copies of the SAME distributed octree (decomposition preserved).
432 {
433 auto l0 = std::make_unique<Level>();
434 l0->d = finest;
435 levels_.push_back(std::move(l0));
436 for (;;) {
437 Octree c = levels_.back()->d.local();
438 const Index before = c.numLeaves();
439 const Index merged = c.coarsenIf([](Code, unsigned) { return true; });
440 if (merged == 0 || c.numLeaves() == before)
441 break;
442 auto lv = std::make_unique<Level>();
443 lv->d = levels_.back()->d;
444 lv->d.local() = std::move(c);
445 levels_.push_back(std::move(lv));
446 if (levels_.back()->d.local().numLeaves() == 1)
447 break;
448 }
449 // Pad to the global max level count (identity root-brick levels): the per-level halo
450 // point-to-points and build collectives must pair up across ranks, and the extra
451 // smoothing exactly mirrors COMM_SELF's already-coarse regions.
452 int nl = static_cast<int>(levels_.size()), gnl = nl;
453 MPI_Allreduce(&nl, &gnl, 1, MPI_INT, MPI_MAX, comm_);
454 while (static_cast<int>(levels_.size()) < gnl) {
455 auto lv = std::make_unique<Level>();
456 lv->d = levels_.back()->d;
457 levels_.push_back(std::move(lv));
458 }
459 // Below the root brick (docs/amr_mg_depth.md §6.2/§6.4). Every rank now sits at its root
460 // brick (the coarsenIf loop ran to exhaustion; the padded ranks repeat theirs), so the lift
461 // is a pure function of the global root grid and the ORB blocks — and it is LOCKSTEP: the
462 // depth is the MIN over ranks of the depth each rank's own block allows, Allreduced BEFORE
463 // any level is built. Every rank therefore appends the SAME number of levels and the
464 // per-level halo collectives below keep pairing up (the padding argument, extended by k).
465 if (liftRoot)
466 liftDepth_ = allowedLiftDepth(levels_.back()->d, bottomExtent);
467 for (Index j = 0; j < liftDepth_; ++j) {
468 auto lv = std::make_unique<Level>();
469 lv->d = levels_.back()->d; // copy: carries the decomposition AND the leaf set
470 lv->d.liftRoot(); // root brick halved, lmax + 1, codes/levels untouched
471 const Index before = lv->d.local().numLeaves();
472 const Index merged = lv->d.local().coarsenIf([](Code, unsigned) { return true; });
473 if (merged == 0 || lv->d.local().numLeaves() == before)
474 break; // defensive: a level that did not shrink would stall the ladder
475 levels_.push_back(std::move(lv));
476 }
477 // The level counts MUST be equal on every rank — every per-level halo build and exchange
478 // below is a collective. Cheap (once per build) and a hard error, not an assert.
479 {
480 int nlv = static_cast<int>(levels_.size()), lo = 0, hi = 0;
481 MPI_Allreduce(&nlv, &lo, 1, MPI_INT, MPI_MIN, comm_);
482 MPI_Allreduce(&nlv, &hi, 1, MPI_INT, MPI_MAX, comm_);
483 if (lo != hi)
484 throw std::runtime_error(
485 "amr::DistributedFlowMultigrid: the pressure ladder has different level counts on "
486 "different ranks (the lift is not lockstep)");
487 }
488 }
489 // Per level: seam install + discovery fixpoint + topology freeze. Collective per level —
490 // every rank walks its levels in the same order (counts padded), so the resolveMisses /
491 // coverLevels rounds stay matched.
492 const Vec<Dim> gorigin = finest.globalGeometry().origin;
493 std::array<long, Dim> shift{};
494 for (int a = 0; a < Dim; ++a)
495 shift[a] = finest.blockFineOrigin()[a];
496 shift_ = shift;
497 bool first = true;
498 for (auto& lvp : levels_) {
499 Level& lv = *lvp;
500 lv.comm = comm_; // the octree's communicator: a stage's sub-communicator for a continuation
501 lv.n = lv.d.local().numLeaves();
502 lv.ap.init(lv.d.local(), h0_);
503 lv.ap.setOrigin(gorigin);
504 lv.ap.setFrameShift(shift);
505 // Install the ghost metadata of a registry into ap (level/lo lookups during the walks).
506 auto installGhosts = [&](const LeafHalo<Dim, Bits>& h) {
507 std::vector<std::array<long, Dim>> glo(static_cast<std::size_t>(h.numGhosts()));
508 std::vector<unsigned> glv(static_cast<std::size_t>(h.numGhosts()));
509 for (Index g = 0; g < h.numGhosts(); ++g) {
510 for (int a = 0; a < Dim; ++a)
511 glo[static_cast<std::size_t>(g)][a] = static_cast<long>(h.ghostCoord(g)[a]) - shift[a];
512 glv[static_cast<std::size_t>(g)] = static_cast<unsigned>(h.level(h.numLocal() + g));
513 }
514 lv.ap.setGhosts(std::move(glo), std::move(glv));
515 };
516 if (first && shared0) {
517 // Level 0 on the flow's frozen ±2 registry: same extended layout as the flow's cell
518 // vectors; every ±1 face probe is already cached ⇒ const lookups, no discovery.
519 lv.hp = shared0;
520 lv.ap.setResolver([shared0, shift](const std::array<long, Dim>& p) -> Index {
521 std::array<long, Dim> g = p;
522 for (int a = 0; a < Dim; ++a)
523 g[a] += shift[a];
524 return shared0->lookupGlobal(g);
525 });
527 } else {
528 lv.hp = &lv.halo;
529 lv.halo.init(lv.d);
530 LeafHalo<Dim, Bits>* hp = &lv.halo;
531 lv.ap.setResolver([hp, shift](const std::array<long, Dim>& p) -> Index {
532 std::array<long, Dim> g = p;
533 for (int a = 0; a < Dim; ++a)
534 g[a] += shift[a];
535 return hp->resolveGlobal(g);
536 });
537 // Ghosts must be (re-)installed into ap at the TOP of every fixpoint round: a probe
538 // that resolved in an earlier round returns its ghost slot immediately, and probeSlot
539 // then reads ap.levelOf(slot) — which must already cover it (newly-PENDING coords are
540 // fine: they return kPending and are skipped until the next round).
541 for (;;) {
542 installGhosts(lv.halo);
543 for (Index i = 0; i < lv.n; ++i)
544 lv.ap.forEachFaceNeighbor(i, [](Index, Real, int, double) {});
545 if (lv.halo.resolveMisses() == 0)
546 break;
547 }
548 lv.halo.finalize();
549 }
550 lv.nExt = lv.hp->extendedSize();
551 lv.ex.init(*lv.hp);
552 first = false;
553 }
554 // Local covering-leaf transfers (parents never cross blocks); identity on padded levels.
555 for (std::size_t L = 0; L + 1 < levels_.size(); ++L) {
556 const Octree& f = levels_[L]->d.local();
557 const Octree& c = levels_[L + 1]->d.local();
558 const Index nf = f.numLeaves(), nc = c.numLeaves();
559 std::vector<Index>& c2p = levels_[L]->c2pHost;
560 c2p.assign(static_cast<std::size_t>(nf), -1);
561 std::vector<Index> cnt(static_cast<std::size_t>(nc), 0);
562 for (Index i = 0; i < nf; ++i) {
563 const Index p = c.find(f.code(i)); // covering construction (== single-rank Multigrid)
564 c2p[static_cast<std::size_t>(i)] = p;
565 if (p >= 0)
566 ++cnt[static_cast<std::size_t>(p)];
567 }
568 std::vector<Index> start(static_cast<std::size_t>(nc) + 1, 0);
569 for (Index p = 0; p < nc; ++p)
570 start[static_cast<std::size_t>(p) + 1] =
571 start[static_cast<std::size_t>(p)] + cnt[static_cast<std::size_t>(p)];
572 std::vector<Index> idx(static_cast<std::size_t>(start[static_cast<std::size_t>(nc)]));
573 std::vector<Index> cur(start.begin(), start.end() - 1);
574 for (Index i = 0; i < nf; ++i) { // fine order ⇒ deterministic restrict accumulation
575 const Index p = c2p[static_cast<std::size_t>(i)];
576 if (p >= 0)
577 idx[static_cast<std::size_t>(cur[static_cast<std::size_t>(p)]++)] = i;
578 }
579 levels_[L]->c2p = toDevice(c2p, "dfmg_c2p");
580 levels_[L]->childStart = toDevice(start, "dfmg_cstart");
581 levels_[L]->childIdx = toDevice(idx, "dfmg_cidx");
582 }
583 }
584
585 // Area-average level-L face openness onto level L+1 — the EXACT AmrMultigrid::coarsenOpenness
586 // arithmetic for the local rows (children of a local coarse cell are local, summed in local
587 // Z-order == COMM_SELF's relative order ⇒ bit-identical), then ghost α rows exchanged from
588 // the owners through the level-(L+1) halo (kFaces one-time host exchanges).
589 void coarsenOpennessTo(std::size_t L) {
590 const Octree& f = levels_[L]->d.local();
591 const Octree& c = levels_[L + 1]->d.local();
592 Poisson& capL = levels_[L]->ap;
593 const int F = 2 * Dim;
594 const Index nc = c.numLeaves();
595 const Index ngc = levels_[L + 1]->hp->numGhosts();
596 std::vector<double> ca(static_cast<std::size_t>(nc + ngc) * F, 0.0);
597 std::vector<int> cnt(static_cast<std::size_t>(nc) * F, 0);
598 const std::vector<Index>& c2p = levels_[L]->c2pHost;
599 for (Index i = 0; i < f.numLeaves(); ++i) {
600 const Index p = c2p[static_cast<std::size_t>(i)];
601 if (p < 0)
602 continue;
603 const std::size_t base = static_cast<std::size_t>(p) * F;
604 if (c.level(p) == f.level(i)) {
605 for (int axis = 0; axis < Dim; ++axis)
606 for (int dir = -1; dir <= 1; dir += 2) {
607 const int fi = Poisson::faceIndex(axis, dir);
608 ca[base + static_cast<std::size_t>(fi)] += capL.faceOpenness(i, axis, dir);
609 cnt[base + static_cast<std::size_t>(fi)] += 1;
610 }
611 } else {
612 const unsigned oct = M::from_code(f.code(i)).child_index(f.level(i));
613 for (int axis = 0; axis < Dim; ++axis) {
614 const int dir = ((oct >> axis) & 1) ? +1 : -1;
615 const int fi = Poisson::faceIndex(axis, dir);
616 ca[base + static_cast<std::size_t>(fi)] += capL.faceOpenness(i, axis, dir);
617 cnt[base + static_cast<std::size_t>(fi)] += 1;
618 }
619 }
620 }
621 for (std::size_t k = 0; k < static_cast<std::size_t>(nc) * F; ++k)
622 ca[k] = cnt[k] ? ca[k] / cnt[k] : 1.0;
623 // Ghost rows from the owners (their local rows were computed by the identical arithmetic).
624 const LeafHalo<Dim, Bits>& h = *levels_[L + 1]->hp;
625 std::vector<double> col(static_cast<std::size_t>(levels_[L + 1]->nExt), 0.0);
626 for (int fi = 0; fi < F; ++fi) {
627 for (Index i = 0; i < nc; ++i)
628 col[static_cast<std::size_t>(i)] = ca[static_cast<std::size_t>(i) * F + fi];
629 h.exchangeHost(col);
630 for (Index g = 0; g < ngc; ++g)
631 ca[static_cast<std::size_t>(nc + g) * F + fi] = col[static_cast<std::size_t>(nc + g)];
632 }
633 levels_[L + 1]->ap.setOpennessRaw(std::move(ca));
634 }
635
636 // Host-assemble each level's operator through the resolver seam and upload (the distributed
637 // mirror of the D5 device assembly — same CSR, ghost columns included), then the scratch.
638 void finishOps() {
639 for (auto& lvp : levels_) {
640 Level& lv = *lvp;
641 auto A = lv.ap.assembleFv();
642 lv.op.n = lv.n;
643 lv.op.invVol = toDevice(A.invVol, "dfmg_invvol");
644 lv.op.faceStart = toDevice(A.start, "dfmg_start");
645 lv.op.faceNbr = toDevice(A.nbr, "dfmg_nbr");
646 lv.op.faceW = toDevice(A.coef, "dfmg_w");
647 lv.op.bcDiag = toDevice(A.bcDiag, "dfmg_bc");
648 lv.x = View<double>("dfmg_x", static_cast<std::size_t>(lv.nExt));
649 lv.b = View<double>("dfmg_b", static_cast<std::size_t>(lv.nExt));
650 lv.res = View<double>("dfmg_res", static_cast<std::size_t>(lv.n));
651 lv.tmp = View<double>("dfmg_tmp", static_cast<std::size_t>(lv.n));
652 Kokkos::deep_copy(lv.x, 0.0);
653 Kokkos::deep_copy(lv.b, 0.0);
654 }
655 }
656
659 static Index allowedLiftDepth(const DO& d, Index bottomExtent) {
660 IVec<Dim> G = d.globalRootSize(), o = d.blockOriginRoot(), b = d.blockBrick();
661 int kLocal = 0;
662 for (;;) {
663 Index mx = 0;
664 for (int a = 0; a < Dim; ++a)
665 mx = std::max(mx, G[a]);
666 if (mx <= bottomExtent)
667 break; // the bottom smoother is already exact here (§6.6)
668 bool ok = true;
669 for (int a = 0; a < Dim; ++a)
670 if ((G[a] % 2) != 0 || (G[a] / 2) < 2 || (o[a] % 2) != 0 || (b[a] % 2) != 0) {
671 ok = false; // grid-limited, or decomposition-limited (this rank's block turns odd)
672 break;
673 }
674 if (!ok)
675 break;
676 for (int a = 0; a < Dim; ++a) {
677 G[a] /= 2;
678 o[a] /= 2;
679 b[a] /= 2;
680 }
681 ++kLocal;
682 }
683 int kGlobal = kLocal;
684 MPI_Allreduce(&kLocal, &kGlobal, 1, MPI_INT, MPI_MIN, d.comm());
685 return static_cast<Index>(kGlobal);
686 }
687
688 MPI_Comm comm_ = MPI_COMM_NULL;
689 Vec<Dim> h0_ = detail::filledVec<Dim>(1.0);
690 Index bottomExtent_ = 4;
691 Index liftDepth_ = 0;
692 IVec<Dim> stageFrom_{};
694 std::unique_ptr<MgStage<Dim, Bits>> stage_;
697 std::unique_ptr<AmgBottom<Dim, Bits>> bottomAmg_;
698 std::array<long, Dim> shift_{};
699 std::vector<std::unique_ptr<Level>> levels_;
700 bool removeMean_ = false;
701 StagePolicy stagePolicy_{};
702 StagePolicy resolved_{}; // stagePolicy_ with maxBlockCells resolved at the last build
703};
704
722template <int Dim, unsigned Bits>
723class DistributedStage final : public MgStage<Dim, Bits> {
724 public:
726 using Octree = typename Base::Octree;
731 using Policy = typename MG::StagePolicy;
732
742 void build(const DO& d, const Vec<Dim>& h0, const Poisson& ap, Index n,
743 const core::decomp::StageTarget<Dim>& target, int id, Index bottomExtent,
744 const Policy& policy, typename Multigrid<Dim, Bits>::Bottom bottom, bool removeMean) {
745 namespace cd = core::decomp;
746 if (target.kind != cd::StageKind::SiblingMerge && target.kind != cd::StageKind::Repartition)
747 throw std::invalid_argument(
748 "amr::DistributedStage: only sibling-merge and repartition targets continue a "
749 "distributed ladder");
750 for (int a = 0; a < Dim; ++a)
751 if (!d.periodic()[a])
752 throw std::runtime_error(
753 "amr::DistributedStage: periodic-only (as the whole distributed pressure path is)");
754 Index blockCells = 1;
755 for (int a = 0; a < Dim; ++a)
756 blockCells *= d.blockBrick()[a];
757 if (n != blockCells || n != d.local().numLeaves())
758 throw std::runtime_error(
759 "amr::DistributedStage: the moved level does not tile its block with root cells (the "
760 "lift is not nested)");
761 parent_ = d.comm();
762 target_ = target;
763 n_ = n;
764 comm_ = cd::makeStageComm<Dim>(parent_, target_);
765 if (comm_.active)
766 tdo_.initDecomposed(target_.dec, d.lmax(), d.globalGeometry(), d.periodic(), comm_.sub);
767 nT_ = comm_.active ? tdo_.local().numLeaves() : 0;
768
769 auto srcIndex = [&](const IVec<Dim>& g) -> Index {
770 const Index i = d.findGlobalRoot(g);
771 if (i < 0 || d.local().level(i) != d.lmax())
772 throw std::runtime_error(
773 "amr::DistributedStage: a cell of this rank's block is not one of its root leaves (the "
774 "lift is not nested)");
775 return i;
776 };
777 auto dstIndex = [&](const IVec<Dim>& g) -> Index { return tdo_.findGlobalRoot(g); };
778 topo_.build(d.decomposition(), target_, comm_, srcIndex, dstIndex, id);
779
780 // The moved level's α rows, one field per face, onto the target block.
781 const int F = 2 * Dim;
782 const std::vector<double>& aloc = ap.opennessRaw();
783 std::vector<std::vector<double>> aCol(static_cast<std::size_t>(F),
784 std::vector<double>(static_cast<std::size_t>(n), 1.0));
785 if (!aloc.empty())
786 for (Index i = 0; i < n; ++i)
787 for (int f = 0; f < F; ++f)
788 aCol[static_cast<std::size_t>(f)][static_cast<std::size_t>(i)] =
789 aloc[static_cast<std::size_t>(i) * F + static_cast<std::size_t>(f)];
790 std::vector<std::vector<double>> tCol(static_cast<std::size_t>(F),
791 std::vector<double>(static_cast<std::size_t>(nT_), 1.0));
792 std::vector<const double*> aSrc(static_cast<std::size_t>(F));
793 std::vector<double*> aDst;
794 for (int f = 0; f < F; ++f)
795 aSrc[static_cast<std::size_t>(f)] = aCol[static_cast<std::size_t>(f)].data();
796 if (comm_.active)
797 for (int f = 0; f < F; ++f)
798 aDst.push_back(tCol[static_cast<std::size_t>(f)].data());
799 topo_.forward(aSrc, aDst);
800
801 cont_.reset();
802 if (comm_.active) {
803 std::vector<double> aT(static_cast<std::size_t>(nT_) * F);
804 for (Index r = 0; r < nT_; ++r)
805 for (int f = 0; f < F; ++f)
806 aT[static_cast<std::size_t>(r) * F + static_cast<std::size_t>(f)] =
807 tCol[static_cast<std::size_t>(f)][static_cast<std::size_t>(r)];
808 cont_ = std::make_unique<MG>();
809 cont_->setStagePolicy(policy);
810 cont_->setBottom(bottom); // before build: only stored
811 cont_->buildRaw(tdo_, h0, aT, /*liftRoot=*/true, bottomExtent);
812 cont_->setRemoveMean(removeMean);
813 }
814 publish();
815
816 dSrc_ = View<double>("dstage_src", static_cast<std::size_t>(n));
817 dDst_ = View<double>("dstage_dst", static_cast<std::size_t>(n));
818 dT_ = View<double>("dstage_t", static_cast<std::size_t>(nT_));
819 srcMirror_ = Kokkos::View<double*, Kokkos::HostSpace>("dstage_hs", static_cast<std::size_t>(n));
820 dstMirror_ = Kokkos::View<double*, Kokkos::HostSpace>("dstage_hd", static_cast<std::size_t>(n));
821 tMirror_ = Kokkos::View<double*, Kokkos::HostSpace>("dstage_ht", static_cast<std::size_t>(nT_));
822 }
823
824 const char* kind() const override {
825 return target_.kind == core::decomp::StageKind::SiblingMerge ? "sibling" : "repartition";
826 }
827 const char* diagnosticSuffix() const override {
828 return target_.kind == core::decomp::StageKind::SiblingMerge ? "+sibling" : "+repartition";
829 }
830 const Decomposition& targetDecomposition() const override { return target_.dec; }
831 MPI_Comm targetComm() const override { return comm_.sub; }
832 bool active() const override { return comm_.active; }
833 const Octree& targetOctree() const override { return tdo_.local(); }
834
835 std::size_t numLevels() const override { return numLevels_; }
836 Index numLeaves(std::size_t L) const override {
837 if (!cont_)
838 return 0;
839 const std::size_t k = cont_->numInPlaceLevels();
840 return L < k ? cont_->numLeaves(L) : cont_->stageLeaves(L - k);
841 }
842 std::string bottomName() const override { return bottomName_; }
843 void setRemoveMean(bool on) override {
844 if (cont_)
845 cont_->setRemoveMean(on);
846 }
849 void setBottom(typename Multigrid<Dim, Bits>::Bottom b) override {
850 if (cont_)
851 cont_->setBottom(b);
852 publish();
853 }
854 View<double> targetX() override { return cont_ ? cont_->x(0) : View<double>(); }
855
857 MG& multigrid() { return *cont_; }
858
860 auto ds = dSrc_;
861 Kokkos::parallel_for(
862 "amr::dstage_pack", nSrc, KOKKOS_LAMBDA(const Index i) { ds(i) = src(i); });
863 Kokkos::deep_copy(srcMirror_, dSrc_);
864 std::vector<double*> to;
865 if (comm_.active)
866 to.push_back(tMirror_.data());
867 topo_.forward({srcMirror_.data()}, to);
868 if (comm_.active) {
869 Kokkos::deep_copy(dT_, tMirror_);
870 auto b = cont_->b(0);
871 auto t = dT_;
872 Kokkos::parallel_for("amr::dstage_b", nT_, KOKKOS_LAMBDA(const Index i) { b(i) = t(i); });
873 Kokkos::deep_copy(cont_->x(0), 0.0);
874 }
875 }
876
877 void cycle(int pre, int post, int bottom, double omega) override {
878 cont_->vcycle(pre, post, bottom, omega);
879 }
880
882 std::vector<const double*> from;
883 if (comm_.active) {
884 auto x = cont_->x(0);
885 auto t = dT_;
886 Kokkos::parallel_for("amr::dstage_x", nT_, KOKKOS_LAMBDA(const Index i) { t(i) = x(i); });
887 Kokkos::deep_copy(tMirror_, dT_);
888 from.push_back(tMirror_.data());
889 }
890 topo_.backward(from, {dstMirror_.data()});
891 Kokkos::deep_copy(dDst_, dstMirror_);
892 auto dd = dDst_;
893 Kokkos::parallel_for(
894 "amr::dstage_scatter", nDst, KOKKOS_LAMBDA(const Index i) { dst(i) = dd(i); });
895 }
896
897 private:
900 void publish() {
901 long long nl = 0;
902 std::string nm;
903 if (cont_) {
904 nl = static_cast<long long>(cont_->numInPlaceLevels() + cont_->numStageLevels());
905 nm = cont_->bottomName();
906 }
907 MPI_Bcast(&nl, 1, MPI_LONG_LONG, 0, parent_);
908 int len = static_cast<int>(nm.size());
909 MPI_Bcast(&len, 1, MPI_INT, 0, parent_);
910 nm.resize(static_cast<std::size_t>(len));
911 MPI_Bcast(nm.data(), len, MPI_CHAR, 0, parent_);
912 numLevels_ = static_cast<std::size_t>(nl);
913 bottomName_ = nm;
914 }
915
916 MPI_Comm parent_ = MPI_COMM_NULL;
917 core::decomp::StageTarget<Dim> target_;
918 core::decomp::StageComm comm_; // owns group/sub; outlives topo_'s use and cont_'s halos
919 core::decomp::RedistributeTopology<Dim, double> topo_;
920 DO tdo_; // the moved level on this rank's target block (active only)
921 std::unique_ptr<MG> cont_; // the continued ladder on comm_.sub (active only)
922 Index n_ = 0, nT_ = 0;
923 std::size_t numLevels_ = 0;
924 std::string bottomName_;
925 View<double> dSrc_, dDst_, dT_;
926 Kokkos::View<double*, Kokkos::HostSpace> srcMirror_, dstMirror_, tMirror_;
927};
928
929} // namespace peclet::amr
930
931#endif // PECLET_AMR_DISTRIBUTED_FLOW_MG_HPP
static int faceIndex(int axis, int dir)
Definition poisson.hpp:147
const std::vector< double > & opennessRaw() const
Definition poisson.hpp:157
unsigned level(Index i) const
const char * stageKind() const
"replicated", "sibling" or "repartition", or "none" without a stage.
Index extendedSize(std::size_t L=0) const
std::size_t numStageLevels() const
Levels of the continued ladder (its level 0 IS the moved level, so the full ladder is numInPlaceLevel...
Index liftDepth() const
How many levels the ladder gained BELOW the root brick by lifting (§6.2).
std::string bottomName() const
"jacobi" | "amg", suffixed by the stage's own spelling — "+tail" for the replicated one (docs/amr_mg_...
View< double > stageSolution()
The moved level's solution on THIS rank's target block.
Index stageLeaves(std::size_t L=0) const
Cells of continued-ladder level L on this rank's target block (GLOBAL counts for the replicated stage...
const FvOp & op(std::size_t L=0) const
void setBottom(typename Multigrid< Dim, Bits >::Bottom b)
Select what solves the coarsest level (docs/amr_mg_depth.md §6.6).
bool hasStage() const
Whether a STAGE fires below the in-place ladder (docs/amr_mg_depth.md §5.6/§6.5): the coarsest in-pla...
void setRemoveMean(bool on)
Per-level nullspace projection for the singular (periodic pure-Neumann) pressure.
void build(const DO &finest, const Vec< Dim > &h0, OpenFn &&openFn, const LeafHalo< Dim, Bits > *shared0=nullptr, bool liftRoot=true, Index bottomExtent=4)
Phase 3: the finest spacing per axis; every level inherits the aspect ratio (AM1).
MgStage< Dim, Bits > & stage()
The stage itself, for tests and diagnostics.
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 (correction scheme), the distributed mirror of Multigrid::vcycle: ghost refres...
PressureStagePolicy StagePolicy
Which stage fires where the in-place ladder blocks (docs/amr_mg_core_boundary.md §11....
const LeafHalo< Dim, Bits > & halo(std::size_t L=0) const
void sync(std::size_t L, View< double > v) const
Refresh the ghost tail of a level-L vector (the PCG matvec hook uses level 0).
const Poisson & poisson(std::size_t L=0) const
std::size_t numInPlaceLevels() const
How many of those levels are IN PLACE — octree coarsenings plus lifted levels, i.e.
void buildRaw(const DO &finest, const Vec< Dim > &h0, const std::vector< double > &alphaLocal, bool liftRoot=true, Index bottomExtent=4)
Build on finest with its level-0 openness GIVEN as raw rows (AmrPoisson::opennessRaw() layout,...
void build(const DO &finest, double h0, OpenFn &&openFn, const LeafHalo< Dim, Bits > *shared0=nullptr, bool liftRoot=true, Index bottomExtent=4)
Build with cut-cell openness openFn(faceCentreWorld, axis) → [0,1] on the flow's distributed octree (...
const IVec< Dim > & stageFrom() const
The global grid the stage moves (the coarsest in-place level's extent).
const AmrGeometry< Dim > & globalGeometry() const
const std::array< bool, Dim > & periodic() const
void initDecomposed(const decomp::BlockDecomposer< Dim > &dec, unsigned lmax, AmrGeometry< Dim > globalGeo, std::array< bool, Dim > periodic, MPI_Comm comm)
As init, but on a GIVEN decomposition of the global root grid dec.globalSize() instead of the proport...
Index findGlobalRoot(const IVec< Dim > &g) const
Local leaf covering global root-cell g, or -1 if not owned by this rank (lmax==0).
const decomp::BlockDecomposer< Dim > & decomposition() const
The ORB over the global ROOT grid in the CURRENT root units — rank r owns block r.
const IVec< Dim > & blockBrick() const
A SIBLING-MERGE or REPARTITION stage (docs/amr_mg_depth.md §5.6, WO4b; docs/amr_mg_core_boundary....
Index numLeaves(std::size_t L) const override
Cells of continued-ladder level L on this rank's target block — the GLOBAL count for the replicated s...
void build(const DO &d, const Vec< Dim > &h0, const Poisson &ap, Index n, const core::decomp::StageTarget< Dim > &target, int id, Index bottomExtent, const Policy &policy, typename Multigrid< Dim, Bits >::Bottom bottom, bool removeMean)
Build the stage from the level it fires at (d, ap, n as for ReplicatedTailStage) onto target,...
typename Base::Octree Octree
a level's cells on one block
DistributedOctree< Dim, Bits > DO
the level the stage fires at
View< double > targetX() override
The continued ladder's level-0 solution — what moveDown reads.
typename Base::Decomposition Decomposition
the target partition's type
const char * kind() const override
"replicated" | "sibling" | "repartition".
bool active() const override
Whether THIS rank owns a target block, i.e.
std::size_t numLevels() const override
Levels of the continued ladder, its level 0 (the moved level) included. Local.
typename MG::StagePolicy Policy
inherited by the continued ladder
std::string bottomName() const override
"jacobi" | "amg": what solves the continued ladder's coarsest level, WITHOUT the stage's diagnosticSu...
void setRemoveMean(bool on) override
Per-level constant-nullspace projection for the singular periodic pressure; the owner forwards its ow...
void moveUp(View< const double > src, Index nSrc) override
src (this rank's rows of the moved level, device; may be longer than nSrc) → the target's level-0 rhs...
void cycle(int pre, int post, int bottom, double omega) override
One cycle of the continued ladder on targetComm(). Called on active ranks only.
MPI_Comm targetComm() const override
The communicator every collective of the continued ladder runs on.
const Decomposition & targetDecomposition() const override
The decomposition of the moved level's grid this stage targets.
const char * diagnosticSuffix() const override
What pressure_mg_bottom appends for this stage — "+tail" for the replicated one, which is the spellin...
const Octree & targetOctree() const override
The moved level's cells on this rank's target block.
void setBottom(typename Multigrid< Dim, Bits >::Bottom b) override
Collective on the PARENT communicator (it re-publishes the continued ladder's shape,...
MG & multigrid()
The continued ladder (active ranks only).
void moveDown(View< double > dst, Index nDst) override
The target's level-0 solution → the first nDst rows of dst.
Device-resident value refresh over a finalized LeafHalo: pack the owner's local values as a Kokkos ke...
One stage of the pressure hierarchy (docs/amr_mg_depth.md §6.5).
Definition mg_stage.hpp:60
BlockOctree< Dim, Bits > Octree
a level's cells on one block
Definition mg_stage.hpp:62
core::decomp::BlockDecomposer< Dim > Decomposition
the target partition's type
Definition mg_stage.hpp:63
Bottom
What solves the coarsest level (docs/amr_mg_depth.md §6.6).
void removeMeanFvDist(const FvOp &op, View< double > u, MPI_Comm comm)
removeMeanFv with the two mean sums folded across ranks (identical kernels; the Allreduce sits betwee...
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 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 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
View< Index > faceStart
CSR row offsets, size n+1.
Definition fv_op.hpp:80
View< double > bcDiag
Dirichlet boundary diagonal per cell (0 if periodic), size n.
Definition fv_op.hpp:83
View< double > invVol
1/V_i, size n
Definition fv_op.hpp:79
View< double > faceW
w_f = openness·A_f/d_f per face, size nFaces
Definition fv_op.hpp:82
Which stage the distributed pressure ladder uses where its in-place lift blocks (docs/amr_mg_depth....
bool enabled
false: the replicated tail only (WO4)
Index maxBlockCells
Core's bound on a target block's cells.
int minExtent
Core's economic trigger / extent cap.