core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
fv_op.hpp
Go to the documentation of this file.
1// core — device (Kokkos) Poisson operator + smoother on a BlockOctreeView.
2//
3// The device compute path for the AMR octree: the Laplacian matvec and a weighted-
4// Jacobi smoother run as Kokkos::parallel_for over the leaf Views, using the
5// device-callable face-neighbour walk of BlockOctreeView (block_octree_kokkos.hpp).
6// Same arithmetic as the host BlockOctree::faceNeighbor, so the result is identical
7// to the host operator (validated bit-for-bit in tests/test_amr_device_poisson_kokkos).
8//
9// Scope: the plain (uniform, openness-free) Laplacian over the leaves; a face with
10// no neighbour (block edge) is skipped (homogeneous Neumann). Openness / graded /
11// distributed-on-device build on this + the device halo.
12//
13// Requires a Kokkos build (MORTON_ENABLE_KOKKOS ⇒ MORTON_HD = KOKKOS_FUNCTION) and
14// the morton checkout (PECLET_CORE_HAVE_MORTON).
15#ifndef PECLET_CORE_AMR_FV_OP_HPP
16#define PECLET_CORE_AMR_FV_OP_HPP
17
18#ifdef PECLET_CORE_HAVE_MORTON
19
21#include "peclet/core/amr/face_csr.hpp" // shared host+device FV (weight-CSR) row kernels
23
24namespace peclet::core::amr {
25
27template <int Dim, unsigned Bits>
29 const Index n = dev.numLeaves();
30 Kokkos::parallel_for(
31 "amr::device_laplacian", n, KOKKOS_LAMBDA(const Index i) {
32 double s = 0.0;
33 for (int axis = 0; axis < Dim; ++axis)
34 for (int dir = -1; dir <= 1; dir += 2) {
35 Index j = dev.faceNeighbor(i, axis, dir);
36 if (j >= 0)
37 s += x(j) - x(i);
38 }
39 y(i) = inv * s;
40 });
41}
42
46template <int Dim, unsigned Bits>
48 View<double> lx, double inv, double omega) {
49 const Index n = dev.numLeaves();
50 const double diag = 2.0 * Dim * inv;
51 // lx = L x = ∇² x
52 Kokkos::parallel_for(
53 "amr::device_jacobi_apply", n, KOKKOS_LAMBDA(const Index i) {
54 double s = 0.0;
55 for (int axis = 0; axis < Dim; ++axis)
56 for (int dir = -1; dir <= 1; dir += 2) {
57 Index j = dev.faceNeighbor(i, axis, dir);
58 if (j >= 0)
59 s += x(j) - x(i);
60 }
61 lx(i) = inv * s;
62 });
63 Kokkos::parallel_for(
64 "amr::device_jacobi_update", n,
65 KOKKOS_LAMBDA(const Index i) { x(i) += omega * (lx(i) - b(i)) / diag; });
66}
67
68// ===========================================================================
69// Consistent FV (graded) operator as a precomputed face CSR.
70//
71// The plain operator above ignores 2:1-interface geometry (it treats every face
72// as same-level, spacing h0). The *consistent* conservative FV Laplacian is
73// (L u)_i = invVol_i · Σ_{f ∈ [start_i, start_{i+1})} w_f · (u[nbr_f] − u_i),
74// with w_f = openness · A_f/d_f and the coarse side of a 2:1 interface carrying
75// one face per fine sub-neighbour — exactly the host AmrPoisson enumeration
76// (forEachFaceNeighbor). The CSR is built once on the host in that same face
77// order, so the device operator is bit-identical to the host operator (and the
78// graded V-cycle converges instead of stalling on the inconsistent plain op).
79// ===========================================================================
80struct FvOp {
86 Index n = 0;
87 // Helmholtz generalisation: the applied operator is H = c0·I + cD·L (c0=0, cD=1 ⇒ the
88 // pure Laplacian L, the default — bit-exact unchanged). Setting c0=idiag, cD=−μ turns the
89 // existing hierarchy into the momentum operator idiag·I − μ∇², so Multigrid can be
90 // used as a (non-singular when c0≠0) preconditioner for the momentum BiCGStab. The L path
91 // is bit-exact preserved: c0·u+cD·(L) with c0=0,cD=1 is L exactly in IEEE arithmetic, and
92 // the Jacobi point-solve branches on (c0==0 && cD==1) to keep the original expression.
93 double c0 = 0.0;
94 double cD = 1.0;
95};
96
101 v.n = op.n;
102 v.invVol = op.invVol;
103 v.coef = op.faceW;
104 v.start = op.faceStart;
105 v.nbr = op.faceNbr;
106 v.bcDiag = op.bcDiag;
107 v.c0 = op.c0;
108 v.cD = op.cD;
109 return v;
110}
111
114inline void applyFv(const FvOp& op, View<const double> u, View<double> Lu) {
115 const auto A = fvView(op);
116 Kokkos::parallel_for(
117 "amr::fv_apply", op.n, KOKKOS_LAMBDA(const Index i) { Lu(i) = fvApplyRow(A, i, u); });
118}
119
122 View<double> res) {
123 const auto A = fvView(op);
124 Kokkos::parallel_for(
125 "amr::fv_residual", op.n,
126 KOKKOS_LAMBDA(const Index i) { res(i) = rhs(i) - fvApplyRow(A, i, u); });
127}
128
136 double omega) {
137 const auto A = fvView(op);
138 Kokkos::parallel_for(
139 "amr::fv_jacobi_compute", op.n,
140 KOKKOS_LAMBDA(const Index i) { tmp(i) = fvPointSolve(A, i, u, rhs(i), u(i)); });
141 Kokkos::parallel_for(
142 "amr::fv_jacobi_update", op.n,
143 KOKKOS_LAMBDA(const Index i) { u(i) = (1.0 - omega) * u(i) + omega * tmp(i); });
144}
145
151inline void removeMeanFv(const FvOp& op, View<double> u) {
152 auto invVol = op.invVol;
153 auto fs = op.faceStart;
154 auto fw = op.faceW;
155 auto bc = op.bcDiag;
156 double sx = 0.0, sv = 0.0;
157 // Numerator and denominator share the same active-cell diagonal d = bc + Σfw; fold them into a
158 // single multi-output reduction so that diagonal is walked once instead of twice. Each output
159 // keeps its original term verbatim (u/invVol, 1/invVol) and its own Sum reducer over the same
160 // range, so the result is bit-identical to the two separate reduces. (The subtract pass below
161 // recomputes d a third time but is a parallel_for, where reloading a cached mask would cost the
162 // same as the walk.)
163 Kokkos::parallel_reduce(
164 "amr::fv_rmean", op.n,
165 KOKKOS_LAMBDA(const Index i, double& an, double& ad) {
166 double d = bc(i);
167 for (Index k = fs(i); k < fs(i + 1); ++k)
168 d += fw(k);
169 if (d > 1e-30) {
170 an += u(i) / invVol(i); // V_i = 1/invVol_i
171 ad += 1.0 / invVol(i);
172 }
173 },
174 sx, sv);
175 if (sv <= 0.0)
176 return;
177 const double m = sx / sv;
178 Kokkos::parallel_for(
179 "amr::fv_rmean_sub", op.n, KOKKOS_LAMBDA(const Index i) {
180 double d = bc(i);
181 for (Index k = fs(i); k < fs(i + 1); ++k)
182 d += fw(k);
183 if (d > 1e-30)
184 u(i) -= m;
185 });
186}
187
193 Kokkos::parallel_for(
194 "amr::fv_quad_delta", n, KOKKOS_LAMBDA(const Index i) {
195 double acc = 0.0;
196 for (Index k = qStart(i); k < qStart(i + 1); ++k)
197 acc += qCoef(k) * u(qSlot(k));
198 dq(i) = acc;
199 });
200}
201
202} // namespace peclet::core::amr
203
204#endif // PECLET_CORE_HAVE_MORTON
205#endif // PECLET_CORE_AMR_FV_OP_HPP
void laplacian(BlockOctreeView< Dim, Bits > dev, View< double > x, View< double > y, double inv)
y = inv · Σ_faces (x_nb − x_i) (= ∇² in spacing h0 with inv = 1/h0²), on device.
Definition fv_op.hpp:28
void applyFv(const FvOp &op, View< const double > u, View< double > Lu)
Hu = (c0·I + cD·L) u (consistent conservative FV Laplacian, c0=0/cD=1 ⇒ pure L).
Definition fv_op.hpp:114
void jacobiSweep(BlockOctreeView< Dim, Bits > dev, View< double > x, View< const double > b, View< double > lx, double inv, double omega)
One weighted-Jacobi sweep of L u = b with L = ∇² (negative-definite, diagonal −2·Dim·inv),...
Definition fv_op.hpp:47
void removeMeanFv(const FvOp &op, View< double > u)
Project u to volume-weighted-mean-zero over the ACTIVE (fluid) cells of the operator — the constant-n...
Definition fv_op.hpp:151
void quadDelta(View< const Index > qStart, View< const Index > qSlot, View< const double > qCoef, View< const double > u, View< double > dq, Index n)
dq = (L_quad − L_std) u, the quadratic coarse-fine correction as its own SpMV over a precomputed CSR ...
Definition fv_op.hpp:191
void jacobiFv(const FvOp &op, View< double > u, View< const double > rhs, View< double > tmp, double omega)
One weighted-Jacobi sweep of H u = rhs (in place).
Definition fv_op.hpp:135
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
FvCsrOpT< View< const double >, View< const Index > > fvView(const FvOp &op)
View the assembled FV operator through the shared backend-agnostic FvCsrOpT, so the device kernels an...
Definition fv_op.hpp:99
MORTON_HD double fvPointSolve(const Op &op, Index i, const U &u, double rhs_i, double uOld)
The point solve of H u = rhs for one row (raw value, before damping), matching the host point solve a...
Definition face_csr.hpp:131
void residualFv(const FvOp &op, View< const double > u, View< const double > rhs, View< double > res)
res = rhs − H u.
Definition fv_op.hpp:121
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
View< double > invVol
1/V_i, size n
Definition fv_op.hpp:81
View< double > faceW
w_f = openness·A_f/d_f per face, size nFaces
Definition fv_op.hpp:84
View< Index > faceStart
CSR row offsets, size n+1.
Definition fv_op.hpp:82
View< double > bcDiag
Dirichlet boundary diagonal per cell (0 if periodic), size n.
Definition fv_op.hpp:85
View< Index > faceNbr
neighbour leaf per face, size nFaces
Definition fv_op.hpp:83