core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
csr_bicgstab.hpp
Go to the documentation of this file.
1// core — preconditioned BiCGStab (and defect correction / plain Jacobi) for an assembled face-CSR
2// operator on the device (Kokkos).
3//
4// Lifted verbatim from the AMR tree (peclet/core/amr/momentum.hpp, 2026-09-10; QUALITY_PLAN G.2).
5// The class carried a vestigial `Bits` template parameter there (it never touched the octree) —
6// dropped here; the AMR package keeps `MomentumSolver<Bits>` as an alias template. Reuses the
7// device matvec + Kokkos reductions of csr_operator.hpp; the default preconditioner is `jacPre`
8// damped-Jacobi sweeps of A (diagonal-dominant ⇒ a cheap, effective smoother-preconditioner), and
9// setPreconditioner() takes any z = M⁻¹ r callable (the AMR Galerkin momentum multigrid, an AMG).
10// setDistributed() makes the same iteration run multi-rank through two callables (ghost refresh +
11// global dot), so this header stays MPI-free. Consumers: peclet-amr's AmrFlow, voro's OT optimiser.
12#ifndef PECLET_CORE_SOLVER_CSR_BICGSTAB_HPP
13#define PECLET_CORE_SOLVER_CSR_BICGSTAB_HPP
14
15#include <cmath>
16#include <cstddef>
17#include <functional>
18#include <utility>
19
24
25namespace peclet::core::solver {
26
27// ---------------------------------------------------------------------------
28// Jacobi-preconditioned BiCGStab for the (non-symmetric) momentum operator. Reuses the
29// device matvec + Kokkos reductions; the preconditioner is `jacPre` damped-Jacobi sweeps
30// of A (diagonal-dominant ⇒ a cheap, effective smoother-preconditioner). Robust where
31// plain Jacobi stalls (large dt / weak reaction term).
32// ---------------------------------------------------------------------------
34 public:
35 void setJacobi(int preSweeps, double omega) {
36 jacPre_ = preSweeps;
37 omega_ = omega;
38 }
39
49 void setDistributed(std::function<void(View<double>)> refresh,
50 std::function<double(double)> dotReduce, Index nExt) {
51 haloFn_ = std::move(refresh);
52 dotReduce_ = std::move(dotReduce);
53 nExt_ = nExt;
54 }
55
63 precFn_ = std::move(fn);
64 }
65
69 ensure(op.n);
70 for (int s = 0; s < sweeps; ++s) {
71 sync(u);
72 jacobiMom(op, u, b, tmp_, omega_);
73 }
74 sync(u);
76 return std::sqrt(dot(View<const double>(r_), View<const double>(r_), op.n));
77 }
78
79 struct Result {
80 int iters = 0;
81 double res0 = 0.0;
82 double res = 0.0;
83 };
84
93 int maxIters = 200, double tol = 1e-8) {
94 const Index n = op.n;
95 ensure(n);
96 Result R;
97 sync(u);
99 R.res0 = std::sqrt(dot(View<const double>(r_), View<const double>(r_), n));
100 if (R.res0 == 0.0)
101 return R;
102 double rnorm = R.res0;
103 int it = 0;
104 for (; it < maxIters; ++it) {
105 applyPrec(op, r_, phat_); // phat = M⁻¹ r
106 axpy(u, 1.0, View<const double>(phat_), n); // u += phat
107 sync(u);
109 rnorm = std::sqrt(dot(View<const double>(r_), View<const double>(r_), n));
110 if (rnorm <= tol * R.res0) {
111 ++it;
112 break;
113 }
114 }
115 R.iters = it;
116 R.res = rnorm;
117 return R;
118 }
119
123 int maxIters = 500, double tol = 1e-10) {
124 const Index n = op.n;
125 ensure(n);
126 Result R;
127 // r = b − A u
128 sync(u);
130 Kokkos::deep_copy(rhat_, r_); // shadow residual
131 R.res0 = std::sqrt(dot(View<const double>(r_), View<const double>(r_), n));
132 if (R.res0 == 0.0)
133 return R;
134 double rho = 1, alpha = 1, omega = 1;
135 Kokkos::deep_copy(v_, 0.0);
136 Kokkos::deep_copy(p_, 0.0);
137 double rnorm = R.res0;
138 int it = 0;
139 for (; it < maxIters; ++it) {
140 double rhoNew = dot(View<const double>(rhat_), View<const double>(r_), n);
141 if (rhoNew == 0.0)
142 break;
143 double beta = (rhoNew / rho) * (alpha / omega);
144 // p = r + beta (p − omega v)
146 applyPrec(op, p_, phat_); // phat = M^{-1} p
147 sync(phat_);
148 applyMom(op, View<const double>(phat_), v_);
149 double rhatV = dot(View<const double>(rhat_), View<const double>(v_), n);
150 alpha = rhoNew / rhatV;
151 // s = r − alpha v
152 Kokkos::deep_copy(s_, r_);
153 axpy(s_, -alpha, View<const double>(v_), n);
154 double snorm = std::sqrt(dot(View<const double>(s_), View<const double>(s_), n));
155 if (snorm <= tol * R.res0) {
156 axpy(u, alpha, View<const double>(phat_), n); // u += alpha phat
157 rnorm = snorm;
158 ++it;
159 break;
160 }
161 applyPrec(op, s_, shat_); // shat = M^{-1} s
162 sync(shat_);
163 applyMom(op, View<const double>(shat_), t_);
164 double tt = dot(View<const double>(t_), View<const double>(t_), n);
165 omega = (tt != 0.0) ? dot(View<const double>(t_), View<const double>(s_), n) / tt : 0.0;
166 // u += alpha phat + omega shat
167 axpy(u, alpha, View<const double>(phat_), n);
168 axpy(u, omega, View<const double>(shat_), n);
169 // r = s − omega t
170 Kokkos::deep_copy(r_, s_);
171 axpy(r_, -omega, View<const double>(t_), n);
172 rnorm = std::sqrt(dot(View<const double>(r_), View<const double>(r_), n));
173 if (rnorm <= tol * R.res0) {
174 ++it;
175 break;
176 }
177 rho = rhoNew;
178 if (omega == 0.0)
179 break;
180 }
181 R.iters = it;
182 R.res = rnorm;
183 return R;
184 }
185
186 private:
187 // z = M^{-1} v : the generic MG preconditioner if set, else `jacPre_` damped-Jacobi sweeps of
188 // A z = v starting from z = 0.
189 void applyPrec(const MomentumOp& op, View<double> v, View<double> z) {
190 if (precFn_) {
191 precFn_(View<const double>(v), z);
192 return;
193 }
194 Kokkos::deep_copy(z, 0.0);
195 if (jacPre_ <= 0) { // no preconditioner ⇒ identity
196 Kokkos::deep_copy(z, v);
197 return;
198 }
199 for (int s = 0; s < jacPre_; ++s) {
200 if (s)
201 sync(z); // ghosts of the previous iterate (first sweep: z = 0 everywhere already)
202 jacobiMom(op, z, View<const double>(v), tmp_, omega_);
203 }
204 }
207 void sync(View<double> v) const {
208 if (haloFn_)
209 haloFn_(v);
210 }
212 double dot(View<const double> a, View<const double> b, Index n) const {
213 const double s = dotPlain(a, b, n);
214 return dotReduce_ ? dotReduce_(s) : s;
215 }
216 void ensure(Index n) {
217 if (nExt_ > n)
218 n = nExt_; // scratch carries the ghost tail in distributed solves
219 if (r_.extent(0) == static_cast<std::size_t>(n))
220 return;
221 auto mk = [&](const char* l) { return View<double>(l, static_cast<std::size_t>(n)); };
222 r_ = mk("mom_r");
223 rhat_ = mk("mom_rhat");
224 p_ = mk("mom_p");
225 phat_ = mk("mom_phat");
226 v_ = mk("mom_v");
227 s_ = mk("mom_s");
228 shat_ = mk("mom_shat");
229 t_ = mk("mom_t");
230 tmp_ = mk("mom_tmp");
231 }
232
233 View<double> r_, rhat_, p_, phat_, v_, s_, shat_, t_, tmp_;
234 int jacPre_ = 2;
235 double omega_ = 0.7;
236 std::function<void(View<const double>, View<double>)> precFn_; // generic z = M^{-1} r
237 std::function<void(View<double>)> haloFn_; // ghost-tail refresh (unset ⇒ no-op)
238 std::function<double(double)> dotReduce_; // global dot reduction (unset ⇒ local)
239 Index nExt_ = 0; // extended (local+ghost) scratch size
240};
241
242} // namespace peclet::core::solver
243
244#endif // PECLET_CORE_SOLVER_CSR_BICGSTAB_HPP
void setDistributed(std::function< void(View< double >)> refresh, std::function< double(double)> dotReduce, Index nExt)
Distributed solve (docs/amr_distributed_flow.md, rung 2): refresh re-fills the ghost tail [op....
double solveJacobi(const MomentumOp &op, View< double > u, View< const double > b, int sweeps)
Plain weighted-Jacobi solve (the simple parallel mirror of the host GS smoother): sweeps damped-Jacob...
Result solveBiCGStab(const MomentumOp &op, View< double > u, View< const double > b, int maxIters=500, double tol=1e-10)
Jacobi-preconditioned BiCGStab solve of A u = b in place.
void setJacobi(int preSweeps, double omega)
void setPreconditioner(std::function< void(View< const double >, View< double >)> fn)
Set a generic preconditioner z = M⁻¹ r (a host callable that launches device kernels) — the multigrid...
Result solveDefectCorrection(const MomentumOp &op, View< double > u, View< const double > b, int maxIters=200, double tol=1e-8)
MG-preconditioned defect-correction (Richardson) solve of A u = b in place: u ← u + M⁻¹(b − A u),...
void bicgPUpdate(View< double > p, View< const double > r, View< const double > v, double beta, double omega, Index n)
BiCGStab direction update: p = r + β(p − ω v).
double dotPlain(View< const double > a, View< const double > b, Index n)
Plain (unweighted) dot product.
void applyMom(const MomentumOp &op, View< const double > u, View< double > Au)
Au = A u (cut-cell operator + optional implicit-FOU advection).
void axpy(View< double > y, double a, View< const double > x, Index n)
y += a·x
void residualMom(const MomentumOp &op, View< const double > u, View< const double > b, View< double > res)
res = b − A u.
void jacobiMom(const MomentumOp &op, View< double > u, View< const double > b, View< double > tmp, double omega)
One weighted-Jacobi sweep of A u = b (in place).
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
Assembled momentum operator on the device: (A u)_i = diag_i u_i + Σ coef·u[nbr], with an optional imp...