core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
distributed_fv.hpp
Go to the documentation of this file.
1// core — consistent graded FV Laplacian on a *distributed* octree.
2//
3// The distributed analog of AmrPoisson / Multigrid's consistent operator:
4// the conservative two-point FV Laplacian
5// (L u)_i = invVol_i · Σ_f w_f (u[nbr_f] − u_i), w_f = A_f / d_f,
6// on a graded DistributedOctree (refined + cross-block 2:1 balanced), with the
7// coarse side of every 2:1 interface carrying one face per fine sub-neighbour —
8// including sub-neighbours that live on another rank.
9//
10// Topology built once: each leaf's face stencil is enumerated exactly like
11// AmrPoisson::forEachFaceNeighbor (same axis/dir/sub-k order), with in-block
12// neighbours resolved locally and cross-block neighbours turned into *ghost*
13// entries. Ghost cells are discovered by one owner gather of the across-face
14// covering level (DistributedOctree::coverLevels) — enough to know whether a
15// remote face is same/coarser (one neighbour) or finer (2^(Dim-1) sub-neighbours,
16// each possibly on a different owner). Each matvec then fills the ghost values with
17// one owner gather (coverValues). Because the per-entry term w(val−u_i) is summed
18// in the same face order whether a neighbour is local or ghost, the operator is
19// bit-identical across rank counts (COMM_WORLD == COMM_SELF) and, on a single
20// block, bit-identical to host AmrPoisson::applyLaplacian.
21//
22// Scope: openness-free (w_f = A_f/d_f); the smoother is weighted Jacobi. Openness
23// (cut-cell) and a graded distributed *multigrid* hierarchy build on this.
24//
25// Header-only, guarded by PECLET_CORE_HAVE_MORTON.
26#ifndef PECLET_CORE_AMR_DISTRIBUTED_FV_HPP
27#define PECLET_CORE_AMR_DISTRIBUTED_FV_HPP
28
29#ifdef PECLET_CORE_HAVE_MORTON
30
31#include <array>
32#include <cmath>
33#include <map>
34#include <memory>
35#include <vector>
36
41
42namespace peclet::core::amr {
43
44template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
46 public:
48 using M = typename DO::M;
49 using Code = typename DO::Code;
50 using Coord = typename DO::Coord;
51
53 void init(DO& d) {
54 init(d, [](const Vec<Dim>&, int) { return 1.0; });
55 }
56
63 template <class OpenFn>
64 void init(DO& d, OpenFn&& openFn) {
65 d_ = &d;
66 h0_ = d.h0();
67 const auto& t = d.local();
68 const Index n = t.numLeaves();
69 const IVec<Dim> bfo = d.blockFineOrigin();
70 const Vec<Dim> org = d.globalGeometry().origin;
71 invVol_.assign(static_cast<std::size_t>(n), 0.0);
72 diag_.assign(static_cast<std::size_t>(n), 0.0);
73
74 // α at the finer side's (sub-)face: flo = finer cell global fine lower corner,
75 // s its size (fine units), faceDir its outward normal toward the neighbour.
76 auto alphaOf = [&](const std::array<long, Dim>& flo, Coord s, int axis, int faceDir) -> double {
77 Vec<Dim> fc{};
78 long plane = flo[axis] + (faceDir > 0 ? static_cast<long>(s) : 0L);
79 for (int d2 = 0; d2 < Dim; ++d2)
80 fc[d2] =
81 (d2 == axis)
82 ? org[d2] + static_cast<double>(plane) * h0_
83 : org[d2] + (static_cast<double>(flo[d2]) + 0.5 * static_cast<double>(s)) * h0_;
84 double a = openFn(fc, axis);
85 return a < 0.0 ? 0.0 : (a > 1.0 ? 1.0 : a);
86 };
87 auto gLo = [&](const std::array<Coord, Dim>& lc) {
88 std::array<long, Dim> g{};
89 for (int d2 = 0; d2 < Dim; ++d2)
90 g[d2] = static_cast<long>(lc[d2]) + static_cast<long>(bfo[d2]);
91 return g;
92 };
93 // Global fine lower corner of the k-th fine sub-neighbour across a coarse cell's
94 // (axis,dir) face: clo is the coarse cell's global lo, si its size, sj=si/2. Used
95 // for the sub-face α centroid — the *finer* cell's lo, not the probe point.
96 auto fineLoGlobal = [&](const std::array<long, Dim>& clo, Coord si, Coord sj, int axis, int dir,
97 int k) {
98 std::array<long, Dim> flo{};
99 flo[axis] = (dir > 0) ? clo[axis] + static_cast<long>(si) : clo[axis] - static_cast<long>(sj);
100 int bit = 0;
101 for (int t = 0; t < Dim; ++t) {
102 if (t == axis)
103 continue;
104 flo[t] = clo[t] + (((k >> bit) & 1) ? static_cast<long>(sj) : 0L);
105 ++bit;
106 }
107 for (int d2 = 0; d2 < Dim; ++d2) {
108 long gf = static_cast<long>(d.globalFineSize()[d2]);
109 if (flo[d2] < 0)
110 flo[d2] += gf;
111 else if (flo[d2] >= gf)
112 flo[d2] -= gf;
113 }
114 return flo;
115 };
116
117 // Pass 1: per-leaf entries in face order; remote faces become markers.
118 struct E {
119 double w = 0.0;
120 Index localRef = -1; // >=0 local neighbour slot
121 int rfId = -1; // >=0 unresolved remote face
122 };
123 std::vector<std::vector<E>> ent(static_cast<std::size_t>(n));
124 struct RFace {
125 std::array<Coord, Dim> gc{};
126 int Li = 0;
127 Coord si = 1;
128 int axis = 0;
129 int dir = 1;
130 std::array<long, Dim> iLo{}; // requesting leaf's global fine lower corner
131 };
132 std::vector<RFace> rfaces;
133
134 for (Index i = 0; i < n; ++i) {
135 const int Li = static_cast<int>(t.level(i));
136 const Coord si = Coord(Coord(1) << Li);
137 invVol_[static_cast<std::size_t>(i)] = 1.0 / cellVol(Li);
138 auto b = t.bounds(i);
139 const auto& lo = b[0];
140 for (int axis = 0; axis < Dim; ++axis)
141 for (int dir = -1; dir <= 1; dir += 2) {
142 typename DO::FaceInfo fi = d.faceAcross(i, axis, dir);
143 if (fi.state == 2)
144 continue; // DomainNone
145 if (fi.state == 0) { // InBlock: enumerate locally
146 Index j = fi.localNb;
147 if (j < 0)
148 continue;
149 const int Lj = static_cast<int>(t.level(j));
150 if (Lj >= Li) { // finer side is i: α at i's face
151 const double a = alphaOf(gLo(lo), si, axis, dir);
152 ent[static_cast<std::size_t>(i)].push_back(
153 E{a * coeff(si, Coord(Coord(1) << Lj)), j, -1});
154 } else { // finer side is the sub-neighbour jj: α at jj's face (−dir)
155 const Coord sj = Coord(si >> 1);
156 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
157 : static_cast<long>(lo[axis]) - 1;
158 const int nsub = 1 << (Dim - 1);
159 for (int k = 0; k < nsub; ++k) {
160 std::array<Coord, Dim> q = lo;
161 q[axis] = static_cast<Coord>(pc);
162 int bit = 0;
163 for (int tt = 0; tt < Dim; ++tt) {
164 if (tt == axis)
165 continue;
166 const Coord off = ((k >> bit) & 1) ? sj : Coord(0);
167 q[tt] = static_cast<Coord>(lo[tt] + off);
168 ++bit;
169 }
170 Index jj = t.find(M::encode(q).code());
171 const double a =
172 alphaOf(fineLoGlobal(gLo(lo), si, sj, axis, dir, k), sj, axis, -dir);
173 ent[static_cast<std::size_t>(i)].push_back(E{a * coeff(si, sj), jj, -1});
174 }
175 }
176 } else { // Remote: defer (need covering level)
177 E e;
178 e.rfId = static_cast<int>(rfaces.size());
179 ent[static_cast<std::size_t>(i)].push_back(e);
180 RFace rf{fi.gc, Li, si, axis, dir, {}};
181 rf.iLo = gLo(lo);
182 rfaces.push_back(rf);
183 }
184 }
185 }
186
187 // One owner gather of the across-face covering level for every remote face.
188 std::vector<std::array<Coord, Dim>> coverCoords(rfaces.size());
189 for (std::size_t r = 0; r < rfaces.size(); ++r)
190 coverCoords[r] = rfaces[r].gc;
191 std::vector<int> coverLv = d.coverLevels(coverCoords);
192
193 // Pass 2: expand into the final CSR; ghost coords deduped to ghost slots.
194 std::map<std::array<Coord, Dim>, Index> ghostSlot;
195 std::vector<std::array<Coord, Dim>> ghostCoords;
196 auto ghostRef = [&](const std::array<Coord, Dim>& gc) -> Index {
197 auto it = ghostSlot.find(gc);
198 if (it != ghostSlot.end())
199 return it->second;
200 Index s = static_cast<Index>(ghostCoords.size());
201 ghostSlot.emplace(gc, s);
202 ghostCoords.push_back(gc);
203 return s;
204 };
205
206 start_.assign(static_cast<std::size_t>(n) + 1, 0);
207 for (Index i = 0; i < n; ++i) {
208 for (const E& e : ent[static_cast<std::size_t>(i)]) {
209 if (e.rfId < 0) {
210 ref_.push_back(e.localRef);
211 w_.push_back(e.w);
212 diag_[static_cast<std::size_t>(i)] += e.w;
213 } else {
214 const RFace& rf = rfaces[static_cast<std::size_t>(e.rfId)];
215 const int Lj = coverLv[static_cast<std::size_t>(e.rfId)];
216 if (Lj < 0)
217 continue; // no neighbour
218 if (Lj >= rf.Li) { // finer side is i: α at i's face
219 const double a = alphaOf(rf.iLo, rf.si, rf.axis, rf.dir);
220 const double ww = a * coeff(rf.si, Coord(Coord(1) << Lj));
221 ref_.push_back(d_->local().numLeaves() + ghostRef(rf.gc));
222 w_.push_back(ww);
223 diag_[static_cast<std::size_t>(i)] += ww;
224 } else { // finer side is each remote sub-neighbour: α at its face (−dir)
225 const Coord sj = Coord(rf.si >> 1);
226 const int nsub = 1 << (Dim - 1);
227 for (int k = 0; k < nsub; ++k) {
228 std::array<Coord, Dim> sc = subCoord(rf.gc, rf.axis, k, sj);
229 const double a = alphaOf(fineLoGlobal(rf.iLo, rf.si, sj, rf.axis, rf.dir, k), sj,
230 rf.axis, -rf.dir);
231 const double ww = a * coeff(rf.si, sj);
232 ref_.push_back(d_->local().numLeaves() + ghostRef(sc));
233 w_.push_back(ww);
234 diag_[static_cast<std::size_t>(i)] += ww;
235 }
236 }
237 }
238 }
239 start_[static_cast<std::size_t>(i) + 1] = static_cast<Index>(ref_.size());
240 }
241 ghostCoords_ = std::move(ghostCoords);
242 n_ = n;
243 }
244
245 Index numLeaves() const { return n_; }
246
248 void apply(const std::vector<double>& u, std::vector<double>& Lu) const {
249 std::vector<double> g = d_->coverValues(ghostCoords_, u);
250 Lu.assign(static_cast<std::size_t>(n_), 0.0);
251 for (Index i = 0; i < n_; ++i) {
252 const double ui = u[static_cast<std::size_t>(i)];
253 double acc = 0.0;
254 for (Index k = start_[static_cast<std::size_t>(i)];
256 const Index r = ref_[static_cast<std::size_t>(k)];
257 const double val =
258 (r < n_) ? u[static_cast<std::size_t>(r)] : g[static_cast<std::size_t>(r - n_)];
259 acc += w_[static_cast<std::size_t>(k)] * (val - ui);
260 }
261 Lu[static_cast<std::size_t>(i)] = invVol_[static_cast<std::size_t>(i)] * acc;
262 }
263 }
264
266 double residual(const std::vector<double>& u, const std::vector<double>& rhs,
267 std::vector<double>& res) const {
268 std::vector<double> lu;
269 apply(u, lu);
270 res.assign(static_cast<std::size_t>(n_), 0.0);
271 double s = 0.0;
272 for (Index i = 0; i < n_; ++i) {
273 double r = rhs[static_cast<std::size_t>(i)] - lu[static_cast<std::size_t>(i)];
274 res[static_cast<std::size_t>(i)] = r;
275 s += r * r / invVol_[static_cast<std::size_t>(i)]; // V_i r²
276 }
277 double g = 0.0;
278 MPI_Allreduce(&s, &g, 1, MPI_DOUBLE, MPI_SUM, d_->comm());
279 return std::sqrt(g);
280 }
281
282 double residualNorm(const std::vector<double>& u, const std::vector<double>& rhs) const {
283 std::vector<double> res;
284 return residual(u, rhs, res);
285 }
286
290 void jacobi(std::vector<double>& u, const std::vector<double>& rhs, int sweeps,
291 double omega = 0.8) const {
292 std::vector<double> tmp(static_cast<std::size_t>(n_));
293 for (int s = 0; s < sweeps; ++s) {
294 std::vector<double> g = d_->coverValues(ghostCoords_, u);
295 for (Index i = 0; i < n_; ++i) {
296 double sumOff = 0.0;
297 for (Index k = start_[static_cast<std::size_t>(i)];
299 const Index r = ref_[static_cast<std::size_t>(k)];
300 const double val =
301 (r < n_) ? u[static_cast<std::size_t>(r)] : g[static_cast<std::size_t>(r - n_)];
302 sumOff += w_[static_cast<std::size_t>(k)] * val;
303 }
304 const double Vrhs = rhs[static_cast<std::size_t>(i)] / invVol_[static_cast<std::size_t>(i)];
305 tmp[static_cast<std::size_t>(i)] =
306 (diag_[static_cast<std::size_t>(i)] != 0.0)
307 ? (sumOff - Vrhs) / diag_[static_cast<std::size_t>(i)]
308 : u[static_cast<std::size_t>(i)];
309 }
310 for (Index i = 0; i < n_; ++i)
311 u[static_cast<std::size_t>(i)] = (1.0 - omega) * u[static_cast<std::size_t>(i)] +
312 omega * tmp[static_cast<std::size_t>(i)];
313 }
314 }
315
316 private:
317 double cellWidth(int level) const { return h0_ * static_cast<double>(Index(1) << level); }
318 double cellVol(int level) const {
319 double w = cellWidth(level), v = 1.0;
320 for (int d = 0; d < Dim; ++d)
321 v *= w;
322 return v;
323 }
324 double areaOf(Coord s) const {
325 double a = 1.0;
326 for (int d = 0; d < Dim - 1; ++d)
327 a *= static_cast<double>(s) * h0_;
328 return a;
329 }
330 double coeff(Coord si, Coord sj) const {
331 double dist = 0.5 * (static_cast<double>(si) + static_cast<double>(sj)) * h0_;
332 return areaOf(si < sj ? si : sj) / dist;
333 }
334
335 // tangential sub-coord for a finer 2:1 face (global, wrapped to the domain).
336 std::array<Coord, Dim> subCoord(const std::array<Coord, Dim>& gc, int axis, int k,
337 Coord sj) const {
338 std::array<Coord, Dim> q = gc;
339 int bit = 0;
340 for (int tt = 0; tt < Dim; ++tt) {
341 if (tt == axis)
342 continue;
343 const Coord off = ((k >> bit) & 1) ? sj : Coord(0);
344 long v = static_cast<long>(gc[tt]) + static_cast<long>(off);
345 const long gf = static_cast<long>(d_->globalFineSize()[tt]);
346 if (v >= gf)
347 v -= gf; // periodic tangential wrap (in-domain otherwise)
348 q[tt] = static_cast<Coord>(v);
349 ++bit;
350 }
351 return q;
352 }
353
354 DO* d_ = nullptr;
355 double h0_ = 1.0;
356 Index n_ = 0;
357 std::vector<double> invVol_, diag_, w_;
358 std::vector<Index> start_, ref_; // face CSR (ref<n local, else ghost)
359 std::vector<std::array<Coord, Dim>> ghostCoords_; // dedup ghost cells (slot = ref−n)
360};
361
379template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
381 public:
383 using BO = typename DO::Octree;
384 using M = typename DO::M;
385
388 void build(const DO& finest) {
389 buildImpl(finest, [](const Vec<Dim>&, int) { return 1.0; }, false);
390 }
391
396 template <class OpenFn>
397 void build(const DO& finest, OpenFn&& openFn) {
398 buildImpl(finest, std::forward<OpenFn>(openFn), true);
399 }
400
401 private:
402 template <class OpenFn>
403 void buildImpl(const DO& finest, OpenFn&& openFn, bool hasOpen) {
404 hasOpen_ = hasOpen;
405 levels_.clear();
406 const IVec<Dim> g = finest.globalRootSize();
407 const unsigned lmax = finest.lmax();
408 const AmrGeometry<Dim> geo = finest.globalGeometry();
409 const auto per = finest.periodic();
410 MPI_Comm comm = finest.comm();
411
412 auto l0 = std::make_unique<Level>();
413 l0->d = finest; // copy (level 0 owned by the hierarchy)
414 levels_.push_back(std::move(l0));
415 for (;;) {
416 BO coarse = levels_.back()->d.local(); // copy
417 const Index before = coarse.numLeaves();
418 coarse.coarsenIf([](typename DO::Code, unsigned) { return true; });
419 if (coarse.numLeaves() == before)
420 break; // uniform root brick reached
421 auto lv = std::make_unique<Level>();
422 lv->d.init(g, lmax, geo, per, comm);
423 lv->d.local() = coarse; // same ORB block, coarsened local octree
424 levels_.push_back(std::move(lv));
425 }
426 // The coarsening above stops per-rank when this block reaches its root brick, so a
427 // rank owning shallow blocks builds FEWER levels than one owning deep refinement.
428 // Each level's op.init / vcycle drives a COLLECTIVE owner request/reply (coverLevels,
429 // coverValues over `comm`), so an unequal level count would mismatch the collectives
430 // and deadlock at np>1. Pad every rank up to the global max with repeats of its root
431 // brick: the extra levels are identity transfers (c2p maps a root cell to itself), a
432 // no-op in the V-cycle — and they exactly mirror what the whole-domain COMM_SELF
433 // reference does in its already-coarse regions, so the bit-exact WORLD==SELF check is
434 // preserved. (Allreduce on COMM_SELF is a no-op, leaving the serial path unchanged.)
435 {
436 int localLevels = static_cast<int>(levels_.size());
439 while (static_cast<int>(levels_.size()) < globalLevels) {
440 auto lv = std::make_unique<Level>();
441 lv->d.init(g, lmax, geo, per, comm);
442 lv->d.local() = levels_.back()->d.local(); // repeat the root brick (identity level)
443 levels_.push_back(std::move(lv));
444 }
445 }
446 for (auto& lv : levels_) {
447 lv->op.init(lv->d, openFn);
448 const Index n = lv->d.local().numLeaves();
449 lv->x.assign(static_cast<std::size_t>(n), 0.0);
450 lv->b.assign(static_cast<std::size_t>(n), 0.0);
451 lv->res.assign(static_cast<std::size_t>(n), 0.0);
452 }
453 // local fine→coarse maps (covering coarse leaf, same block).
454 for (std::size_t L = 0; L + 1 < levels_.size(); ++L) {
455 const BO& f = levels_[L]->d.local();
456 const BO& c = levels_[L + 1]->d.local();
457 const Index nf = f.numLeaves();
458 auto& c2p = levels_[L]->c2p;
459 c2p.assign(static_cast<std::size_t>(nf), -1);
460 for (Index i = 0; i < nf; ++i)
461 c2p[static_cast<std::size_t>(i)] = c.find(f.code(i));
462 }
463
464 // Bottom solver (openness-free only): a uniform DistributedMultigrid on the root
465 // grid, below the root brick. The coarsest graded level IS the uniform root brick
466 // (root cells at level lmax); its operator is L=∇² at spacing h0·2^lmax, the same
467 // operator the uniform MG's finest applies (DistributedPoisson is also L=∇²) on
468 // the same root grid — so the bottom solve of L e = res is inner.vcycle(e, res).
469 // With openness the uniform inner is inconsistent, so the coarsest is bottom-solved
470 // with Jacobi on its own (openness-carrying) operator instead.
471 if (!hasOpen_) {
473 ig.h0 = geo.h0 * static_cast<double>(Index(1) << lmax); // root-cell width
474 inner_ = std::make_unique<DistributedMultigrid<Dim, Bits>>();
475 inner_->build(g, ig, per, comm);
476 DO& coarsest = levels_.back()->d;
477 nCoarse_ = coarsest.local().numLeaves();
478 innerMap_.assign(static_cast<std::size_t>(nCoarse_), -1);
479 for (Index i = 0; i < nCoarse_; ++i)
480 innerMap_[static_cast<std::size_t>(i)] =
481 inner_->octree(0).findGlobalRoot(coarsest.globalRootOf(i));
482 }
483 }
484
485 public:
486 std::size_t numLevels() const { return levels_.size(); }
487 DistributedFvOperator<Dim, Bits>& op(std::size_t L = 0) { return levels_[L]->op; }
488 Index numLeaves(std::size_t L = 0) const { return levels_[L]->d.local().numLeaves(); }
489
493 void vcycle(std::vector<double>& x, const std::vector<double>& b, int pre = 2, int post = 2,
494 int innerCycles = 6, double omega = 0.8, std::size_t L = 0) {
495 auto& lv = *levels_[L];
496 if (L + 1 == levels_.size()) {
497 if (inner_)
498 bottomSolve(lv.op, x, b, innerCycles); // openness-free: chained uniform MG
499 else
500 lv.op.jacobi(x, b, bottomSweeps_, omega); // openness: Jacobi on the correct op
501 return;
502 }
503 lv.op.jacobi(x, b, pre, omega);
504 std::vector<double> res;
505 lv.op.residual(x, b, res);
506 // restrict residual → coarse rhs (local average over children).
507 auto& cl = *levels_[L + 1];
508 const Index nc = cl.d.local().numLeaves();
509 std::vector<double> cb(static_cast<std::size_t>(nc), 0.0),
510 cn(static_cast<std::size_t>(nc), 0.0);
511 const auto& c2p = lv.c2p;
512 const Index nf = lv.d.local().numLeaves();
513 for (Index i = 0; i < nf; ++i) {
514 Index p = c2p[static_cast<std::size_t>(i)];
515 if (p < 0)
516 continue;
517 cb[static_cast<std::size_t>(p)] += res[static_cast<std::size_t>(i)];
518 cn[static_cast<std::size_t>(p)] += 1.0;
519 }
520 for (Index p = 0; p < nc; ++p)
521 if (cn[static_cast<std::size_t>(p)] > 0.0)
522 cb[static_cast<std::size_t>(p)] /= cn[static_cast<std::size_t>(p)];
523 std::vector<double> cx(static_cast<std::size_t>(nc), 0.0);
524 vcycle(cx, cb, pre, post, innerCycles, omega, L + 1);
525 for (Index i = 0; i < nf; ++i) {
526 Index p = c2p[static_cast<std::size_t>(i)];
527 if (p >= 0)
528 x[static_cast<std::size_t>(i)] += cx[static_cast<std::size_t>(p)];
529 }
530 lv.op.jacobi(x, b, post, omega);
531 }
532
533 private:
534 // Solve L x = b on the uniform root brick via the inner uniform MG (correction
535 // scheme): res = b − Lx; solve L e = res with `cycles` inner V-cycles; x += e.
536 // Both operators are L = ∇² (same suite-wide sign), so no sign flip is needed.
537 // Index map coarsest→inner is by global root cell (identity in practice).
538 void bottomSolve(DistributedFvOperator<Dim, Bits>& op, std::vector<double>& x,
539 const std::vector<double>& b, int cycles) {
540 std::vector<double> res;
541 op.residual(x, b, res);
542 const Index ni = inner_->numLeaves(0);
543 std::vector<double> bi(static_cast<std::size_t>(ni), 0.0),
544 ei(static_cast<std::size_t>(ni), 0.0);
545 for (Index i = 0; i < nCoarse_; ++i) {
546 Index m = innerMap_[static_cast<std::size_t>(i)];
547 if (m >= 0)
548 bi[static_cast<std::size_t>(m)] = res[static_cast<std::size_t>(i)];
549 }
550 for (int c = 0; c < cycles; ++c)
551 inner_->vcycle(ei, bi);
552 for (Index i = 0; i < nCoarse_; ++i) {
553 Index m = innerMap_[static_cast<std::size_t>(i)];
554 if (m >= 0)
555 x[static_cast<std::size_t>(i)] += ei[static_cast<std::size_t>(m)];
556 }
557 }
558
559 struct Level {
560 DO d;
561 DistributedFvOperator<Dim, Bits> op;
562 std::vector<Index> c2p; // fine leaf → covering coarse leaf (local)
563 std::vector<double> x, b, res; // scratch
564 };
565 std::vector<std::unique_ptr<Level>> levels_;
566 std::unique_ptr<DistributedMultigrid<Dim, Bits>> inner_; // uniform bottom solver (openness-free)
567 std::vector<Index> innerMap_; // coarsest leaf → inner finest leaf
568 Index nCoarse_ = 0;
569 bool hasOpen_ = false;
570 int bottomSweeps_ = 400; // Jacobi sweeps on the coarsest op when openness present
571};
572
573} // namespace peclet::core::amr
574
575#endif // PECLET_CORE_HAVE_MORTON
576#endif // PECLET_CORE_AMR_DISTRIBUTED_FV_HPP
void init(DO &d, OpenFn &&openFn)
Build with cut-cell openness: w_f = α_f · A_f/d_f, where α_f = openFn(face centroid,...
DistributedOctree< Dim, Bits > DO
void init(DO &d)
Build the consistent face stencil, openness-free (w_f = A_f/d_f).
void apply(const std::vector< double > &u, std::vector< double > &Lu) const
Lu = L u (consistent conservative FV Laplacian, negative-definite).
void jacobi(std::vector< double > &u, const std::vector< double > &rhs, int sweeps, double omega=0.8) const
sweeps weighted-Jacobi relaxations of L u = rhs (point solve u_i ← (Σ w u_nb − V_i rhs_i)/Σ w).
double residualNorm(const std::vector< double > &u, const std::vector< double > &rhs) const
double residual(const std::vector< double > &u, const std::vector< double > &rhs, std::vector< double > &res) const
res = rhs − L u; returns the global L2 norm (volume-weighted).
const AmrGeometry< Dim > & globalGeometry() const
FaceInfo faceAcross(Index i, int axis, int dir) const
const IVec< Dim > & globalFineSize() const
std::vector< double > coverValues(const std::vector< std::array< Coord, Dim > > &coords, const std::vector< double > &field, double sentinel=kNoNeighbor) const
For each global fine coord, the covering leaf's field value on its owner (sentinel if none).
std::vector< int > coverLevels(const std::vector< std::array< Coord, Dim > > &coords) const
For each global fine coord (already wrapped into the domain), the level of the covering leaf on its o...
const IVec< Dim > & blockFineOrigin() const
Geometric-multigrid V-cycle on a graded distributed octree, built on DistributedFvOperator.
void vcycle(std::vector< double > &x, const std::vector< double > &b, int pre=2, int post=2, int innerCycles=6, double omega=0.8, std::size_t L=0)
One V-cycle of L u = rhs on level L (default finest), correction scheme.
void build(const DO &finest, OpenFn &&openFn)
Build with cut-cell openness openFn on every level (each level re-samples the geometry at its own fac...
void build(const DO &finest)
Build the hierarchy from an already-graded, 2:1-balanced finest octree (openness-free; the coarsest i...
DistributedFvOperator< Dim, Bits > & op(std::size_t L=0)
int MPI_Allreduce(const void *sbuf, void *rbuf, int count, MPI_Datatype dt, MPI_Op, MPI_Comm)
Definition mpi_stub.hpp:80
int MPI_Comm
Definition mpi_stub.hpp:16
#define MPI_INT
Definition mpi_stub.hpp:38
#define MPI_MAX
Definition mpi_stub.hpp:43
#define MPI_SUM
Definition mpi_stub.hpp:42
#define MPI_DOUBLE
Definition mpi_stub.hpp:40
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