core 0.5.0
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
poisson.hpp
Go to the documentation of this file.
1// core — cell-centered finite-volume Poisson on a BlockOctree, with a
2// geometric multigrid built from the octree's own levels.
3//
4// This is the "grid + multigrid" use of the AMR octree (serial, host; the device
5// and distributed paths build on the same operator later). The operator is a
6// conservative two-point finite-volume Laplacian: for a leaf i,
7//
8// (L u)_i = (1/V_i) * sum_faces A_f / d_f * (u_j - u_i),
9//
10// with V_i the cell volume, A_f the shared face area, d_f the centre-to-centre
11// normal distance. At a 2:1 interface the coarse side sums the flux over all
12// 2^(Dim-1) fine neighbours (each with the fine face area), so the discretisation
13// is conservative (the global integral of L u is zero on a periodic domain). The
14// two-point gradient ignores the tangential centre offset, so the interface flux
15// is first-order there — a documented limitation; a tangential-gradient
16// correction (full 2nd-order AMR flux) is a follow-up.
17//
18// Multigrid: the hierarchy is the octree coarsened uniformly one level at a time
19// (coarsenIf over all sibling groups). Restriction averages children -> parent;
20// prolongation is piecewise-constant (correction scheme). Smoother is
21// lexicographic Gauss-Seidel over the Z-order leaf slots. Periodic BCs only (the
22// natural first target); the singular null space is fixed by mean removal.
23//
24// Header-only, guarded by PECLET_CORE_HAVE_MORTON.
25#ifndef PECLET_CORE_AMR_POISSON_HPP
26#define PECLET_CORE_AMR_POISSON_HPP
27
28#ifdef PECLET_CORE_HAVE_MORTON
29
30#include <array>
31#include <cmath>
32#include <functional>
33#include <vector>
34
36#include "peclet/core/amr/face_csr.hpp" // shared host+device FV (weight-CSR) row kernels
39
40namespace peclet::core::amr {
41
43template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
45 public:
47 using M = typename Octree::M;
48 using Code = typename Octree::Code;
49 using Coord = typename Octree::Coord;
50
51 AmrPoisson() = default;
52 AmrPoisson(const Octree& t, Real h0) { init(t, h0); }
53
54 void init(const Octree& t, Real h0) {
55 t_ = &t;
56 h0_ = h0;
57 alpha_.clear();
58 hasOpen_ = false;
59 extResolve_ = nullptr; // distributed seam re-installed by the owner after every init
60 ghostLo_.clear();
61 ghostLv_.clear();
62 frameShift_ = {};
63 for (int d = 0; d < Dim; ++d)
64 fineExt_[d] = static_cast<Coord>(t.brick()[d] * (Index(1) << t.lmax()));
65 }
66
67 void setOrigin(const Vec<Dim>& o) { origin_ = o; }
68
69 // ---- distributed seam (docs/amr_distributed_flow.md) ---------------------------------------
76 using ExtResolver = std::function<Index(const std::array<long, Dim>&)>;
77 void setResolver(ExtResolver r) { extResolve_ = std::move(r); }
82 void setGhosts(std::vector<std::array<long, Dim>> lo, std::vector<unsigned> lv) {
83 ghostLo_ = std::move(lo);
84 ghostLv_ = std::move(lv);
85 }
86 Index numGhosts() const { return static_cast<Index>(ghostLv_.size()); }
92 void setFrameShift(const std::array<long, Dim>& s) { frameShift_ = s; }
94 unsigned levelOf(Index slot) const {
95 return slot < numLeaves()
96 ? t_->level(slot)
97 : ghostLv_[static_cast<std::size_t>(slot - numLeaves())];
98 }
100 std::array<long, Dim> loOf(Index slot) const {
101 std::array<long, Dim> lo{};
102 if (slot < numLeaves()) {
103 auto b = t_->bounds(slot);
104 for (int d = 0; d < Dim; ++d)
105 lo[d] = static_cast<long>(b[0][d]);
106 } else {
107 lo = ghostLo_[static_cast<std::size_t>(slot - numLeaves())];
108 }
109 return lo;
110 }
111
117 std::pair<Index, unsigned> probeSlot(const std::array<long, Dim>& p) const {
118 bool in = true;
119 for (int d = 0; d < Dim; ++d)
120 if (p[d] < 0 || p[d] >= static_cast<long>(fineExt_[d])) {
121 in = false;
122 break;
123 }
124 std::array<Coord, Dim> q{};
125 if (!in) {
126 if (extResolve_) {
127 const Index s = extResolve_(p);
128 return {s, s >= 0 ? levelOf(s) : 0u};
129 }
130 for (int d = 0; d < Dim; ++d) {
131 const long e = static_cast<long>(fineExt_[d]);
132 q[d] = static_cast<Coord>(((p[d] % e) + e) % e);
133 }
134 } else {
135 for (int d = 0; d < Dim; ++d)
136 q[d] = static_cast<Coord>(p[d]);
137 }
138 const Index j = t_->find(M::encode(q).code());
139 return {j, j >= 0 ? t_->level(j) : 0u};
140 }
141
142 // ---- cut-cell openness (per-leaf per-face fluid fraction in [0,1]) -------
143 static constexpr int kFaces = 2 * Dim;
144 static int faceIndex(int axis, int dir) { return 2 * axis + (dir > 0 ? 0 : 1); }
145
147 double faceOpenness(Index i, int axis, int dir) const {
148 if (!hasOpen_)
149 return 1.0;
150 return alpha_[static_cast<std::size_t>(i) * kFaces + faceIndex(axis, dir)];
151 }
152
153 bool hasOpenness() const { return hasOpen_; }
154 const std::vector<double>& opennessRaw() const { return alpha_; }
155 void setOpennessRaw(std::vector<double> a) {
156 alpha_ = std::move(a);
157 hasOpen_ = true;
158 }
159
163 template <class OpenFn>
165 const Index n = numLeaves();
166 const Index ng = numGhosts();
167 alpha_.assign(static_cast<std::size_t>(n + ng) * kFaces, 1.0);
168 hasOpen_ = true;
169 // Face centroids in the GLOBAL frame (localCoord + frameShift_): every rank evaluates the
170 // world-coord openFn at bit-identical points, so ghost rows match the owner's exactly (the
171 // symmetric-openFn contract). Single-rank: frameShift_ = 0, unchanged.
172 auto fillRow = [&](Index row, const std::array<long, Dim>& lo, long s) {
173 for (int axis = 0; axis < Dim; ++axis)
174 for (int dir = -1; dir <= 1; dir += 2) {
175 const long plane = (dir > 0) ? lo[axis] + frameShift_[axis] + s : lo[axis] + frameShift_[axis];
176 Vec<Dim> fc{};
177 for (int d = 0; d < Dim; ++d)
178 fc[d] = (d == axis)
179 ? origin_[d] + static_cast<Real>(plane) * h0_
180 : origin_[d] + (static_cast<Real>(lo[d] + frameShift_[d]) +
181 0.5 * static_cast<Real>(s)) *
182 h0_;
183 double a = static_cast<double>(openFn(fc, axis));
184 a = a < 0.0 ? 0.0 : (a > 1.0 ? 1.0 : a);
185 alpha_[static_cast<std::size_t>(row) * kFaces + faceIndex(axis, dir)] = a;
186 }
187 };
188 for (Index i = 0; i < n; ++i) {
189 auto b = t_->bounds(i);
190 std::array<long, Dim> lo{};
191 for (int d = 0; d < Dim; ++d)
192 lo[d] = static_cast<long>(b[0][d]);
193 fillRow(i, lo, 1L << t_->level(i));
194 }
195 for (Index g = 0; g < ng; ++g) // ghost α rows (distributed builds; empty single-rank)
196 fillRow(n + g, ghostLo_[static_cast<std::size_t>(g)],
197 1L << ghostLv_[static_cast<std::size_t>(g)]);
198 }
199
200 Index numLeaves() const { return t_->numLeaves(); }
201
205 void setPeriodic(bool p) { periodic_ = p; }
206 bool periodic() const { return periodic_; }
207
215 void setImmersedWall(bool w) { immersedWall_ = w; }
216 bool immersedWall() const { return immersedWall_; }
217
223 double boundaryDiag(Index i) const {
224 if (periodic_ && !immersedWall_)
225 return 0.0;
226 auto b = t_->bounds(i);
227 const auto& lo = b[0];
228 const Coord si = Coord(Coord(1) << t_->level(i));
229 const double wall = areaOf(si) / (0.5 * static_cast<Real>(si) * h0_);
230 double s = 0.0;
231 for (int axis = 0; axis < Dim; ++axis)
232 for (int dir = -1; dir <= 1; dir += 2) {
233 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
234 : static_cast<long>(lo[axis]) - 1;
235 const bool domainBoundary =
236 !periodic_ && (pc < 0 || pc >= static_cast<long>(fineExt_[axis]));
237 if (domainBoundary)
238 s += faceOpenness(i, axis, dir) * wall; // domain Dirichlet wall (open part)
239 else if (immersedWall_)
240 s += (1.0 - faceOpenness(i, axis, dir)) * wall; // immersed no-slip wall (solid part)
241 }
242 return s;
243 }
244
245 Real cellWidth(Index i) const { return h0_ * static_cast<Real>(Index(1) << levelOf(i)); }
247 Real w = cellWidth(i);
248 Real v = 1;
249 for (int d = 0; d < Dim; ++d)
250 v *= w;
251 return v;
252 }
253
257 template <class Fn>
258 void forEachFaceNeighbor(Index i, Fn&& fn) const {
259 auto b = t_->bounds(i);
260 const auto& lo = b[0];
261 const unsigned Li = t_->level(i);
262 const Coord si = Coord(Coord(1) << Li);
263 for (int axis = 0; axis < Dim; ++axis)
264 for (int dir = -1; dir <= 1; dir += 2) {
265 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
266 : static_cast<long>(lo[axis]) - 1;
267 // Non-periodic: a domain-boundary face has no neighbour cell (it is a Dirichlet
268 // wall handled by boundaryDiag) — skip it.
269 if (!periodic_ && (pc < 0 || pc >= static_cast<long>(fineExt_[axis])))
270 continue;
271 std::array<long, Dim> p{};
272 for (int d = 0; d < Dim; ++d)
273 p[d] = static_cast<long>(lo[d]);
274 p[axis] = pc;
275 const auto [j, Lj] = probeSlot(p);
276 if (j < 0)
277 continue; // unresolved during the distributed discovery fixpoint only
278 if (Lj >= Li) {
279 // same level or coarser: one neighbour, shared face = this cell's face.
280 // Openness lives on the finer side (here, this cell i).
281 fn(j, coeff(si, Coord(Coord(1) << Lj)), axis, faceOpenness(i, axis, dir));
282 } else {
283 // finer neighbour: 2^(Dim-1) sub-faces, each the fine face area.
284 const Coord sj = Coord(si >> 1);
285 const int nsub = 1 << (Dim - 1);
286 for (int k = 0; k < nsub; ++k) {
287 std::array<long, Dim> q = p;
288 int bit = 0;
289 for (int t = 0; t < Dim; ++t) {
290 if (t == axis)
291 continue;
292 q[t] = static_cast<long>(lo[t]) + (((k >> bit) & 1) ? static_cast<long>(sj) : 0L);
293 ++bit;
294 }
295 const Index jj = probeSlot(q).first;
296 if (jj < 0)
297 continue;
298 // Fine face area (min = sj) but the true centre-to-centre distance
299 // (si+sj)/2 — same value the fine side computes, so the operator is
300 // symmetric / conservative across the 2:1 interface. Openness lives on
301 // the finer side (the neighbour jj), its face toward i is -dir.
302 fn(jj, coeff(si, sj), axis, faceOpenness(jj, axis, -dir));
303 }
304 }
305 }
306 }
307
312 template <class Fn>
313 void forEachFaceFull(Index i, Fn&& fn) const {
314 auto b = t_->bounds(i);
315 const auto& lo = b[0];
316 const unsigned Li = t_->level(i);
317 const Coord si = Coord(Coord(1) << Li);
318 for (int axis = 0; axis < Dim; ++axis)
319 for (int dir = -1; dir <= 1; dir += 2) {
320 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
321 : static_cast<long>(lo[axis]) - 1;
322 std::array<long, Dim> p{};
323 for (int d = 0; d < Dim; ++d)
324 p[d] = static_cast<long>(lo[d]);
325 p[axis] = pc;
326 const auto [j, Lj] = probeSlot(p);
327 if (j < 0)
328 continue; // unresolved during the distributed discovery fixpoint only
329 if (Lj >= Li) {
330 const Coord sj = Coord(Coord(1) << Lj);
331 fn(j, axis, dir, areaOf(si), 0.5 * (static_cast<Real>(si) + static_cast<Real>(sj)) * h0_,
332 faceOpenness(i, axis, dir));
333 } else {
334 const Coord sj = Coord(si >> 1);
335 const int nsub = 1 << (Dim - 1);
336 for (int k = 0; k < nsub; ++k) {
337 std::array<long, Dim> q = p;
338 int bit = 0;
339 for (int t = 0; t < Dim; ++t) {
340 if (t == axis)
341 continue;
342 q[t] = static_cast<long>(lo[t]) + (((k >> bit) & 1) ? static_cast<long>(sj) : 0L);
343 ++bit;
344 }
345 const Index jj = probeSlot(q).first;
346 if (jj < 0)
347 continue;
348 fn(jj, axis, dir, areaOf(sj),
349 0.5 * (static_cast<Real>(si) + static_cast<Real>(sj)) * h0_,
350 faceOpenness(jj, axis, -dir));
351 }
352 }
353 }
354 }
355
358 Index periodicNeighbor(Index i, int axis, int dir) const {
359 std::array<long, Dim> p = loOf(i);
360 const long si = 1L << levelOf(i);
361 p[axis] = (dir > 0) ? p[axis] + si : p[axis] - 1;
362 return probeSlot(p).first;
363 }
364
372 double coarseStar(const std::vector<double>& u, Index coarse, Index fine, int axis) const {
373 const double uc = u[static_cast<std::size_t>(coarse)];
374 const std::array<long, Dim> bc = loOf(coarse); // ghost-safe (block-local longs)
375 const std::array<long, Dim> bf = loOf(fine);
376 const double H = cellWidth(coarse);
377 const double sc = static_cast<double>(Index(1) << levelOf(coarse));
378 const double sf = static_cast<double>(Index(1) << levelOf(fine));
379 double val = uc;
380 for (int t = 0; t < Dim; ++t) {
381 if (t == axis)
382 continue;
383 const double dt = ((static_cast<double>(bf[t]) + 0.5 * sf) -
384 (static_cast<double>(bc[t]) + 0.5 * sc)) *
385 h0_;
388 if (cp < 0 || cm < 0)
389 continue;
390 if (levelOf(cp) != levelOf(coarse) || levelOf(cm) != levelOf(coarse))
391 continue;
392 // Skip the correction near a solid: a nearly-closed tangential face means
393 // the quadratic stencil would lean on a solid-side value. Drop to the raw
394 // coarse value on this axis (locally lower order, but robust).
395 if (faceOpenness(coarse, t, +1) < 0.5 || faceOpenness(coarse, t, -1) < 0.5)
396 continue;
397 const double up = u[static_cast<std::size_t>(cp)];
398 const double um = u[static_cast<std::size_t>(cm)];
399 const double Dt = (up - um) / (2.0 * H);
400 const double Dtt = (up - 2.0 * uc + um) / (H * H);
401 val += dt * Dt + 0.5 * dt * dt * Dtt;
402 }
403 return val;
404 }
405
407 void applyLaplacianQuad(const std::vector<double>& u, std::vector<double>& out) const {
408 const Index n = numLeaves();
409 out.assign(static_cast<std::size_t>(n), 0.0);
410 for (Index i = 0; i < n; ++i) {
411 const double ui = u[static_cast<std::size_t>(i)];
412 const unsigned Li = t_->level(i);
413 double acc = 0.0;
414 forEachFaceNeighbor(i, [&](Index j, Real c, int axis, double a) {
415 const unsigned Lj = levelOf(j); // ghost-safe
416 double uj = u[static_cast<std::size_t>(j)];
417 double uii = ui;
418 if (Lj > Li)
419 uj = coarseStar(u, j, i, axis); // j coarser: correct its value
420 else if (Lj < Li)
421 uii = coarseStar(u, i, j, axis); // i coarser: correct our value for this sub-face
422 acc += a * c * (uj - uii);
423 });
424 out[static_cast<std::size_t>(i)] = acc / cellVolume(i);
425 }
426 }
427
429 double residualQuad(const std::vector<double>& u, const std::vector<double>& rhs,
430 std::vector<double>& res) const {
431 std::vector<double> lu;
433 const Index n = numLeaves();
434 res.assign(static_cast<std::size_t>(n), 0.0);
435 double s = 0.0;
436 for (Index i = 0; i < n; ++i) {
437 double r = rhs[static_cast<std::size_t>(i)] - lu[static_cast<std::size_t>(i)];
438 res[static_cast<std::size_t>(i)] = r;
439 s += cellVolume(i) * r * r;
440 }
441 return std::sqrt(s);
442 }
443
448 struct FvAssembled {
449 std::vector<double> invVol;
450 std::vector<Index> start;
451 std::vector<Index> nbr;
452 std::vector<double> coef;
453 std::vector<double> bcDiag;
454 };
456 const Index n = numLeaves();
457 FvAssembled A;
458 A.start.assign(static_cast<std::size_t>(n) + 1, 0);
459 for (Index i = 0; i < n; ++i) {
460 Index cnt = 0;
461 forEachFaceNeighbor(i, [&](Index, Real, int, double) { ++cnt; });
462 A.start[static_cast<std::size_t>(i) + 1] = A.start[static_cast<std::size_t>(i)] + cnt;
463 }
464 const Index nf = A.start[static_cast<std::size_t>(n)];
465 A.nbr.resize(static_cast<std::size_t>(nf));
466 A.coef.resize(static_cast<std::size_t>(nf));
467 A.invVol.resize(static_cast<std::size_t>(n));
468 A.bcDiag.resize(static_cast<std::size_t>(n));
469 for (Index i = 0; i < n; ++i) {
470 A.invVol[static_cast<std::size_t>(i)] = 1.0 / cellVolume(i);
471 A.bcDiag[static_cast<std::size_t>(i)] = boundaryDiag(i);
472 Index k = A.start[static_cast<std::size_t>(i)];
473 forEachFaceNeighbor(i, [&](Index j, Real c, int, double a) {
474 A.nbr[static_cast<std::size_t>(k)] = j;
475 A.coef[static_cast<std::size_t>(k)] = a * c;
476 ++k;
477 });
478 }
479 return A;
480 }
484 v.n = numLeaves();
485 v.invVol = HostArr<double>(A.invVol.data());
486 v.coef = HostArr<double>(A.coef.data());
487 v.start = HostArr<Index>(A.start.data());
488 v.nbr = HostArr<Index>(A.nbr.data());
489 v.bcDiag = HostArr<double>(A.bcDiag.data());
490 return v;
491 }
495 void applyFvShared(const std::vector<double>& u, std::vector<double>& out) const {
496 const FvAssembled A = assembleFv();
497 const auto op = hostFvOp(A);
498 const Index n = numLeaves();
499 out.assign(static_cast<std::size_t>(n), 0.0);
500 const HostArr<double> uacc(u.data());
501 for (Index i = 0; i < n; ++i)
502 out[static_cast<std::size_t>(i)] = fvApplyRow(op, i, uacc);
503 }
504
506 void applyLaplacian(const std::vector<double>& u, std::vector<double>& out) const {
507 const Index n = numLeaves();
508 out.assign(static_cast<std::size_t>(n), 0.0);
509 for (Index i = 0; i < n; ++i) {
510 const double ui = u[static_cast<std::size_t>(i)];
511 double acc = 0.0;
512 forEachFaceNeighbor(i, [&](Index j, Real c, int, double a) {
513 acc += a * c * (u[static_cast<std::size_t>(j)] - ui);
514 });
515 out[static_cast<std::size_t>(i)] = acc / cellVolume(i);
516 }
517 }
518
520 double residual(const std::vector<double>& u, const std::vector<double>& rhs,
521 std::vector<double>& res) const {
522 const Index n = numLeaves();
523 res.assign(static_cast<std::size_t>(n), 0.0);
524 double s = 0.0;
525 for (Index i = 0; i < n; ++i) {
526 const double ui = u[static_cast<std::size_t>(i)];
527 double acc = 0.0;
528 forEachFaceNeighbor(i, [&](Index j, Real c, int, double a) {
529 acc += a * c * (u[static_cast<std::size_t>(j)] - ui);
530 });
531 double r = rhs[static_cast<std::size_t>(i)] - acc / cellVolume(i);
532 res[static_cast<std::size_t>(i)] = r;
533 s += cellVolume(i) * r * r;
534 }
535 return std::sqrt(s);
536 }
537
539 void gaussSeidel(std::vector<double>& u, const std::vector<double>& rhs, int sweeps) const {
540 const Index n = numLeaves();
541 for (int s = 0; s < sweeps; ++s)
542 for (Index i = 0; i < n; ++i) {
543 double sumOff = 0.0, diag = 0.0;
544 forEachFaceNeighbor(i, [&](Index j, Real c, int, double a) {
545 sumOff += a * c * u[static_cast<std::size_t>(j)];
546 diag += a * c;
547 });
548 if (diag != 0.0)
549 u[static_cast<std::size_t>(i)] =
550 (sumOff - cellVolume(i) * rhs[static_cast<std::size_t>(i)]) / diag;
551 }
552 }
553
555 void removeMean(std::vector<double>& u) const {
556 const Index n = numLeaves();
557 double sum = 0.0, vol = 0.0;
558 for (Index i = 0; i < n; ++i) {
559 sum += cellVolume(i) * u[static_cast<std::size_t>(i)];
560 vol += cellVolume(i);
561 }
562 double m = sum / vol;
563 for (Index i = 0; i < n; ++i)
564 u[static_cast<std::size_t>(i)] -= m;
565 }
566
567 const Octree& octree() const { return *t_; }
568 Real h0() const { return h0_; }
571 const std::array<Coord, Dim>& fineExt() const { return fineExt_; }
572 const Vec<Dim>& origin() const { return origin_; }
573
574 private:
575 Coord wrap(long c, int axis) const {
576 long e = static_cast<long>(fineExt_[axis]);
577 return static_cast<Coord>(((c % e) + e) % e);
578 }
579 // A_f / d_f (physical) for a cell of width-units `si` next to one of `sj`.
580 Real coeff(Coord si, Coord sj) const {
581 Real dist = 0.5 * (static_cast<Real>(si) + static_cast<Real>(sj)) * h0_;
582 return areaOf(si < sj ? si : sj) / dist;
583 }
584 // Physical area of a face of a cell of width-units `s`: (s*h0)^(Dim-1).
585 Real areaOf(Coord s) const {
586 Real area = 1;
587 for (int d = 0; d < Dim - 1; ++d)
588 area *= static_cast<Real>(s) * h0_;
589 return area;
590 }
591
592 const Octree* t_ = nullptr;
593 Real h0_ = 1.0;
594 std::array<Coord, Dim> fineExt_{};
595 Vec<Dim> origin_{};
596 ExtResolver extResolve_; // distributed seam: out-of-block probe resolver
597 std::vector<std::array<long, Dim>> ghostLo_; // ghost slot → block-local lo (longs)
598 std::vector<unsigned> ghostLv_; // ghost slot → covering-leaf level
599 std::array<long, Dim> frameShift_{}; // block global fine origin (0 single-rank)
600 std::vector<double> alpha_; // per-(leaf+ghost) per-face openness (kFaces per row), or empty
601 bool hasOpen_ = false;
602 bool periodic_ = true; // false ⇒ homogeneous Dirichlet domain walls (boundaryDiag)
603 bool immersedWall_ = false; // true ⇒ velocity operator: (1−α) interior faces are no-slip walls
604};
605
607template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
609 public:
611 using M = typename Octree::M;
612 using Code = typename Octree::Code;
613
616 void build(const Octree& finest, Real h0) {
617 levels_.clear();
618 levels_.push_back(finest);
619 for (;;) {
620 Octree c = levels_.back();
621 Index merged = c.coarsenIf([](Code, unsigned) { return true; });
622 if (merged == 0 || c.numLeaves() == levels_.back().numLeaves())
623 break;
624 levels_.push_back(c);
625 if (c.numLeaves() == 1)
626 break;
627 }
628 ops_.resize(levels_.size());
629 // All levels share the finest h0: a coarse octree's leaves carry a higher
630 // `level`, and cellWidth = h0 * 2^level already encodes the doubled width.
631 for (std::size_t L = 0; L < levels_.size(); ++L)
632 ops_[L].init(levels_[L], h0);
633 // child(fine slot) -> parent(coarse slot) for each fine/coarse pair.
634 c2p_.assign(levels_.size() ? levels_.size() - 1 : 0, {});
635 for (std::size_t L = 0; L + 1 < levels_.size(); ++L) {
636 const Octree& f = levels_[L];
637 const Octree& c = levels_[L + 1];
638 c2p_[L].resize(static_cast<std::size_t>(f.numLeaves()));
639 for (Index i = 0; i < f.numLeaves(); ++i) {
640 // Covering-leaf c2p (see multigrid.hpp): == ancestor+find for merged children, correct
641 // (identity) for root-level rows in mixed-depth ladders, block-alignment-independent.
642 c2p_[L][static_cast<std::size_t>(i)] = c.find(f.code(i));
643 }
644 }
645 }
646
647 std::size_t numLevels() const { return levels_.size(); }
648 const AmrPoisson<Dim, Bits>& op(std::size_t L = 0) const { return ops_[L]; }
649
652 void setPeriodic(bool p) {
653 for (auto& o : ops_)
654 o.setPeriodic(p);
655 }
656
660 void setImmersedWall(bool w) {
661 for (auto& o : ops_)
662 o.setImmersedWall(w);
663 }
664
666 void vcycle(std::size_t L, std::vector<double>& u, const std::vector<double>& rhs, int pre = 2,
667 int post = 2) {
668 if (L + 1 == levels_.size()) {
669 ops_[L].gaussSeidel(u, rhs, 40); // coarsest: solve hard
670 ops_[L].removeMean(u);
671 return;
672 }
673 ops_[L].gaussSeidel(u, rhs, pre);
674 std::vector<double> res;
675 ops_[L].residual(u, rhs, res);
676
677 // Restrict residual: volume-weighted average of children -> parent.
678 const Octree& f = levels_[L];
679 const Octree& c = levels_[L + 1];
680 std::vector<double> crhs(static_cast<std::size_t>(c.numLeaves()), 0.0);
681 std::vector<double> cvol(static_cast<std::size_t>(c.numLeaves()), 0.0);
682 for (Index i = 0; i < f.numLeaves(); ++i) {
683 Index p = c2p_[L][static_cast<std::size_t>(i)];
684 if (p < 0)
685 continue;
686 Real v = ops_[L].cellVolume(i);
687 crhs[static_cast<std::size_t>(p)] += v * res[static_cast<std::size_t>(i)];
688 cvol[static_cast<std::size_t>(p)] += v;
689 }
690 for (Index p = 0; p < c.numLeaves(); ++p)
691 if (cvol[static_cast<std::size_t>(p)] > 0)
692 crhs[static_cast<std::size_t>(p)] /= cvol[static_cast<std::size_t>(p)];
693
694 std::vector<double> ccorr(static_cast<std::size_t>(c.numLeaves()), 0.0);
695 vcycle(L + 1, ccorr, crhs, pre, post);
696
697 // Prolong correction (piecewise constant) and add.
698 for (Index i = 0; i < f.numLeaves(); ++i) {
699 Index p = c2p_[L][static_cast<std::size_t>(i)];
700 if (p >= 0)
701 u[static_cast<std::size_t>(i)] += ccorr[static_cast<std::size_t>(p)];
702 }
703 ops_[L].gaussSeidel(u, rhs, post);
704 ops_[L].removeMean(u);
705 }
706
711 double solveQuad(std::vector<double>& u, const std::vector<double>& rhs, int outer = 30,
712 int cyclesPerOuter = 1) {
713 AmrPoisson<Dim, Bits>& P = ops_[0];
714 const std::size_t n = u.size();
715 std::vector<double> lq, ls, rhsp(n), res;
716 double r = 0.0;
717 for (int o = 0; o < outer; ++o) {
718 P.applyLaplacianQuad(u, lq);
719 P.applyLaplacian(u, ls);
720 for (std::size_t i = 0; i < n; ++i)
721 rhsp[i] = rhs[i] - (lq[i] - ls[i]);
722 for (int c = 0; c < cyclesPerOuter; ++c)
723 vcycle(0, u, rhsp);
724 r = P.residualQuad(u, rhs, res);
725 }
726 return r;
727 }
728
735 template <class OpenFn>
737 if (levels_.empty())
738 return;
739 ops_[0].buildOpenness(openFn);
740 for (std::size_t L = 0; L + 1 < levels_.size(); ++L)
741 coarsenOpenness(L);
742 }
743
744 private:
745 static int faceIdx(int axis, int dir) { return 2 * axis + (dir > 0 ? 0 : 1); }
746
747 // Area-average the level-L face openness onto level L+1 (each coarse face is the
748 // mean of the 2^(Dim-1) fine sub-faces covering it; equal areas => plain mean).
749 void coarsenOpenness(std::size_t L) {
750 const Octree& f = levels_[L];
751 const Octree& c = levels_[L + 1];
752 const int F = 2 * Dim;
753 std::vector<double> ca(static_cast<std::size_t>(c.numLeaves()) * F, 0.0);
754 std::vector<int> cnt(static_cast<std::size_t>(c.numLeaves()) * F, 0);
755 for (Index i = 0; i < f.numLeaves(); ++i) {
756 Index p = c2p_[L][static_cast<std::size_t>(i)];
757 if (p < 0)
758 continue;
759 const std::size_t base = static_cast<std::size_t>(p) * F;
760 if (c.level(p) == f.level(i)) {
761 // identity (cell not coarsened this round): copy every face.
762 for (int axis = 0; axis < Dim; ++axis)
763 for (int dir = -1; dir <= 1; dir += 2) {
764 int fi = faceIdx(axis, dir);
765 ca[base + fi] += ops_[L].faceOpenness(i, axis, dir);
766 cnt[base + fi] += 1;
767 }
768 } else {
769 // merged child: each axis contributes its outward face to the parent face.
770 unsigned oct = M::from_code(f.code(i)).child_index(f.level(i));
771 for (int axis = 0; axis < Dim; ++axis) {
772 int dir = ((oct >> axis) & 1) ? +1 : -1;
773 int fi = faceIdx(axis, dir);
774 ca[base + fi] += ops_[L].faceOpenness(i, axis, dir);
775 cnt[base + fi] += 1;
776 }
777 }
778 }
779 for (std::size_t k = 0; k < ca.size(); ++k)
780 ca[k] = cnt[k] ? ca[k] / cnt[k] : 1.0;
781 ops_[L + 1].setOpennessRaw(std::move(ca));
782 }
783
784 std::vector<Octree> levels_;
785 std::vector<AmrPoisson<Dim, Bits>> ops_;
786 std::vector<std::vector<Index>> c2p_;
787};
788
789} // namespace peclet::core::amr
790
791#endif // PECLET_CORE_HAVE_MORTON
792#endif // PECLET_CORE_AMR_POISSON_HPP
Geometric multigrid for AmrPoisson over a uniformly-coarsened octree hierarchy.
Definition poisson.hpp:608
void setPeriodic(bool p)
Apply a boundary condition to every level (periodic default, or non-periodic homogeneous Dirichlet).
Definition poisson.hpp:652
void build(const Octree &finest, Real h0)
Build the hierarchy from a finest octree by uniform coarsening until a single leaf remains (or no ful...
Definition poisson.hpp:616
const AmrPoisson< Dim, Bits > & op(std::size_t L=0) const
Definition poisson.hpp:648
void vcycle(std::size_t L, std::vector< double > &u, const std::vector< double > &rhs, int pre=2, int post=2)
One V-cycle on level L solving L u = rhs (correction scheme).
Definition poisson.hpp:666
double solveQuad(std::vector< double > &u, const std::vector< double > &rhs, int outer=30, int cyclesPerOuter=1)
Solve the quadratic C/F operator L_quad u = rhs by deferred correction: the standard-operator V-cycle...
Definition poisson.hpp:711
typename Octree::Code Code
Definition poisson.hpp:612
void setOpenness(OpenFn &&openFn)
Set cut-cell face openness on the finest level from a geometry callable openFn(faceCentreWorld,...
Definition poisson.hpp:736
BlockOctree< Dim, Bits > Octree
Definition poisson.hpp:610
void setImmersedWall(bool w)
Enable the immersed no-slip (Dirichlet) wall on every level — the velocity operator.
Definition poisson.hpp:660
std::size_t numLevels() const
Definition poisson.hpp:647
Cell-centered FV Poisson operator on one (periodic) block octree.
Definition poisson.hpp:44
const std::array< Coord, Dim > & fineExt() const
Per-axis fine-grid extent (brick·2^lmax) — the periodic wrap modulus.
Definition poisson.hpp:571
void setGhosts(std::vector< std::array< long, Dim > > lo, std::vector< unsigned > lv)
Declare the ghost slots [n, n+nGhost): block-local lo corner (longs — ghosts lie outside the block) a...
Definition poisson.hpp:82
std::array< long, Dim > loOf(Index slot) const
Block-local lo corner of an extended slot as longs (ghosts may lie outside the block).
Definition poisson.hpp:100
FvCsrOpT< HostArr< double >, HostArr< Index > > hostFvOp(const FvAssembled &A) const
View an FvAssembled as the backend-agnostic FvCsrOpT (c0=0,cD=1 ⇒ pure FV Laplacian).
Definition poisson.hpp:482
double coarseStar(const std::vector< double > &u, Index coarse, Index fine, int axis) const
Quadratic coarse-fine value: the coarse leaf coarse's field, evaluated by tangential quadratic interp...
Definition poisson.hpp:372
void forEachFaceNeighbor(Index i, Fn &&fn) const
Visit each face neighbour of leaf i: fn(neighbourSlot, coeff, axis, alpha) where coeff = A_f / d_f (p...
Definition poisson.hpp:258
const Vec< Dim > & origin() const
Definition poisson.hpp:572
const Octree & octree() const
Definition poisson.hpp:567
std::function< Index(const std::array< long, Dim > &)> ExtResolver
Route probes that exit the block through an external resolver (the LeafHalo registry): fn(blockLocalP...
Definition poisson.hpp:76
const std::vector< double > & opennessRaw() const
Definition poisson.hpp:154
Real cellWidth(Index i) const
Definition poisson.hpp:245
void setFrameShift(const std::array< long, Dim > &s)
Distributed frame shift: this block's global fine origin.
Definition poisson.hpp:92
void init(const Octree &t, Real h0)
Definition poisson.hpp:54
AmrPoisson(const Octree &t, Real h0)
Definition poisson.hpp:52
double faceOpenness(Index i, int axis, int dir) const
Openness of leaf i's face on (axis,dir); 1 if no openness has been set.
Definition poisson.hpp:147
typename Octree::M M
Definition poisson.hpp:47
typename Octree::Coord Coord
Definition poisson.hpp:49
static int faceIndex(int axis, int dir)
Definition poisson.hpp:144
void applyFvShared(const std::vector< double > &u, std::vector< double > &out) const
out = L u via the SHARED face_csr.hpp FV kernel over the assembled CSR — the same arithmetic the devi...
Definition poisson.hpp:495
void applyLaplacianQuad(const std::vector< double > &u, std::vector< double > &out) const
out = L u with the quadratic coarse-fine flux (2nd-order at 2:1 interfaces).
Definition poisson.hpp:407
void setImmersedWall(bool w)
Immersed no-slip (Dirichlet) wall mode.
Definition poisson.hpp:215
void gaussSeidel(std::vector< double > &u, const std::vector< double > &rhs, int sweeps) const
sweeps lexicographic Gauss-Seidel relaxations of L u = rhs (in place).
Definition poisson.hpp:539
void setOpennessRaw(std::vector< double > a)
Definition poisson.hpp:155
void setOrigin(const Vec< Dim > &o)
Definition poisson.hpp:67
double boundaryDiag(Index i) const
Σ over leaf i's Dirichlet-wall faces of the wall weight A_f/(½·cellWidth), folded into the operator d...
Definition poisson.hpp:223
Index periodicNeighbor(Index i, int axis, int dir) const
Periodic face neighbour leaf (covering the cell just across the face).
Definition poisson.hpp:358
unsigned levelOf(Index slot) const
Octree level of an extended slot (local leaf or declared ghost).
Definition poisson.hpp:94
static constexpr int kFaces
Definition poisson.hpp:143
void setResolver(ExtResolver r)
Definition poisson.hpp:77
void setPeriodic(bool p)
Boundary condition: periodic (default) wraps every face; non-periodic treats a domain-boundary face a...
Definition poisson.hpp:205
void removeMean(std::vector< double > &u) const
Subtract the volume-weighted mean (fixes the periodic null space).
Definition poisson.hpp:555
std::pair< Index, unsigned > probeSlot(const std::array< long, Dim > &p) const
Covering slot + level of a block-local probe (longs, possibly outside the block).
Definition poisson.hpp:117
double residual(const std::vector< double > &u, const std::vector< double > &rhs, std::vector< double > &res) const
res = rhs - L u, returns its L2 norm (sqrt(sum V_i res_i^2)).
Definition poisson.hpp:520
void applyLaplacian(const std::vector< double > &u, std::vector< double > &out) const
out = L u (periodic FV Laplacian).
Definition poisson.hpp:506
void forEachFaceFull(Index i, Fn &&fn) const
Like forEachFaceNeighbor but exposes geometry for a consistent FV divergence/gradient: fn(neighbour,...
Definition poisson.hpp:313
typename Octree::Code Code
Definition poisson.hpp:48
double residualQuad(const std::vector< double > &u, const std::vector< double > &rhs, std::vector< double > &res) const
L2 norm of rhs - L_quad u.
Definition poisson.hpp:429
void buildOpenness(OpenFn &&openFn)
Build face openness from a geometry callable openFn(faceCentreWorld, axis) -> [0,1] (1 = fully fluid,...
Definition poisson.hpp:164
FvAssembled assembleFv() const
Definition poisson.hpp:455
BlockOctree< Dim, Bits > Octree
Definition poisson.hpp:46
Real cellVolume(Index i) const
Definition poisson.hpp:246
unsigned level(Index i) const
Index find(Code p) const
Leaf containing Morton code p, or -1. Host wrapper over amrLocate.
std::array< std::array< Coord, Dim >, 2 > bounds(Index i) const
Inclusive integer bounds [lo, hi] of leaf i in fine units.
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:53
MORTON_HD double fvApplyRow(const Op &op, Index i, const U &u)
(H u)_i = c0·u_i + cD·( invVol_i·( Σ w·(u_nbr − u_i) − bcDiag_i·u_i ) ).
Definition face_csr.hpp:120
std::array< Real, Dim > Vec
Multi-dimensional real vector.
Definition types.hpp:26
double Real
Default host floating type. Device kernels may use float; conversions happen at the boundary.
Definition types.hpp:18
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15
The assembled FV (weight-CSR) operator: per-face conductance w = A_f/d_f·openness,...
Definition poisson.hpp:448
std::vector< double > invVol
1/V_i, size n
Definition poisson.hpp:449
std::vector< double > coef
w = A_f/d_f·openness per face, size nFaces
Definition poisson.hpp:452
std::vector< Index > nbr
neighbour leaf per face, size nFaces
Definition poisson.hpp:451
std::vector< Index > start
CSR row offsets, size n+1.
Definition poisson.hpp:450
std::vector< double > bcDiag
Dirichlet boundary diagonal per cell (0 if periodic)
Definition poisson.hpp:453
A backend-agnostic view of an assembled FV (weight-CSR) operator.
Definition face_csr.hpp:110
A uniform accessor over a raw host array, giving it the operator()(i) that Kokkos::View has,...
Definition face_csr.hpp:40