chombo-discharge
Loading...
Searching...
No Matches
CD_KrylovBiCGStabImplem.H
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021-2026 SINTEF Energy Research
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
13#ifndef CD_KRYLOVBICGSTABIMPLEM_H
14#define CD_KRYLOVBICGSTABIMPLEM_H
15
16// Chombo includes
17#include <CH_Timer.H>
18#include <parstream.H>
19
20// Our includes
21#include <CD_KrylovBiCGStab.H>
22#include <CD_NamespaceHeader.H>
23
24template <class T>
26{
27 this->undefine();
28}
29
30template <class T>
31void
32KrylovBiCGStab<T>::define(AMRMultigridKrylovOp<T>* a_op, const Vector<LevelData<T>*>& a_template) noexcept
33{
34 CH_TIME("KrylovBiCGStab::define");
35
36 this->undefine();
37
38 m_op = a_op;
39
40 // Allocate the work vectors once; they are reused across all solves until the next define() (regrid).
41 m_op->create(m_r, a_template);
42 m_op->create(m_rTilde, a_template);
43 m_op->create(m_e, a_template);
44 m_op->create(m_p, a_template);
45 m_op->create(m_pTilde, a_template);
46 m_op->create(m_sTilde, a_template);
47 m_op->create(m_t, a_template);
48 m_op->create(m_v, a_template);
49
50 m_isDefined = true;
51}
52
53template <class T>
54void
56{
57 if (m_isDefined) {
58 m_op->clear(m_r);
59 m_op->clear(m_rTilde);
60 m_op->clear(m_e);
61 m_op->clear(m_p);
62 m_op->clear(m_pTilde);
63 m_op->clear(m_sTilde);
64 m_op->clear(m_t);
65 m_op->clear(m_v);
66
67 m_isDefined = false;
68 }
69}
70
71template <class T>
72bool
74{
75 return m_isDefined;
76}
77
78template <class T>
79bool
80KrylovBiCGStab<T>::solve(Vector<LevelData<T>*>& a_phi, const Vector<LevelData<T>*>& a_rhs) noexcept
81{
82 CH_TIME("KrylovBiCGStab::solve");
83
84 CH_assert(m_isDefined);
85
86 AMRMultigridKrylovOp<T>& op = *m_op;
87
88 m_exitStatus = -1;
89
90 // Initialize the (homogeneous) residual r = rhs - A*phi and the BiCGStab state. The correction is accumulated in
91 // m_e and added to a_phi at the very end, so warm starts (a_phi != 0) are honoured.
92 auto computeResidual = [&]() -> void {
93 op.applyOp(m_r, a_phi, true); // r = A*phi
94 op.axby(m_r, a_rhs, m_r, 1.0, -1.0); // r = rhs - A*phi
95 };
96
97 computeResidual();
98 op.assign(m_rTilde, m_r);
99 op.setToZero(m_e);
100 op.setToZero(m_p);
101 op.setToZero(m_v);
102
103 const Real rnorm0 = op.norm(m_r, 2);
104
105 if (rnorm0 == 0.0) {
106 m_exitStatus = 1;
107 m_residualNorm = 0.0;
108 m_iterations = 0;
109 return true;
110 }
111
112 // Tolerance scale. m_residualScale is the host's zero-residual ||rhs - L(0)||; falling back to rnorm0 when the
113 // host did not supply one reproduces a plain reduction relative to this solve's own initial residual.
114 const Real scale = (m_residualScale > 0.0) ? m_residualScale : rnorm0;
115 const Real epsScaled = m_eps * scale;
116
117 Real rnorm = rnorm0;
118 Real rho = 1.0;
119 Real alpha = 1.0;
120 Real omega = 1.0;
121 int iter = 0;
122 int restarts = 0;
124 // Fold the work-so-far into a_phi and restart the iteration from the current solution. Used on breakdown.
125 auto restart = [&]() -> void {
126 op.incr(a_phi, m_e, 1.0);
127 op.setToZero(m_e);
128
129 computeResidual();
130
131 op.assign(m_rTilde, m_r);
132 op.setToZero(m_p);
133 op.setToZero(m_v);
134
135 rho = 1.0;
136 alpha = 1.0;
137 omega = 1.0;
138 rnorm = op.norm(m_r, 2);
139
140 restarts++;
141 };
142
143 // Treat a breakdown: restart if we still can, otherwise stop with status 2.
144 auto breakdown = [&]() -> bool {
145 if (restarts < m_numRestarts) {
146 restart();
147
148 return false; // continue
149 }
150
151 m_exitStatus = 2;
152
153 return true; // stop
154 };
155
156 while (iter < m_maxIter && rnorm > epsScaled) {
157 const Real rnormStart = rnorm; // residual at the start of this iteration (for the convergence-rate report)
158
159 const Real rhoNew = op.dotProduct(m_rTilde, m_r);
160
161 if (rhoNew == 0.0) {
162 if (breakdown()) {
163 break;
164 }
165
166 continue;
167 }
168
169 // p = r + beta*(p - omega*v). With the initial/post-restart state (p = v = 0, rho = alpha = omega = 1) this
170 // correctly reduces to p = r, so no special first-iteration branch is needed.
171 const Real beta = (rhoNew / rho) * (alpha / omega);
172 op.incr(m_p, m_v, -omega);
173 op.scale(m_p, beta);
174 op.incr(m_p, m_r, 1.0);
175
176 op.preCond(m_pTilde, m_p); // p_tilde = K^{-1} p
177 op.applyOp(m_v, m_pTilde, true); // v = A p_tilde
178
179 const Real rtv = op.dotProduct(m_rTilde, m_v);
180 if (rtv == 0.0) {
181 if (breakdown()) {
182 break;
183 }
184
185 continue;
186 }
187 alpha = rhoNew / rtv;
188
189 op.incr(m_e, m_pTilde, alpha); // e += alpha*p_tilde
190 op.incr(m_r, m_v, -alpha); // r = s = r - alpha*v
191
192 rnorm = op.norm(m_r, 2);
193 if (rnorm <= epsScaled) { // converged on the half-step s
194 m_exitStatus = 1;
195 iter++; // this (half) iteration was performed -- count it before breaking
196
197 break;
198 }
199
200 op.preCond(m_sTilde, m_r); // s_tilde = K^{-1} s
201 op.applyOp(m_t, m_sTilde, true); // t = A s_tilde
202
203 // omega = (t,s)/(t,t) -- both inner products in a single fused MPI reduction.
204 Real dots[2];
205 const Vector<LevelData<T>*> rhsPair[2] = {m_r, m_t};
206
207 op.mDotProduct(m_t, 2, rhsPair, dots);
208
209 const Real tDotT = dots[1];
210
211 if (tDotT == 0.0) {
212 if (breakdown()) {
213 break;
214 }
215
216 continue;
217 }
218
219 omega = dots[0] / tDotT;
220
221 op.incr(m_e, m_sTilde, omega); // e += omega*s_tilde
222 op.incr(m_r, m_t, -omega); // r = s - omega*t
223
224 rnorm = op.norm(m_r, 2);
225
226 rho = rhoNew;
227
228 if (m_verbosity >= 3) {
229 // Rate is the iteration-over-iteration reduction factor r_{k-1}/r_k (>1 = converging), matching AMRMultiGrid.
230 pout() << " KrylovBiCGStab:: iteration = " << iter << ", rel. residual = " << (rnorm / scale)
231 << ", rate = " << ((rnorm > 0.0) ? rnormStart / rnorm : 0.0) << endl;
232 }
233
234 if (rnorm <= epsScaled) {
235 m_exitStatus = 1;
236 iter++; // this iteration was performed -- count it before breaking
237
238 break;
239 }
240
241 if (omega == 0.0) {
242 if (breakdown()) {
243 break;
244 }
245
246 continue;
247 }
248
249 iter++;
250 }
251
252 if (m_exitStatus == -1) {
253 m_exitStatus = (rnorm <= epsScaled) ? 1 : 3; // converged on the loop guard, else max iterations
254 }
255
256 m_residualNorm = rnorm;
257 m_iterations = iter;
258
259 op.incr(a_phi, m_e, 1.0); // commit the accumulated correction
260
261 return (m_exitStatus == 1);
262}
263
264#include <CD_NamespaceFooter.H>
265
266#endif
Preconditioned BiCGStab over an AMR hierarchy, with persistent work vectors and fused reductions.
Adapter presenting an already-defined AMRMultiGrid as a LinearOp over the AMR hierarchy.
Definition CD_AMRMultigridKrylovOp.H:78
void incr(Vector< LevelData< T > * > &a_lhs, const Vector< LevelData< T > * > &a_x, Real a_scale) override
Increment a_lhs += a_scale*a_x.
Definition CD_AMRMultigridKrylovOpImplem.H:275
void assign(Vector< LevelData< T > * > &a_lhs, const Vector< LevelData< T > * > &a_rhs) override
Copy a_rhs into a_lhs.
Definition CD_AMRMultigridKrylovOpImplem.H:154
void preCond(Vector< LevelData< T > * > &a_cor, const Vector< LevelData< T > * > &a_residual) override
Apply the preconditioner: a_cor approx L^{-1} a_residual via m_numVCycles multigrid cycles (cycle typ...
Definition CD_AMRMultigridKrylovOpImplem.H:97
void mDotProduct(const Vector< LevelData< T > * > &a_1, const int a_sz, const Vector< LevelData< T > * > a_2[], Real a_mdots[]) override
Batched inner products a_mdots[k] = dotProduct(a_1, a_2[k]) for k=0..a_sz-1.
Definition CD_AMRMultigridKrylovOpImplem.H:214
void axby(Vector< LevelData< T > * > &a_lhs, const Vector< LevelData< T > * > &a_x, const Vector< LevelData< T > * > &a_y, Real a_a, Real a_b) override
Set a_lhs = a_a*a_x + a_b*a_y.
Definition CD_AMRMultigridKrylovOpImplem.H:284
void applyOp(Vector< LevelData< T > * > &a_lhs, const Vector< LevelData< T > * > &a_phi, bool a_homogeneous=false) override
Matrix-vector product L(phi) over the AMR hierarchy (always homogeneous for the Krylov path).
Definition CD_AMRMultigridKrylovOpImplem.H:74
Real norm(const Vector< LevelData< T > * > &a_rhs, int a_ord) override
Norm of a_rhs. For a_ord==2 this is the inner-product-induced norm sqrt(dotProduct(x,...
Definition CD_AMRMultigridKrylovOpImplem.H:306
Real dotProduct(const Vector< LevelData< T > * > &a_1, const Vector< LevelData< T > * > &a_2) override
AMR-composite inner product (allocation-free, fine-covered cells masked out).
Definition CD_AMRMultigridKrylovOpImplem.H:163
void setToZero(Vector< LevelData< T > * > &a_lhs) override
Set a_lhs = 0.
Definition CD_AMRMultigridKrylovOpImplem.H:322
void scale(Vector< LevelData< T > * > &a_lhs, const Real &a_scale) override
Scale a_lhs *= a_scale.
Definition CD_AMRMultigridKrylovOpImplem.H:297
bool isDefined() const noexcept
Whether define() has been called and the work vectors are allocated.
Definition CD_KrylovBiCGStabImplem.H:73
bool solve(Vector< LevelData< T > * > &a_phi, const Vector< LevelData< T > * > &a_rhs) noexcept
Solve a_op(a_phi) = a_rhs (homogeneous operator) with preconditioned BiCGStab.
Definition CD_KrylovBiCGStabImplem.H:80
void undefine() noexcept
Free the persistent work vectors.
Definition CD_KrylovBiCGStabImplem.H:55
void define(AMRMultigridKrylovOp< T > *a_op, const Vector< LevelData< T > * > &a_template) noexcept
Allocate the persistent work vectors. Call once per regrid.
Definition CD_KrylovBiCGStabImplem.H:32
~KrylovBiCGStab() noexcept
Destructor. Frees the persistent work vectors.
Definition CD_KrylovBiCGStabImplem.H:25