core
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 <vector>
33
35#include "peclet/core/amr/face_csr.hpp" // shared host+device FV (weight-CSR) row kernels
38
39namespace peclet::core::amr {
40
42template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
44 public:
46 using M = typename Octree::M;
47 using Code = typename Octree::Code;
48 using Coord = typename Octree::Coord;
49
50 AmrPoisson() = default;
51 AmrPoisson(const Octree& t, Real h0) { init(t, h0); }
52
53 void init(const Octree& t, Real h0) {
54 t_ = &t;
55 h0_ = h0;
56 alpha_.clear();
57 hasOpen_ = false;
58 for (int d = 0; d < Dim; ++d)
59 fineExt_[d] = static_cast<Coord>(t.brick()[d] * (Index(1) << t.lmax()));
60 }
61
62 void setOrigin(const Vec<Dim>& o) { origin_ = o; }
63
64 // ---- cut-cell openness (per-leaf per-face fluid fraction in [0,1]) -------
65 static constexpr int kFaces = 2 * Dim;
66 static int faceIndex(int axis, int dir) { return 2 * axis + (dir > 0 ? 0 : 1); }
67
69 double faceOpenness(Index i, int axis, int dir) const {
70 if (!hasOpen_)
71 return 1.0;
72 return alpha_[static_cast<std::size_t>(i) * kFaces + faceIndex(axis, dir)];
73 }
74
75 bool hasOpenness() const { return hasOpen_; }
76 const std::vector<double>& opennessRaw() const { return alpha_; }
77 void setOpennessRaw(std::vector<double> a) {
78 alpha_ = std::move(a);
79 hasOpen_ = true;
80 }
81
85 template <class OpenFn>
87 const Index n = numLeaves();
88 alpha_.assign(static_cast<std::size_t>(n) * kFaces, 1.0);
89 hasOpen_ = true;
90 for (Index i = 0; i < n; ++i) {
91 auto b = t_->bounds(i);
92 const auto& lo = b[0];
93 const Coord s = Coord(Coord(1) << t_->level(i));
94 for (int axis = 0; axis < Dim; ++axis)
95 for (int dir = -1; dir <= 1; dir += 2) {
96 const long plane = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(s)
97 : static_cast<long>(lo[axis]);
98 Vec<Dim> fc{};
99 for (int d = 0; d < Dim; ++d)
100 fc[d] = (d == axis) ? origin_[d] + static_cast<Real>(plane) * h0_
101 : origin_[d] +
102 (static_cast<Real>(lo[d]) + 0.5 * static_cast<Real>(s)) * h0_;
103 double a = static_cast<double>(openFn(fc, axis));
104 a = a < 0.0 ? 0.0 : (a > 1.0 ? 1.0 : a);
105 alpha_[static_cast<std::size_t>(i) * kFaces + faceIndex(axis, dir)] = a;
106 }
107 }
108 }
109
110 Index numLeaves() const { return t_->numLeaves(); }
111
115 void setPeriodic(bool p) { periodic_ = p; }
116 bool periodic() const { return periodic_; }
117
125 void setImmersedWall(bool w) { immersedWall_ = w; }
126 bool immersedWall() const { return immersedWall_; }
127
133 double boundaryDiag(Index i) const {
134 if (periodic_ && !immersedWall_)
135 return 0.0;
136 auto b = t_->bounds(i);
137 const auto& lo = b[0];
138 const Coord si = Coord(Coord(1) << t_->level(i));
139 const double wall = areaOf(si) / (0.5 * static_cast<Real>(si) * h0_);
140 double s = 0.0;
141 for (int axis = 0; axis < Dim; ++axis)
142 for (int dir = -1; dir <= 1; dir += 2) {
143 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
144 : static_cast<long>(lo[axis]) - 1;
145 const bool domainBoundary =
146 !periodic_ && (pc < 0 || pc >= static_cast<long>(fineExt_[axis]));
147 if (domainBoundary)
148 s += faceOpenness(i, axis, dir) * wall; // domain Dirichlet wall (open part)
149 else if (immersedWall_)
150 s += (1.0 - faceOpenness(i, axis, dir)) * wall; // immersed no-slip wall (solid part)
151 }
152 return s;
153 }
154
155 Real cellWidth(Index i) const { return h0_ * static_cast<Real>(Index(1) << t_->level(i)); }
157 Real w = cellWidth(i);
158 Real v = 1;
159 for (int d = 0; d < Dim; ++d)
160 v *= w;
161 return v;
162 }
163
167 template <class Fn>
168 void forEachFaceNeighbor(Index i, Fn&& fn) const {
169 auto b = t_->bounds(i);
170 const auto& lo = b[0];
171 const unsigned Li = t_->level(i);
172 const Coord si = Coord(Coord(1) << Li);
173 for (int axis = 0; axis < Dim; ++axis)
174 for (int dir = -1; dir <= 1; dir += 2) {
175 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
176 : static_cast<long>(lo[axis]) - 1;
177 // Non-periodic: a domain-boundary face has no neighbour cell (it is a Dirichlet
178 // wall handled by boundaryDiag) — skip it.
179 if (!periodic_ && (pc < 0 || pc >= static_cast<long>(fineExt_[axis])))
180 continue;
181 std::array<Coord, Dim> p = lo;
182 p[axis] = wrap(pc, axis);
183 Index j = t_->find(M::encode(p).code());
184 const unsigned Lj = t_->level(j);
185 if (Lj >= Li) {
186 // same level or coarser: one neighbour, shared face = this cell's face.
187 // Openness lives on the finer side (here, this cell i).
188 fn(j, coeff(si, Coord(Coord(1) << Lj)), axis, faceOpenness(i, axis, dir));
189 } else {
190 // finer neighbour: 2^(Dim-1) sub-faces, each the fine face area.
191 const Coord sj = Coord(si >> 1);
192 const int nsub = 1 << (Dim - 1);
193 for (int k = 0; k < nsub; ++k) {
194 std::array<Coord, Dim> q = lo;
195 q[axis] = wrap(pc, axis);
196 int bit = 0;
197 for (int t = 0; t < Dim; ++t) {
198 if (t == axis)
199 continue;
200 const Coord off = ((k >> bit) & 1) ? sj : Coord(0);
201 q[t] = wrap(static_cast<long>(lo[t]) + static_cast<long>(off), t);
202 ++bit;
203 }
204 Index jj = t_->find(M::encode(q).code());
205 // Fine face area (min = sj) but the true centre-to-centre distance
206 // (si+sj)/2 — same value the fine side computes, so the operator is
207 // symmetric / conservative across the 2:1 interface. Openness lives on
208 // the finer side (the neighbour jj), its face toward i is -dir.
209 fn(jj, coeff(si, sj), axis, faceOpenness(jj, axis, -dir));
210 }
211 }
212 }
213 }
214
219 template <class Fn>
220 void forEachFaceFull(Index i, Fn&& fn) const {
221 auto b = t_->bounds(i);
222 const auto& lo = b[0];
223 const unsigned Li = t_->level(i);
224 const Coord si = Coord(Coord(1) << Li);
225 for (int axis = 0; axis < Dim; ++axis)
226 for (int dir = -1; dir <= 1; dir += 2) {
227 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
228 : static_cast<long>(lo[axis]) - 1;
229 std::array<Coord, Dim> p = lo;
230 p[axis] = wrap(pc, axis);
231 Index j = t_->find(M::encode(p).code());
232 const unsigned Lj = t_->level(j);
233 if (Lj >= Li) {
234 const Coord sj = Coord(Coord(1) << Lj);
235 fn(j, axis, dir, areaOf(si), 0.5 * (static_cast<Real>(si) + static_cast<Real>(sj)) * h0_,
236 faceOpenness(i, axis, dir));
237 } else {
238 const Coord sj = Coord(si >> 1);
239 const int nsub = 1 << (Dim - 1);
240 for (int k = 0; k < nsub; ++k) {
241 std::array<Coord, Dim> q = lo;
242 q[axis] = wrap(pc, axis);
243 int bit = 0;
244 for (int t = 0; t < Dim; ++t) {
245 if (t == axis)
246 continue;
247 const Coord off = ((k >> bit) & 1) ? sj : Coord(0);
248 q[t] = wrap(static_cast<long>(lo[t]) + static_cast<long>(off), t);
249 ++bit;
250 }
251 Index jj = t_->find(M::encode(q).code());
252 fn(jj, axis, dir, areaOf(sj),
253 0.5 * (static_cast<Real>(si) + static_cast<Real>(sj)) * h0_,
254 faceOpenness(jj, axis, -dir));
255 }
256 }
257 }
258 }
259
261 Index periodicNeighbor(Index i, int axis, int dir) const {
262 auto b = t_->bounds(i);
263 const auto& lo = b[0];
264 const Coord si = Coord(Coord(1) << t_->level(i));
265 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
266 : static_cast<long>(lo[axis]) - 1;
267 std::array<Coord, Dim> p = lo;
268 p[axis] = wrap(pc, axis);
269 return t_->find(M::encode(p).code());
270 }
271
279 double coarseStar(const std::vector<double>& u, Index coarse, Index fine, int axis) const {
280 const double uc = u[static_cast<std::size_t>(coarse)];
281 auto bc = t_->bounds(coarse);
282 auto bf = t_->bounds(fine);
283 const double H = cellWidth(coarse);
284 const double sc = static_cast<double>(Index(1) << t_->level(coarse));
285 const double sf = static_cast<double>(Index(1) << t_->level(fine));
286 double val = uc;
287 for (int t = 0; t < Dim; ++t) {
288 if (t == axis)
289 continue;
290 const double dt = ((static_cast<double>(bf[0][t]) + 0.5 * sf) -
291 (static_cast<double>(bc[0][t]) + 0.5 * sc)) *
292 h0_;
295 if (cp < 0 || cm < 0)
296 continue;
297 if (t_->level(cp) != t_->level(coarse) || t_->level(cm) != t_->level(coarse))
298 continue;
299 // Skip the correction near a solid: a nearly-closed tangential face means
300 // the quadratic stencil would lean on a solid-side value. Drop to the raw
301 // coarse value on this axis (locally lower order, but robust).
302 if (faceOpenness(coarse, t, +1) < 0.5 || faceOpenness(coarse, t, -1) < 0.5)
303 continue;
304 const double up = u[static_cast<std::size_t>(cp)];
305 const double um = u[static_cast<std::size_t>(cm)];
306 const double Dt = (up - um) / (2.0 * H);
307 const double Dtt = (up - 2.0 * uc + um) / (H * H);
308 val += dt * Dt + 0.5 * dt * dt * Dtt;
309 }
310 return val;
311 }
312
314 void applyLaplacianQuad(const std::vector<double>& u, std::vector<double>& out) const {
315 const Index n = numLeaves();
316 out.assign(static_cast<std::size_t>(n), 0.0);
317 for (Index i = 0; i < n; ++i) {
318 const double ui = u[static_cast<std::size_t>(i)];
319 const unsigned Li = t_->level(i);
320 double acc = 0.0;
321 forEachFaceNeighbor(i, [&](Index j, Real c, int axis, double a) {
322 const unsigned Lj = t_->level(j);
323 double uj = u[static_cast<std::size_t>(j)];
324 double uii = ui;
325 if (Lj > Li)
326 uj = coarseStar(u, j, i, axis); // j coarser: correct its value
327 else if (Lj < Li)
328 uii = coarseStar(u, i, j, axis); // i coarser: correct our value for this sub-face
329 acc += a * c * (uj - uii);
330 });
331 out[static_cast<std::size_t>(i)] = acc / cellVolume(i);
332 }
333 }
334
336 double residualQuad(const std::vector<double>& u, const std::vector<double>& rhs,
337 std::vector<double>& res) const {
338 std::vector<double> lu;
340 const Index n = numLeaves();
341 res.assign(static_cast<std::size_t>(n), 0.0);
342 double s = 0.0;
343 for (Index i = 0; i < n; ++i) {
344 double r = rhs[static_cast<std::size_t>(i)] - lu[static_cast<std::size_t>(i)];
345 res[static_cast<std::size_t>(i)] = r;
346 s += cellVolume(i) * r * r;
347 }
348 return std::sqrt(s);
349 }
350
355 struct FvAssembled {
356 std::vector<double> invVol;
357 std::vector<Index> start;
358 std::vector<Index> nbr;
359 std::vector<double> coef;
360 std::vector<double> bcDiag;
361 };
363 const Index n = numLeaves();
364 FvAssembled A;
365 A.start.assign(static_cast<std::size_t>(n) + 1, 0);
366 for (Index i = 0; i < n; ++i) {
367 Index cnt = 0;
368 forEachFaceNeighbor(i, [&](Index, Real, int, double) { ++cnt; });
369 A.start[static_cast<std::size_t>(i) + 1] = A.start[static_cast<std::size_t>(i)] + cnt;
370 }
371 const Index nf = A.start[static_cast<std::size_t>(n)];
372 A.nbr.resize(static_cast<std::size_t>(nf));
373 A.coef.resize(static_cast<std::size_t>(nf));
374 A.invVol.resize(static_cast<std::size_t>(n));
375 A.bcDiag.resize(static_cast<std::size_t>(n));
376 for (Index i = 0; i < n; ++i) {
377 A.invVol[static_cast<std::size_t>(i)] = 1.0 / cellVolume(i);
378 A.bcDiag[static_cast<std::size_t>(i)] = boundaryDiag(i);
379 Index k = A.start[static_cast<std::size_t>(i)];
380 forEachFaceNeighbor(i, [&](Index j, Real c, int, double a) {
381 A.nbr[static_cast<std::size_t>(k)] = j;
382 A.coef[static_cast<std::size_t>(k)] = a * c;
383 ++k;
384 });
385 }
386 return A;
387 }
391 v.n = numLeaves();
392 v.invVol = HostArr<double>(A.invVol.data());
393 v.coef = HostArr<double>(A.coef.data());
394 v.start = HostArr<Index>(A.start.data());
395 v.nbr = HostArr<Index>(A.nbr.data());
396 v.bcDiag = HostArr<double>(A.bcDiag.data());
397 return v;
398 }
402 void applyFvShared(const std::vector<double>& u, std::vector<double>& out) const {
403 const FvAssembled A = assembleFv();
404 const auto op = hostFvOp(A);
405 const Index n = numLeaves();
406 out.assign(static_cast<std::size_t>(n), 0.0);
407 const HostArr<double> uacc(u.data());
408 for (Index i = 0; i < n; ++i)
409 out[static_cast<std::size_t>(i)] = fvApplyRow(op, i, uacc);
410 }
411
413 void applyLaplacian(const std::vector<double>& u, std::vector<double>& out) const {
414 const Index n = numLeaves();
415 out.assign(static_cast<std::size_t>(n), 0.0);
416 for (Index i = 0; i < n; ++i) {
417 const double ui = u[static_cast<std::size_t>(i)];
418 double acc = 0.0;
419 forEachFaceNeighbor(i, [&](Index j, Real c, int, double a) {
420 acc += a * c * (u[static_cast<std::size_t>(j)] - ui);
421 });
422 out[static_cast<std::size_t>(i)] = acc / cellVolume(i);
423 }
424 }
425
427 double residual(const std::vector<double>& u, const std::vector<double>& rhs,
428 std::vector<double>& res) const {
429 const Index n = numLeaves();
430 res.assign(static_cast<std::size_t>(n), 0.0);
431 double s = 0.0;
432 for (Index i = 0; i < n; ++i) {
433 const double ui = u[static_cast<std::size_t>(i)];
434 double acc = 0.0;
435 forEachFaceNeighbor(i, [&](Index j, Real c, int, double a) {
436 acc += a * c * (u[static_cast<std::size_t>(j)] - ui);
437 });
438 double r = rhs[static_cast<std::size_t>(i)] - acc / cellVolume(i);
439 res[static_cast<std::size_t>(i)] = r;
440 s += cellVolume(i) * r * r;
441 }
442 return std::sqrt(s);
443 }
444
446 void gaussSeidel(std::vector<double>& u, const std::vector<double>& rhs, int sweeps) const {
447 const Index n = numLeaves();
448 for (int s = 0; s < sweeps; ++s)
449 for (Index i = 0; i < n; ++i) {
450 double sumOff = 0.0, diag = 0.0;
451 forEachFaceNeighbor(i, [&](Index j, Real c, int, double a) {
452 sumOff += a * c * u[static_cast<std::size_t>(j)];
453 diag += a * c;
454 });
455 if (diag != 0.0)
456 u[static_cast<std::size_t>(i)] =
457 (sumOff - cellVolume(i) * rhs[static_cast<std::size_t>(i)]) / diag;
458 }
459 }
460
462 void removeMean(std::vector<double>& u) const {
463 const Index n = numLeaves();
464 double sum = 0.0, vol = 0.0;
465 for (Index i = 0; i < n; ++i) {
466 sum += cellVolume(i) * u[static_cast<std::size_t>(i)];
467 vol += cellVolume(i);
468 }
469 double m = sum / vol;
470 for (Index i = 0; i < n; ++i)
471 u[static_cast<std::size_t>(i)] -= m;
472 }
473
474 const Octree& octree() const { return *t_; }
475 Real h0() const { return h0_; }
478 const std::array<Coord, Dim>& fineExt() const { return fineExt_; }
479 const Vec<Dim>& origin() const { return origin_; }
480
481 private:
482 Coord wrap(long c, int axis) const {
483 long e = static_cast<long>(fineExt_[axis]);
484 return static_cast<Coord>(((c % e) + e) % e);
485 }
486 // A_f / d_f (physical) for a cell of width-units `si` next to one of `sj`.
487 Real coeff(Coord si, Coord sj) const {
488 Real dist = 0.5 * (static_cast<Real>(si) + static_cast<Real>(sj)) * h0_;
489 return areaOf(si < sj ? si : sj) / dist;
490 }
491 // Physical area of a face of a cell of width-units `s`: (s*h0)^(Dim-1).
492 Real areaOf(Coord s) const {
493 Real area = 1;
494 for (int d = 0; d < Dim - 1; ++d)
495 area *= static_cast<Real>(s) * h0_;
496 return area;
497 }
498
499 const Octree* t_ = nullptr;
500 Real h0_ = 1.0;
501 std::array<Coord, Dim> fineExt_{};
502 Vec<Dim> origin_{};
503 std::vector<double> alpha_; // per-leaf per-face openness (kFaces per leaf), or empty
504 bool hasOpen_ = false;
505 bool periodic_ = true; // false ⇒ homogeneous Dirichlet domain walls (boundaryDiag)
506 bool immersedWall_ = false; // true ⇒ velocity operator: (1−α) interior faces are no-slip walls
507};
508
510template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
512 public:
514 using M = typename Octree::M;
515 using Code = typename Octree::Code;
516
519 void build(const Octree& finest, Real h0) {
520 levels_.clear();
521 levels_.push_back(finest);
522 for (;;) {
523 Octree c = levels_.back();
524 Index merged = c.coarsenIf([](Code, unsigned) { return true; });
525 if (merged == 0 || c.numLeaves() == levels_.back().numLeaves())
526 break;
527 levels_.push_back(c);
528 if (c.numLeaves() == 1)
529 break;
530 }
531 ops_.resize(levels_.size());
532 // All levels share the finest h0: a coarse octree's leaves carry a higher
533 // `level`, and cellWidth = h0 * 2^level already encodes the doubled width.
534 for (std::size_t L = 0; L < levels_.size(); ++L)
535 ops_[L].init(levels_[L], h0);
536 // child(fine slot) -> parent(coarse slot) for each fine/coarse pair.
537 c2p_.assign(levels_.size() ? levels_.size() - 1 : 0, {});
538 for (std::size_t L = 0; L + 1 < levels_.size(); ++L) {
539 const Octree& f = levels_[L];
540 const Octree& c = levels_[L + 1];
541 c2p_[L].resize(static_cast<std::size_t>(f.numLeaves()));
542 for (Index i = 0; i < f.numLeaves(); ++i) {
543 Code parent = M::from_code(f.code(i)).ancestor(f.level(i) + 1).code();
544 c2p_[L][static_cast<std::size_t>(i)] = c.find(parent);
545 }
546 }
547 }
548
549 std::size_t numLevels() const { return levels_.size(); }
550 const AmrPoisson<Dim, Bits>& op(std::size_t L = 0) const { return ops_[L]; }
551
554 void setPeriodic(bool p) {
555 for (auto& o : ops_)
556 o.setPeriodic(p);
557 }
558
562 void setImmersedWall(bool w) {
563 for (auto& o : ops_)
564 o.setImmersedWall(w);
565 }
566
568 void vcycle(std::size_t L, std::vector<double>& u, const std::vector<double>& rhs, int pre = 2,
569 int post = 2) {
570 if (L + 1 == levels_.size()) {
571 ops_[L].gaussSeidel(u, rhs, 40); // coarsest: solve hard
572 ops_[L].removeMean(u);
573 return;
574 }
575 ops_[L].gaussSeidel(u, rhs, pre);
576 std::vector<double> res;
577 ops_[L].residual(u, rhs, res);
578
579 // Restrict residual: volume-weighted average of children -> parent.
580 const Octree& f = levels_[L];
581 const Octree& c = levels_[L + 1];
582 std::vector<double> crhs(static_cast<std::size_t>(c.numLeaves()), 0.0);
583 std::vector<double> cvol(static_cast<std::size_t>(c.numLeaves()), 0.0);
584 for (Index i = 0; i < f.numLeaves(); ++i) {
585 Index p = c2p_[L][static_cast<std::size_t>(i)];
586 if (p < 0)
587 continue;
588 Real v = ops_[L].cellVolume(i);
589 crhs[static_cast<std::size_t>(p)] += v * res[static_cast<std::size_t>(i)];
590 cvol[static_cast<std::size_t>(p)] += v;
591 }
592 for (Index p = 0; p < c.numLeaves(); ++p)
593 if (cvol[static_cast<std::size_t>(p)] > 0)
594 crhs[static_cast<std::size_t>(p)] /= cvol[static_cast<std::size_t>(p)];
595
596 std::vector<double> ccorr(static_cast<std::size_t>(c.numLeaves()), 0.0);
597 vcycle(L + 1, ccorr, crhs, pre, post);
598
599 // Prolong correction (piecewise constant) and add.
600 for (Index i = 0; i < f.numLeaves(); ++i) {
601 Index p = c2p_[L][static_cast<std::size_t>(i)];
602 if (p >= 0)
603 u[static_cast<std::size_t>(i)] += ccorr[static_cast<std::size_t>(p)];
604 }
605 ops_[L].gaussSeidel(u, rhs, post);
606 ops_[L].removeMean(u);
607 }
608
613 double solveQuad(std::vector<double>& u, const std::vector<double>& rhs, int outer = 30,
614 int cyclesPerOuter = 1) {
615 AmrPoisson<Dim, Bits>& P = ops_[0];
616 const std::size_t n = u.size();
617 std::vector<double> lq, ls, rhsp(n), res;
618 double r = 0.0;
619 for (int o = 0; o < outer; ++o) {
620 P.applyLaplacianQuad(u, lq);
621 P.applyLaplacian(u, ls);
622 for (std::size_t i = 0; i < n; ++i)
623 rhsp[i] = rhs[i] - (lq[i] - ls[i]);
624 for (int c = 0; c < cyclesPerOuter; ++c)
625 vcycle(0, u, rhsp);
626 r = P.residualQuad(u, rhs, res);
627 }
628 return r;
629 }
630
637 template <class OpenFn>
639 if (levels_.empty())
640 return;
641 ops_[0].buildOpenness(openFn);
642 for (std::size_t L = 0; L + 1 < levels_.size(); ++L)
643 coarsenOpenness(L);
644 }
645
646 private:
647 static int faceIdx(int axis, int dir) { return 2 * axis + (dir > 0 ? 0 : 1); }
648
649 // Area-average the level-L face openness onto level L+1 (each coarse face is the
650 // mean of the 2^(Dim-1) fine sub-faces covering it; equal areas => plain mean).
651 void coarsenOpenness(std::size_t L) {
652 const Octree& f = levels_[L];
653 const Octree& c = levels_[L + 1];
654 const int F = 2 * Dim;
655 std::vector<double> ca(static_cast<std::size_t>(c.numLeaves()) * F, 0.0);
656 std::vector<int> cnt(static_cast<std::size_t>(c.numLeaves()) * F, 0);
657 for (Index i = 0; i < f.numLeaves(); ++i) {
658 Index p = c2p_[L][static_cast<std::size_t>(i)];
659 if (p < 0)
660 continue;
661 const std::size_t base = static_cast<std::size_t>(p) * F;
662 if (c.level(p) == f.level(i)) {
663 // identity (cell not coarsened this round): copy every face.
664 for (int axis = 0; axis < Dim; ++axis)
665 for (int dir = -1; dir <= 1; dir += 2) {
666 int fi = faceIdx(axis, dir);
667 ca[base + fi] += ops_[L].faceOpenness(i, axis, dir);
668 cnt[base + fi] += 1;
669 }
670 } else {
671 // merged child: each axis contributes its outward face to the parent face.
672 unsigned oct = M::from_code(f.code(i)).child_index(f.level(i));
673 for (int axis = 0; axis < Dim; ++axis) {
674 int dir = ((oct >> axis) & 1) ? +1 : -1;
675 int fi = faceIdx(axis, dir);
676 ca[base + fi] += ops_[L].faceOpenness(i, axis, dir);
677 cnt[base + fi] += 1;
678 }
679 }
680 }
681 for (std::size_t k = 0; k < ca.size(); ++k)
682 ca[k] = cnt[k] ? ca[k] / cnt[k] : 1.0;
683 ops_[L + 1].setOpennessRaw(std::move(ca));
684 }
685
686 std::vector<Octree> levels_;
687 std::vector<AmrPoisson<Dim, Bits>> ops_;
688 std::vector<std::vector<Index>> c2p_;
689};
690
691} // namespace peclet::core::amr
692
693#endif // PECLET_CORE_HAVE_MORTON
694#endif // PECLET_CORE_AMR_POISSON_HPP
Geometric multigrid for AmrPoisson over a uniformly-coarsened octree hierarchy.
Definition poisson.hpp:511
void setPeriodic(bool p)
Apply a boundary condition to every level (periodic default, or non-periodic homogeneous Dirichlet).
Definition poisson.hpp:554
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:519
const AmrPoisson< Dim, Bits > & op(std::size_t L=0) const
Definition poisson.hpp:550
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:568
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:613
typename Octree::Code Code
Definition poisson.hpp:515
void setOpenness(OpenFn &&openFn)
Set cut-cell face openness on the finest level from a geometry callable openFn(faceCentreWorld,...
Definition poisson.hpp:638
BlockOctree< Dim, Bits > Octree
Definition poisson.hpp:513
void setImmersedWall(bool w)
Enable the immersed no-slip (Dirichlet) wall on every level — the velocity operator.
Definition poisson.hpp:562
std::size_t numLevels() const
Definition poisson.hpp:549
Cell-centered FV Poisson operator on one (periodic) block octree.
Definition poisson.hpp:43
const std::array< Coord, Dim > & fineExt() const
Per-axis fine-grid extent (brick·2^lmax) — the periodic wrap modulus.
Definition poisson.hpp:478
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:389
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:279
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:168
const Vec< Dim > & origin() const
Definition poisson.hpp:479
const Octree & octree() const
Definition poisson.hpp:474
const std::vector< double > & opennessRaw() const
Definition poisson.hpp:76
Real cellWidth(Index i) const
Definition poisson.hpp:155
void init(const Octree &t, Real h0)
Definition poisson.hpp:53
AmrPoisson(const Octree &t, Real h0)
Definition poisson.hpp:51
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:69
typename Octree::M M
Definition poisson.hpp:46
typename Octree::Coord Coord
Definition poisson.hpp:48
static int faceIndex(int axis, int dir)
Definition poisson.hpp:66
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:402
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:314
void setImmersedWall(bool w)
Immersed no-slip (Dirichlet) wall mode.
Definition poisson.hpp:125
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:446
void setOpennessRaw(std::vector< double > a)
Definition poisson.hpp:77
void setOrigin(const Vec< Dim > &o)
Definition poisson.hpp:62
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:133
Index periodicNeighbor(Index i, int axis, int dir) const
Periodic face neighbour leaf (covering the cell just across the face).
Definition poisson.hpp:261
static constexpr int kFaces
Definition poisson.hpp:65
void setPeriodic(bool p)
Boundary condition: periodic (default) wraps every face; non-periodic treats a domain-boundary face a...
Definition poisson.hpp:115
void removeMean(std::vector< double > &u) const
Subtract the volume-weighted mean (fixes the periodic null space).
Definition poisson.hpp:462
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:427
void applyLaplacian(const std::vector< double > &u, std::vector< double > &out) const
out = L u (periodic FV Laplacian).
Definition poisson.hpp:413
void forEachFaceFull(Index i, Fn &&fn) const
Like forEachFaceNeighbor but exposes geometry for a consistent FV divergence/gradient: fn(neighbour,...
Definition poisson.hpp:220
typename Octree::Code Code
Definition poisson.hpp:47
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:336
void buildOpenness(OpenFn &&openFn)
Build face openness from a geometry callable openFn(faceCentreWorld, axis) -> [0,1] (1 = fully fluid,...
Definition poisson.hpp:86
FvAssembled assembleFv() const
Definition poisson.hpp:362
BlockOctree< Dim, Bits > Octree
Definition poisson.hpp:45
Real cellVolume(Index i) const
Definition poisson.hpp:156
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.
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
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.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:355
std::vector< double > invVol
1/V_i, size n
Definition poisson.hpp:356
std::vector< double > coef
w = A_f/d_f·openness per face, size nFaces
Definition poisson.hpp:359
std::vector< Index > nbr
neighbour leaf per face, size nFaces
Definition poisson.hpp:358
std::vector< Index > start
CSR row offsets, size n+1.
Definition poisson.hpp:357
std::vector< double > bcDiag
Dirichlet boundary diagonal per cell (0 if periodic)
Definition poisson.hpp:360