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
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#ifndef PECLET_AMR_FV_OP_HPP
15#define PECLET_AMR_FV_OP_HPP
16
18#include "peclet/amr/common.hpp"
19#include "peclet/amr/face_csr.hpp" // shared host+device FV (weight-CSR) row kernels
20#include "peclet/core/common/view.hpp"
21
22namespace peclet::amr {
23
25template <int Dim, unsigned Bits>
27 const Index n = dev.numLeaves();
28 Kokkos::parallel_for(
29 "amr::device_laplacian", n, KOKKOS_LAMBDA(const Index i) {
30 double s = 0.0;
31 for (int axis = 0; axis < Dim; ++axis)
32 for (int dir = -1; dir <= 1; dir += 2) {
33 Index j = dev.faceNeighbor(i, axis, dir);
34 if (j >= 0)
35 s += x(j) - x(i);
36 }
37 y(i) = inv * s;
38 });
39}
40
44template <int Dim, unsigned Bits>
46 View<double> lx, double inv, double omega) {
47 const Index n = dev.numLeaves();
48 const double diag = 2.0 * Dim * inv;
49 // lx = L x = ∇² x
50 Kokkos::parallel_for(
51 "amr::device_jacobi_apply", n, KOKKOS_LAMBDA(const Index i) {
52 double s = 0.0;
53 for (int axis = 0; axis < Dim; ++axis)
54 for (int dir = -1; dir <= 1; dir += 2) {
55 Index j = dev.faceNeighbor(i, axis, dir);
56 if (j >= 0)
57 s += x(j) - x(i);
58 }
59 lx(i) = inv * s;
60 });
61 Kokkos::parallel_for(
62 "amr::device_jacobi_update", n,
63 KOKKOS_LAMBDA(const Index i) { x(i) += omega * (lx(i) - b(i)) / diag; });
64}
65
66// ===========================================================================
67// Consistent FV (graded) operator as a precomputed face CSR.
68//
69// The plain operator above ignores 2:1-interface geometry (it treats every face
70// as same-level, spacing h0). The *consistent* conservative FV Laplacian is
71// (L u)_i = invVol_i · Σ_{f ∈ [start_i, start_{i+1})} w_f · (u[nbr_f] − u_i),
72// with w_f = openness · A_f/d_f and the coarse side of a 2:1 interface carrying
73// one face per fine sub-neighbour — exactly the host AmrPoisson enumeration
74// (forEachFaceNeighbor). The CSR is built once on the host in that same face
75// order, so the device operator is bit-identical to the host operator (and the
76// graded V-cycle converges instead of stalling on the inconsistent plain op).
77// ===========================================================================
78struct FvOp {
84 Index n = 0;
85 // Helmholtz generalisation: the applied operator is H = c0·I + cD·L (c0=0, cD=1 ⇒ the
86 // pure Laplacian L, the default — bit-exact unchanged). Setting c0=idiag, cD=−μ turns the
87 // existing hierarchy into the momentum operator idiag·I − μ∇², so Multigrid can be
88 // used as a (non-singular when c0≠0) preconditioner for the momentum BiCGStab. The L path
89 // is bit-exact preserved: c0·u+cD·(L) with c0=0,cD=1 is L exactly in IEEE arithmetic, and
90 // the Jacobi point-solve branches on (c0==0 && cD==1) to keep the original expression.
91 double c0 = 0.0;
92 double cD = 1.0;
93};
94
99 v.n = op.n;
100 v.invVol = op.invVol;
101 v.coef = op.faceW;
102 v.start = op.faceStart;
103 v.nbr = op.faceNbr;
104 v.bcDiag = op.bcDiag;
105 v.c0 = op.c0;
106 v.cD = op.cD;
107 return v;
108}
109
112inline void applyFv(const FvOp& op, View<const double> u, View<double> Lu) {
113 const auto A = fvView(op);
114 Kokkos::parallel_for(
115 "amr::fv_apply", op.n, KOKKOS_LAMBDA(const Index i) { Lu(i) = fvApplyRow(A, i, u); });
116}
117
120 View<double> res) {
121 const auto A = fvView(op);
122 Kokkos::parallel_for(
123 "amr::fv_residual", op.n,
124 KOKKOS_LAMBDA(const Index i) { res(i) = rhs(i) - fvApplyRow(A, i, u); });
125}
126
134 double omega) {
135 const auto A = fvView(op);
136 Kokkos::parallel_for(
137 "amr::fv_jacobi_compute", op.n,
138 KOKKOS_LAMBDA(const Index i) { tmp(i) = fvPointSolve(A, i, u, rhs(i), u(i)); });
139 Kokkos::parallel_for(
140 "amr::fv_jacobi_update", op.n,
141 KOKKOS_LAMBDA(const Index i) { u(i) = (1.0 - omega) * u(i) + omega * tmp(i); });
142}
143
149inline void removeMeanFv(const FvOp& op, View<double> u) {
150 auto invVol = op.invVol;
151 auto fs = op.faceStart;
152 auto fw = op.faceW;
153 auto bc = op.bcDiag;
154 double sx = 0.0, sv = 0.0;
155 // Numerator and denominator share the same active-cell diagonal d = bc + Σfw; fold them into a
156 // single multi-output reduction so that diagonal is walked once instead of twice. Each output
157 // keeps its original term verbatim (u/invVol, 1/invVol) and its own Sum reducer over the same
158 // range, so the result is bit-identical to the two separate reduces. (The subtract pass below
159 // recomputes d a third time but is a parallel_for, where reloading a cached mask would cost the
160 // same as the walk.)
161 Kokkos::parallel_reduce(
162 "amr::fv_rmean", op.n,
163 KOKKOS_LAMBDA(const Index i, double& an, double& ad) {
164 double d = bc(i);
165 for (Index k = fs(i); k < fs(i + 1); ++k)
166 d += fw(k);
167 if (d > 1e-30) {
168 an += u(i) / invVol(i); // V_i = 1/invVol_i
169 ad += 1.0 / invVol(i);
170 }
171 },
172 sx, sv);
173 if (sv <= 0.0)
174 return;
175 const double m = sx / sv;
176 Kokkos::parallel_for(
177 "amr::fv_rmean_sub", op.n, KOKKOS_LAMBDA(const Index i) {
178 double d = bc(i);
179 for (Index k = fs(i); k < fs(i + 1); ++k)
180 d += fw(k);
181 if (d > 1e-30)
182 u(i) -= m;
183 });
184}
185
191 Kokkos::parallel_for(
192 "amr::fv_quad_delta", n, KOKKOS_LAMBDA(const Index i) {
193 double acc = 0.0;
194 for (Index k = qStart(i); k < qStart(i + 1); ++k)
195 acc += qCoef(k) * u(qSlot(k));
196 dq(i) = acc;
197 });
198}
199
200} // namespace peclet::amr
201
202#endif // PECLET_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:26
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:97
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:149
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:45
void residualFv(const FvOp &op, View< const double > u, View< const double > rhs, View< double > res)
res = rhs − H u.
Definition fv_op.hpp:119
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:133
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:112
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:189
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.
View< Index > faceStart
CSR row offsets, size n+1.
Definition fv_op.hpp:80
View< double > bcDiag
Dirichlet boundary diagonal per cell (0 if periodic), size n.
Definition fv_op.hpp:83
View< double > invVol
1/V_i, size n
Definition fv_op.hpp:79
View< Index > faceNbr
neighbour leaf per face, size nFaces
Definition fv_op.hpp:81
View< double > faceW
w_f = openness·A_f/d_f per face, size nFaces
Definition fv_op.hpp:82