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
momentum_assembly.hpp
Go to the documentation of this file.
1// core — device assembly of the cut-cell MOMENTUM operator (D3), on the S1 CSR primitive.
2//
3// AmrCutCell::build (host) samples the SDF per cell and runs buildCutStencil to produce the
4// ξ-overlay stencil (AC_/off_/cut_/rscale_); assembleOperator (host) then merges that stencil, the
5// regular-fluid C/F-aware ∇², and the implicit-FOU advection into one diag + face-CSR via a
6// std::vector<vector<pair>>. For a moving boundary that whole walk reruns every step on the host
7// and round-trips the operator.
8//
9// This header moves the two device-portable halves onto the device:
10// * the per-cell ξ stencil rebuild — buildCutStencil is pure math (now MORTON_HD), fed the cell +
11// 6
12// neighbour SDF samples (staged like D2 stages openness; the SDF *sampling* itself, like
13// openness sampling, stays host until a device SDF exists); and
14// * the operator merge — the three per-cell branches (solid identity / ξ-overlay / regular ∇²·μ)
15// plus
16// the advection fold, emitted as a face-CSR through buildFaceCsr (S1) + a per-cell diagonal.
17// The regular-fluid faces reuse the D2 FvFaceEmit traversal (α=1) via a scaling sink adapter, so
18// the 2:1 enumeration and coeff are shared, not re-derived.
19//
20// Bit-exactness: emit order, branch logic and arithmetic mirror assembleOperator exactly, each cell
21// fills its own CSR slice (S1, atomic-free), so on OpenMP the device MomentumOp == host
22// assembleOperator bit-for-bit (test_amr_momentum). GPU is tolerance-not-bit-exact (FMA), per the
23// convention.
24//
25#ifndef PECLET_AMR_MOMENTUM_ASSEMBLY_HPP
26#define PECLET_AMR_MOMENTUM_ASSEMBLY_HPP
27
28#include "peclet/amr/assembly.hpp" // FvFaceEmit (the α=1 ∇² geometry traversal)
30#include "peclet/amr/common.hpp"
31#include "peclet/amr/csr.hpp"
34#include "peclet/core/common/view.hpp"
35
36namespace peclet::amr {
37
41template <class Sink>
44 double factor;
45 KOKKOS_INLINE_FUNCTION void operator()(Index j, double c) const { (*sink)(j, factor* c); }
46};
47
49struct MomSumSink {
50 double sum = 0.0;
51 KOKKOS_INLINE_FUNCTION void operator()(Index /*j*/, double c) { sum += c; }
52};
53
57template <unsigned Bits = 21u>
65 double idiag = 0.0, mu = 1.0;
66 bool hasAdv = false, scaleAdv = false;
67
68 KOKKOS_INLINE_FUNCTION double cellVolume(Index i) const { return geom.cellVolume(i); }
69
72 template <class Sink>
74 if (!fluid(i))
75 return; // solid: identity row, no off-diagonals
76 const std::size_t s = static_cast<std::size_t>(i);
77 if (cut(i)) {
78 for (int k = 0; k < 6; ++k) {
79 const double a = off(s * 6 + k);
80 if (a == 0.0)
81 continue;
82 const Index j = nbr6(s * 6 + k);
83 if (j >= 0)
84 sink(j, a);
85 }
86 } else {
87 const double invV = 1.0 / geom.cellVolume(i);
89 geom(i, adapter); // emits (j, -mu·invV·(a·c)) per C/F face (a=1)
90 }
91 if (hasAdv) {
92 const double as = scaleAdv ? rscale(i) : 1.0;
93 for (Index p = advStart(i); p < advStart(i + 1); ++p)
94 sink(advNbr(p), as * advCoef(p));
95 }
96 }
97
101 if (!fluid(i))
102 return 1.0;
103 double d;
104 if (cut(i)) {
105 d = AC(i);
106 } else {
107 const double invV = 1.0 / geom.cellVolume(i);
109 geom(i, ss);
110 d = idiag + mu * invV * ss.sum;
111 }
112 if (hasAdv) {
113 const double as = scaleAdv ? rscale(i) : 1.0;
114 d += as * advDiag(i);
115 }
116 return d;
117 }
118};
119
124template <unsigned Bits>
125void rebuildCutStencil(Index n, const double betaIn[3], double idiag, const View<double>& sdfC,
126 const View<Index>& nbr6, const View<char>& fluid,
127 const View<std::uint8_t>& levels, View<double> AC, View<double> off,
128 View<char> cut, View<double> rscale) {
129 // Phase 3: capture the three axis coefficients by value into the device lambda. `betaIn` is
130 // mu/h_finest^2, so it must be divided by 4^level per leaf — the device twin of the host
131 // AmrCutCell::build Pass 2 (see the long note there; bit-for-bit the old expressions at level 0).
132 const double b0 = betaIn[0], b1 = betaIn[1], b2 = betaIn[2], id0 = idiag;
133 Kokkos::parallel_for(
134 "amr::cut_stencil", n, KOKKOS_LAMBDA(const Index i) {
135 const double f = static_cast<double>(Index(1) << levels(i)); // h_leaf / h_finest
136 const double inv = 1.0 / (f * f);
137 const double beta[3] = {b0 * inv, b1 * inv, b2 * inv};
138 const double AC0 = id0 + ((2.0 * beta[0] + 2.0 * beta[1]) + 2.0 * beta[2]);
139 const std::size_t s = static_cast<std::size_t>(i);
140 if (!fluid(i)) { // solid: identity row
141 AC(i) = 1.0;
142 for (int k = 0; k < 6; ++k)
143 off(s * 6 + k) = 0.0;
144 cut(i) = 0;
145 rscale(i) = 1.0;
146 return;
147 }
148 double sdf_n[6];
149 bool anyGhost = false;
150 for (int k = 0; k < 6; ++k) {
151 const Index j = nbr6(s * 6 + k);
152 sdf_n[k] = (j >= 0) ? sdfC(j) : -1.0; // missing neighbour => solid
153 if (sdf_n[k] < 0.0)
154 anyGhost = true;
155 }
156 cut(i) = anyGhost ? 1 : 0;
157 double AClocal = AC0, offl[6];
158 for (int k = 0; k < 6; ++k)
159 offl[k] = -beta[k / 2];
160 double rsl = 1.0, inhoml = 0.0;
161 if (anyGhost)
163 AC(i) = AClocal;
164 for (int k = 0; k < 6; ++k)
165 off(s * 6 + k) = offl[k];
166 rscale(i) = rsl;
167 });
168}
169
175template <unsigned Bits>
177 bool scaleAdvByRscale = false) {
178 const Index n = ov.numLeaves();
179 // Phase 3: beta per axis, and the diagonal as one parenthesised sum (Rule B).
180 const Vec<3> betaV = ccop.beta();
181 const double beta[3] = {betaV[0], betaV[1], betaV[2]};
182
183 // Stage build inputs + rebuild the ξ stencil on device.
184 View<double> sdfC = toDevice(ccop.sdfCRaw(), "mom::sdfC");
185 View<Index> nbr6 = toDevice(ccop.nbRaw(), "mom::nb");
186 View<char> fluid = toDevice(ccop.fluidRaw(), "mom::fluid");
187 View<double> AC(Kokkos::view_alloc("mom::AC", Kokkos::WithoutInitializing),
188 static_cast<std::size_t>(n));
189 View<double> off(Kokkos::view_alloc("mom::off", Kokkos::WithoutInitializing),
190 static_cast<std::size_t>(n) * 6);
191 View<char> cut(Kokkos::view_alloc("mom::cut", Kokkos::WithoutInitializing),
192 static_cast<std::size_t>(n));
193 View<double> rscale(Kokkos::view_alloc("mom::rscale", Kokkos::WithoutInitializing),
194 static_cast<std::size_t>(n));
195 rebuildCutStencil<Bits>(n, beta, ccop.idiag(), sdfC, nbr6, fluid, ov.levels, AC, off, cut,
196 rscale);
197
198 // The α=1 ∇² geometry for regular fluid cells (reuses the D2 traversal with openness off).
200 geom.ov = ov;
201 for (int d = 0; d < 3; ++d)
202 geom.h0[d] = ccop.lap().h0()[d];
203 geom.periodic = ccop.lap().periodic();
204 geom.hasOpen = false;
205 for (int d = 0; d < 3; ++d)
206 geom.fineExt[d] = ccop.lap().fineExt()[d];
207
209 emit.geom = geom;
210 emit.fluid = fluid;
211 emit.cut = cut;
212 emit.AC = AC;
213 emit.off = off;
214 emit.rscale = rscale;
215 emit.nbr6 = nbr6;
216 emit.idiag = ccop.idiag();
217 emit.mu = ccop.mu();
218 emit.hasAdv = ccop.hasAdv();
219 emit.scaleAdv = scaleAdvByRscale;
220 if (ccop.hasAdv()) {
221 emit.advDiag = toDevice(ccop.advDiagRaw(), "mom::advDiag");
222 emit.advCoef = toDevice(ccop.advCoefRaw(), "mom::advCoef");
223 emit.advStart = toDevice(ccop.advStartRaw(), "mom::advStart");
224 emit.advNbr = toDevice(ccop.advNbrRaw(), "mom::advNbr");
225 }
226
227 Csr csr = buildFaceCsr(n, emit);
228
229 MomentumOp op;
230 op.n = n;
231 op.faceStart = csr.start;
232 op.faceNbr = csr.nbr;
233 op.faceCoef = csr.coef;
234 op.hasAdv = false; // advection folded into the single CSR (as host assembleOperator does)
235 op.diag = View<double>(Kokkos::view_alloc("mom::diag", Kokkos::WithoutInitializing),
236 static_cast<std::size_t>(n));
237 View<double> diagV = op.diag;
238 const MomFaceEmit<Bits> e = emit;
239 Kokkos::parallel_for("amr::mom_diag", n, KOKKOS_LAMBDA(const Index i) { diagV(i) = e.diag(i); });
240 return op;
241}
242
243} // namespace peclet::amr
244
245#endif // PECLET_AMR_MOMENTUM_ASSEMBLY_HPP
static void buildCutStencil(double sdf_c, const double sdf_n[6], const double beta[3], double AC0, double &ACout, double off[6], double &rscaleOut, double &inhomOut)
Phase 3: beta[3] — the viscous coefficient of each AXIS; face k uses beta[k/2].
Definition cut_cell.hpp:630
void rebuildCutStencil(Index n, const double betaIn[3], double idiag, const View< double > &sdfC, const View< Index > &nbr6, const View< char > &fluid, const View< std::uint8_t > &levels, View< double > AC, View< double > off, View< char > cut, View< double > rscale)
The per-cell ξ-overlay rebuild (build() Pass 2) on device: from the staged cell SDF + neighbour indic...
Csr buildFaceCsr(Index n, const Emit &emit)
Build a face-CSR on device from a per-cell emit functor.
Definition csr.hpp:89
MomentumOp assembleMomentum(const AmrCutCell< Bits > &ccop, const BlockOctreeView< 3, Bits > &ov, bool scaleAdvByRscale=false)
Assemble the MomentumOp entirely on the device from a built host AmrCutCell + a device octree view.
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
Device mirror of a BlockOctree's leaf arrays + device-callable queries.
An assembled face-CSR: row offsets (size n+1), neighbour index + coefficient per face (size nFaces).
Definition csr.hpp:72
Device-callable reproduction of AmrPoisson's per-cell face walk + geometry.
Definition assembly.hpp:36
Coord fineExt[Dim]
periodic-wrap modulus per axis
Definition assembly.hpp:44
View_t ov
device octree (codes/levels/locate)
Definition assembly.hpp:42
Real h0[Dim]
finest cell width PER AXIS (Phase 3); set by the emitters
Definition assembly.hpp:45
Device-callable reproduction of AmrCutCell::assembleOperator's per-cell row + diagonal.
void operator()(Index i, Sink &sink) const
Emit the off-diagonal entries of row i (S1 fill order): ξ-overlay for cut cells, −μ·invV·(a·c) for re...
View< const Index > nbr6
n·6 periodic face-neighbour indices (cut-stencil order)
View< const Index > advStart
double diag(Index i) const
Diagonal of row i: 1 (solid), AC (cut), or idiag + μ·invV·Σ(a·c) (regular), plus folded advection.
View< const double > advCoef
FvFaceEmit< 3, Bits > geom
α=1 (hasOpen=false) C/F-aware ∇² traversal for regular fluid cells
double cellVolume(Index i) const
View< const double > advDiag
View< const double > rscale
off is n·6 (ξ-overlay), AC/rscale are n
Sink adapter: forwards each face from the FvFaceEmit traversal as (j, factor·coef) to the real CSR si...
void operator()(Index j, double c) const
Sink that just sums the geometric coeffs of a cell's faces (Σ a·c for the regular diagonal).
void operator()(Index, double c)