core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
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// Requires a Kokkos build + the morton checkout (PECLET_CORE_HAVE_MORTON).
26#ifndef PECLET_CORE_AMR_MOMENTUM_ASSEMBLY_HPP
27#define PECLET_CORE_AMR_MOMENTUM_ASSEMBLY_HPP
28
29#ifdef PECLET_CORE_HAVE_MORTON
30
31#include "peclet/core/amr/assembly.hpp" // FvFaceEmit (the α=1 ∇² geometry traversal)
37
38namespace peclet::core::amr {
39
43template <class Sink>
46 double factor;
47 KOKKOS_INLINE_FUNCTION void operator()(Index j, double c) const { (*sink)(j, factor* c); }
48};
49
51struct MomSumSink {
52 double sum = 0.0;
53 KOKKOS_INLINE_FUNCTION void operator()(Index /*j*/, double c) { sum += c; }
54};
55
59template <unsigned Bits = 21u>
67 double idiag = 0.0, mu = 1.0;
68 bool hasAdv = false, scaleAdv = false;
69
70 KOKKOS_INLINE_FUNCTION double cellVolume(Index i) const { return geom.cellVolume(i); }
71
74 template <class Sink>
76 if (!fluid(i))
77 return; // solid: identity row, no off-diagonals
78 const std::size_t s = static_cast<std::size_t>(i);
79 if (cut(i)) {
80 for (int k = 0; k < 6; ++k) {
81 const double a = off(s * 6 + k);
82 if (a == 0.0)
83 continue;
84 const Index j = nbr6(s * 6 + k);
85 if (j >= 0)
86 sink(j, a);
87 }
88 } else {
89 const double invV = 1.0 / geom.cellVolume(i);
91 geom(i, adapter); // emits (j, -mu·invV·(a·c)) per C/F face (a=1)
92 }
93 if (hasAdv) {
94 const double as = scaleAdv ? rscale(i) : 1.0;
95 for (Index p = advStart(i); p < advStart(i + 1); ++p)
96 sink(advNbr(p), as * advCoef(p));
97 }
98 }
99
103 if (!fluid(i))
104 return 1.0;
105 double d;
106 if (cut(i)) {
107 d = AC(i);
108 } else {
109 const double invV = 1.0 / geom.cellVolume(i);
111 geom(i, ss);
112 d = idiag + mu * invV * ss.sum;
113 }
114 if (hasAdv) {
115 const double as = scaleAdv ? rscale(i) : 1.0;
116 d += as * advDiag(i);
117 }
118 return d;
119 }
120};
121
126template <unsigned Bits>
127void rebuildCutStencil(Index n, double beta, double AC0, const View<double>& sdfC,
128 const View<Index>& nbr6, const View<char>& fluid, View<double> AC,
129 View<double> off, View<char> cut, View<double> rscale) {
130 Kokkos::parallel_for(
131 "amr::cut_stencil", n, KOKKOS_LAMBDA(const Index i) {
132 const std::size_t s = static_cast<std::size_t>(i);
133 if (!fluid(i)) { // solid: identity row
134 AC(i) = 1.0;
135 for (int k = 0; k < 6; ++k)
136 off(s * 6 + k) = 0.0;
137 cut(i) = 0;
138 rscale(i) = 1.0;
139 return;
140 }
141 double sdf_n[6];
142 bool anyGhost = false;
143 for (int k = 0; k < 6; ++k) {
144 const Index j = nbr6(s * 6 + k);
145 sdf_n[k] = (j >= 0) ? sdfC(j) : -1.0; // missing neighbour => solid
146 if (sdf_n[k] < 0.0)
147 anyGhost = true;
148 }
149 cut(i) = anyGhost ? 1 : 0;
150 double AClocal = AC0, offl[6];
151 for (int k = 0; k < 6; ++k)
152 offl[k] = -beta;
153 double rsl = 1.0, inhoml = 0.0;
154 if (anyGhost)
156 AC(i) = AClocal;
157 for (int k = 0; k < 6; ++k)
158 off(s * 6 + k) = offl[k];
159 rscale(i) = rsl;
160 });
161}
162
168template <unsigned Bits>
170 bool scaleAdvByRscale = false) {
171 const Index n = ov.numLeaves();
172 const double beta = ccop.beta();
173 const double AC0 = ccop.idiag() + 6.0 * beta;
174
175 // Stage build inputs + rebuild the ξ stencil on device.
176 View<double> sdfC = toDevice(ccop.sdfCRaw(), "mom::sdfC");
177 View<Index> nbr6 = toDevice(ccop.nbRaw(), "mom::nb");
178 View<char> fluid = toDevice(ccop.fluidRaw(), "mom::fluid");
179 View<double> AC(Kokkos::view_alloc("mom::AC", Kokkos::WithoutInitializing),
180 static_cast<std::size_t>(n));
181 View<double> off(Kokkos::view_alloc("mom::off", Kokkos::WithoutInitializing),
182 static_cast<std::size_t>(n) * 6);
183 View<char> cut(Kokkos::view_alloc("mom::cut", Kokkos::WithoutInitializing),
184 static_cast<std::size_t>(n));
185 View<double> rscale(Kokkos::view_alloc("mom::rscale", Kokkos::WithoutInitializing),
186 static_cast<std::size_t>(n));
187 rebuildCutStencil<Bits>(n, beta, AC0, sdfC, nbr6, fluid, AC, off, cut, rscale);
188
189 // The α=1 ∇² geometry for regular fluid cells (reuses the D2 traversal with openness off).
191 geom.ov = ov;
192 geom.h0 = ccop.lap().h0();
193 geom.periodic = ccop.lap().periodic();
194 geom.hasOpen = false;
195 for (int d = 0; d < 3; ++d)
196 geom.fineExt[d] = ccop.lap().fineExt()[d];
197
199 emit.geom = geom;
200 emit.fluid = fluid;
201 emit.cut = cut;
202 emit.AC = AC;
203 emit.off = off;
204 emit.rscale = rscale;
205 emit.nbr6 = nbr6;
206 emit.idiag = ccop.idiag();
207 emit.mu = ccop.mu();
208 emit.hasAdv = ccop.hasAdv();
209 emit.scaleAdv = scaleAdvByRscale;
210 if (ccop.hasAdv()) {
211 emit.advDiag = toDevice(ccop.advDiagRaw(), "mom::advDiag");
212 emit.advCoef = toDevice(ccop.advCoefRaw(), "mom::advCoef");
213 emit.advStart = toDevice(ccop.advStartRaw(), "mom::advStart");
214 emit.advNbr = toDevice(ccop.advNbrRaw(), "mom::advNbr");
215 }
216
217 Csr csr = buildFaceCsr(n, emit);
218
219 MomentumOp op;
220 op.n = n;
221 op.faceStart = csr.start;
222 op.faceNbr = csr.nbr;
223 op.faceCoef = csr.coef;
224 op.hasAdv = false; // advection folded into the single CSR (as host assembleOperator does)
225 op.diag = View<double>(Kokkos::view_alloc("mom::diag", Kokkos::WithoutInitializing),
226 static_cast<std::size_t>(n));
228 const MomFaceEmit<Bits> e = emit;
229 Kokkos::parallel_for("amr::mom_diag", n, KOKKOS_LAMBDA(const Index i) { diagV(i) = e.diag(i); });
230 return op;
231}
232
233} // namespace peclet::core::amr
234
235#endif // PECLET_CORE_HAVE_MORTON
236#endif // PECLET_CORE_AMR_MOMENTUM_ASSEMBLY_HPP
static MORTON_HD void buildCutStencil(double sdf_c, const double sdf_n[6], double beta, double AC0, double &ACout, double off[6], double &rscaleOut, double &inhomOut)
Definition cut_cell.hpp:536
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.
Csr buildFaceCsr(Index n, const Emit &emit)
Build a face-CSR on device from a per-cell emit functor.
Definition csr.hpp:89
void rebuildCutStencil(Index n, double beta, double AC0, const View< double > &sdfC, const View< Index > &nbr6, const View< char > &fluid, 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...
View< T > toDevice(const std::vector< T > &h, const std::string &label)
Upload a host std::vector into a freshly-sized device View (empty vector => empty view).
Definition view.hpp:44
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
An assembled face-CSR: row offsets (size n+1), neighbour index + coefficient per face (size nFaces).
Definition csr.hpp:72
Coord fineExt[Dim]
periodic-wrap modulus per axis
Definition assembly.hpp:46
View_t ov
device octree (codes/levels/locate)
Definition assembly.hpp:44
Real h0
finest cell width
Definition assembly.hpp:47
Device-callable reproduction of AmrCutCell::assembleOperator's per-cell row + diagonal.
View< const Index > nbr6
n·6 periodic face-neighbour indices (cut-stencil order)
View< const double > rscale
off is n·6 (ξ-overlay), AC/rscale are n
double diag(Index i) const
Diagonal of row i: 1 (solid), AC (cut), or idiag + μ·invV·Σ(a·c) (regular), plus folded advection.
FvFaceEmit< 3, Bits > geom
α=1 (hasOpen=false) C/F-aware ∇² traversal for regular fluid cells
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...
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).
Assembled momentum operator on the device: (A u)_i = diag_i u_i + Σ coef·u[nbr], with an optional imp...
Definition momentum.hpp:43
View< double > diag
size n
Definition momentum.hpp:44
View< Index > faceNbr
neighbour leaf per off-diagonal, size nnz
Definition momentum.hpp:46
View< double > faceCoef
off-diagonal coefficient, size nnz
Definition momentum.hpp:47
View< Index > faceStart
CSR row offsets, size n+1.
Definition momentum.hpp:45