core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
pcg.hpp
Go to the documentation of this file.
1// core — device (Kokkos) multigrid-preconditioned CG for the AMR FV Poisson.
2//
3// A Krylov accelerator on top of the existing device machinery: the matvec is the
4// consistent conservative FV Laplacian `applyFv` (poisson.hpp), the
5// preconditioner is one (or a few) Multigrid V-cycle(s) (multigrid.hpp),
6// and the inner products / vector updates are Kokkos reductions / parallel_fors. This
7// is exactly flow's structured MG-PCG, ported onto the AMR octree CSR: CG accelerates
8// the geometric MG so a given residual is reached in far fewer fine-grid matvecs than
9// stationary V-cycling, on whatever backend Kokkos targets (CUDA / HIP / OpenMP).
10//
11// SPD subtlety: the FV operator L = D^{-1} S (D = diag(cell volume), S the symmetric
12// stencil) is *not* symmetric in the Euclidean inner product, but it is symmetric and
13// negative-definite in the volume-weighted inner product <u,v>_D = Σ V_i u_i v_i. So CG
14// runs on A := −L (SPD in <·,·>_D) and every dot product is volume-weighted. The V-cycle
15// preconditioner solves L z = −r (correction scheme) ⇒ z ≈ A^{-1} r.
16//
17// Singular (periodic, pure-Neumann) case: A has the constant nullspace. The RHS, the
18// residual, and the preconditioned residual are projected volume-weighted-mean-zero each
19// iteration (deflated CG) so the iteration stays in the range space. The homogeneous-
20// Dirichlet build is non-singular (bcDiag > 0) ⇒ no projection.
21//
22// The MG preconditioner is bit-exact deterministic (Jacobi smoother); CG itself depends
23// on global-reduction summation order, so this is the *performance* path — validated by
24// convergence + matching the V-cycle's converged solution, not by host bit-exactness.
25//
26// Requires a Kokkos build + the morton checkout (PECLET_CORE_HAVE_MORTON).
27#ifndef PECLET_CORE_AMR_PCG_HPP
28#define PECLET_CORE_AMR_PCG_HPP
29
30#ifdef PECLET_CORE_HAVE_MORTON
31
32#include <cmath>
33
37
38namespace peclet::core::amr {
39
40// ---- small device vector primitives (volume-weighted where the FV operator needs it) ----
41
44 Index n) {
45 double s = 0.0;
46 Kokkos::parallel_reduce(
47 "amr::pcg_dotvol", n,
48 KOKKOS_LAMBDA(const Index i, double& acc) { acc += (u(i) * v(i)) / invVol(i); }, s);
49 return s;
50}
51
57inline void buildFluidMask(const FvOp& op, View<double> mask, Index n) {
58 auto start = op.faceStart;
59 auto w = op.faceW;
60 auto bc = op.bcDiag;
61 Kokkos::parallel_for(
62 "amr::pcg_fluidmask", n, KOKKOS_LAMBDA(const Index i) {
63 double d = bc(i);
64 for (Index k = start(i); k < start(i + 1); ++k)
65 d += w(k);
66 mask(i) = (d > 1e-30) ? 1.0 : 0.0;
67 });
68}
69
72 Kokkos::parallel_for("amr::pcg_masksolid", n, KOKKOS_LAMBDA(const Index i) { u(i) *= mask(i); });
73}
74
79 Index n) {
80 Kokkos::parallel_for("amr::pcg_masksolid", n, KOKKOS_LAMBDA(const Index i) { u(i) *= mask(i); });
81 double su = 0.0, sv = 0.0;
82 Kokkos::parallel_reduce(
83 "amr::pcg_meannum", n,
84 KOKKOS_LAMBDA(const Index i, double& a) { a += mask(i) * u(i) / invVol(i); }, su);
85 Kokkos::parallel_reduce(
86 "amr::pcg_meanden", n, KOKKOS_LAMBDA(const Index i, double& a) { a += mask(i) / invVol(i); },
87 sv);
88 const double m = (sv > 0.0) ? su / sv : 0.0;
89 Kokkos::parallel_for(
90 "amr::pcg_meansub", n, KOKKOS_LAMBDA(const Index i) { u(i) -= mask(i) * m; });
91}
92
94inline void axpy(View<double> y, double a, View<const double> x, Index n) {
95 Kokkos::parallel_for("amr::pcg_axpy", n, KOKKOS_LAMBDA(const Index i) { y(i) += a * x(i); });
96}
97
99inline void zpby(View<double> p, View<const double> z, double b, Index n) {
100 Kokkos::parallel_for(
101 "amr::pcg_zpby", n, KOKKOS_LAMBDA(const Index i) { p(i) = z(i) + b * p(i); });
102}
103
105inline void negate(View<double> x, Index n) {
106 Kokkos::parallel_for("amr::pcg_negate", n, KOKKOS_LAMBDA(const Index i) { x(i) = -x(i); });
107}
108
109// ---------------------------------------------------------------------------
110// MG-preconditioned CG over a Multigrid, driving the system L x = rhs on its
111// finest level. Owns the Krylov scratch; reuses the multigrid's own finest x/b as
112// transient preconditioner storage. Solves A x = b_A with A := −L (SPD in <·,·>_D).
113// ---------------------------------------------------------------------------
114template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
115class PCG {
116 public:
118
119 struct Result {
120 int iters = 0;
121 double res0 = 0.0;
122 double res = 0.0;
123 };
124
126 void setVcycle(int pre, int post, int bottom, double omega) {
127 pre_ = pre;
128 post_ = post;
129 bottom_ = bottom;
130 omega_ = omega;
131 }
133 void setCyclesPerPrec(int k) { cyclesPerPrec_ = k; }
136 void setSingular(bool s) { singular_ = s; }
137
142 double tol = 1e-10) {
143 const Index n = mg.numLeaves(0);
144 const FvOp& op = mg.op(0);
145 View<const double> invVol(op.invVol);
146 ensure(n);
147 buildFluidMask(op, mask_, n);
149 Result R;
150 // Project onto the fluid range: always zero the solid cells (their per-region null modes); for
151 // the singular (periodic/all-Neumann) operator also remove the fluid constant. Applied to every
152 // Krylov quantity so the iteration stays in the well-posed fluid range (mirrors flow
153 // removeMean∘maskSolid).
154 auto project = [&](View<double> u) {
155 if (singular_)
156 removeMeanVol(u, invVol, mask, n);
157 else
158 maskSolid(u, mask, n);
159 };
160
161 // x = 0 ; r = b_A − A·0 = b_A = −rhs (A = −L, b_A = −rhs)
162 Kokkos::deep_copy(x, 0.0);
163 Kokkos::deep_copy(r_, rhs);
164 negate(r_, n);
165 project(r_);
166 R.res0 = std::sqrt(dotVol(View<const double>(r_), View<const double>(r_), invVol, n));
167 if (R.res0 == 0.0)
168 return R;
169
170 applyPrec(mg, r_, z_, n); // z = M^{-1} r ≈ A^{-1} r
171 project(z_);
172 Kokkos::deep_copy(p_, z_);
173 double rz = dotVol(View<const double>(r_), View<const double>(z_), invVol, n);
174
175 int it = 0;
176 double rnorm = R.res0;
177 for (; it < maxIters; ++it) {
178 // Ap = A p = −L p, projected back onto the fluid range (keeps the search directions there).
179 applyFv(op, View<const double>(p_), Ap_);
180 negate(Ap_, n);
181 project(Ap_);
182 double pAp = dotVol(View<const double>(p_), View<const double>(Ap_), invVol, n);
183 if (pAp == 0.0)
184 break;
185 double alpha = rz / pAp;
186 axpy(x, alpha, View<const double>(p_), n); // x += α p
187 axpy(r_, -alpha, View<const double>(Ap_), n); // r −= α Ap
188 project(r_);
189 rnorm = std::sqrt(dotVol(View<const double>(r_), View<const double>(r_), invVol, n));
190 if (rnorm <= tol * R.res0) {
191 ++it;
192 break;
193 }
194 applyPrec(mg, r_, z_, n);
195 project(z_);
196 double rzNew = dotVol(View<const double>(r_), View<const double>(z_), invVol, n);
197 double beta = rzNew / rz;
198 zpby(p_, View<const double>(z_), beta, n); // p = z + β p
199 rz = rzNew;
200 }
201 project(x); // solid cells exactly 0; fluid mean removed (singular)
202 R.iters = it;
203 R.res = rnorm;
204 return R;
205 }
206
207 private:
208 // z = M^{-1} r : solve L z = −r with `cyclesPerPrec_` V-cycles (correction scheme),
209 // using the multigrid's own finest x/b as scratch. (A = −L ⇒ A z = r ⟺ L z = −r.)
210 void applyPrec(MG& mg, View<double> r, View<double> z, Index n) {
211 Kokkos::deep_copy(mg.b(0), r);
212 negate(mg.b(0), n);
213 Kokkos::deep_copy(mg.x(0), 0.0);
214 for (int k = 0; k < cyclesPerPrec_; ++k)
215 mg.vcycle(pre_, post_, bottom_, omega_);
216 Kokkos::deep_copy(z, mg.x(0));
217 }
218
219 void ensure(Index n) {
220 if (r_.extent(0) == static_cast<std::size_t>(n))
221 return;
222 r_ = View<double>("pcg_r", static_cast<std::size_t>(n));
223 z_ = View<double>("pcg_z", static_cast<std::size_t>(n));
224 p_ = View<double>("pcg_p", static_cast<std::size_t>(n));
225 Ap_ = View<double>("pcg_Ap", static_cast<std::size_t>(n));
226 mask_ = View<double>("pcg_fluidmask", static_cast<std::size_t>(n));
227 }
228
229 View<double> r_, z_, p_, Ap_, mask_;
230 int pre_ = 2, post_ = 2, bottom_ = 40;
231 double omega_ = 0.8;
232 int cyclesPerPrec_ = 1;
233 bool singular_ = true;
234};
235
236} // namespace peclet::core::amr
237
238#endif // PECLET_CORE_HAVE_MORTON
239#endif // PECLET_CORE_AMR_PCG_HPP
Result solve(MG &mg, View< double > x, View< const double > rhs, int maxIters=200, double tol=1e-10)
Solve L x = rhs on mg's finest level into x (size n).
Definition pcg.hpp:141
Multigrid< Dim, Bits > MG
Definition pcg.hpp:117
void setVcycle(int pre, int post, int bottom, double omega)
V-cycle parameters used for the preconditioner application.
Definition pcg.hpp:126
void setCyclesPerPrec(int k)
Number of V-cycles per preconditioner application (default 1).
Definition pcg.hpp:133
void setSingular(bool s)
Whether to project out the constant nullspace (default true; set false for the non-singular homogeneo...
Definition pcg.hpp:136
void axpy(View< double > y, double a, View< const double > x, Index n)
y += a·x
Definition pcg.hpp:94
void removeMeanVol(View< double > u, View< const double > invVol, View< const double > mask, Index n)
Project u onto the FLUID range: zero solid cells, then subtract the volume-weighted mean over the flu...
Definition pcg.hpp:78
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 zpby(View< double > p, View< const double > z, double b, Index n)
p = z + b·p (CG direction update)
Definition pcg.hpp:99
void negate(View< double > x, Index n)
y = −x (negate in place)
Definition pcg.hpp:105
void maskSolid(View< double > u, View< const double > mask, Index n)
Zero the solid cells (project out the solid null modes).
Definition pcg.hpp:71
void buildFluidMask(const FvOp &op, View< double > mask, Index n)
Build the fluid mask: mask(i)=1 where the operator diagonal Σ_f w_f (+ bcDiag) is non-trivial,...
Definition pcg.hpp:57
double dotVol(View< const double > u, View< const double > v, View< const double > invVol, Index n)
Volume-weighted dot <u,v>_D = Σ_i V_i u_i v_i, V_i = 1/invVol_i.
Definition pcg.hpp:43
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
double res0
initial volume-weighted residual norm
Definition pcg.hpp:121
double res
final volume-weighted residual norm
Definition pcg.hpp:122