chombo-discharge
Loading...
Searching...
No Matches
CD_EllipticSolverChainImplem.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_ELLIPTICSOLVERCHAINIMPLEM_H
14#define CD_ELLIPTICSOLVERCHAINIMPLEM_H
15
16// Std includes
17#include <algorithm>
18
19// Chombo includes
20#include <CH_Timer.H>
21#include <parstream.H>
22
23// Our includes
25#include <CD_NamespaceHeader.H>
26
27namespace EllipticSolverChain {
28
29template <class T>
31{
32 this->freeWork();
33}
34
35template <class T>
36void
38{
39 m_bicg.undefine();
40 m_gmres.undefine();
41
42 if (m_workAllocated) {
43 m_op.clear(m_r0);
44 m_op.clear(m_dphi);
45 m_op.clear(m_phiInit);
46 m_workAllocated = false;
47 }
48}
49
50template <class T>
51void
53{
54 m_op.create(m_r0, a_template);
55 m_op.create(m_dphi, a_template);
56 m_op.create(m_phiInit, a_template);
57 m_workAllocated = true;
58}
59
60template <class T>
61void
63{
64 CH_TIME("EllipticSolverChain::KrylovSolver::snapshotGuess");
65
66 if (!m_workAllocated) {
67 this->allocateWork(a_phi);
68 }
69
70 m_op.assign(m_phiInit, a_phi);
71 m_initResidual = m_mg->computeAMRResidual(a_phi, a_rhs, m_lmax, m_lbase);
72}
73
74template <class T>
75bool
77{
78 CH_TIME("EllipticSolverChain::KrylovSolver::keepOrRestore");
79
80 const Real r = m_mg->computeAMRResidual(a_phi, a_rhs, m_lmax, m_lbase);
81
82 // Keep the just-failed solve only if it improved on the chain's initial residual; otherwise revert to the
83 // initial-residual solution.
84 if (r >= m_initResidual) {
85 m_op.assign(a_phi, m_phiInit);
86
87 return false;
88 }
89
90 return true;
91}
92
93template <class T>
94void
95KrylovSolver<T>::define(AMRMultiGrid<LevelData<T>>* a_mg,
96 const Vector<RefCountedPtr<LevelData<BaseFab<bool>>>>& a_validCells,
97 const Vector<Real>& a_dx,
98 const int a_lbase,
99 const int a_lmax,
100 const int a_numVCycles)
101{
102 CH_TIME("EllipticSolverChain::KrylovSolver::define");
103
104 // Release any storage tied to the previous grid before redefining the adapter for the new one. The work vectors
105 // are re-allocated lazily on the first solve (when a template hierarchy is available).
106 this->freeWork();
107
108 m_mg = a_mg;
109 m_lbase = a_lbase;
110 m_lmax = a_lmax;
111
112 m_op.define(a_mg, a_validCells, a_dx, a_lbase, a_lmax, a_numVCycles);
113
114 m_isDefined = true;
115}
116
117template <class T>
118bool
120 const Vec& a_rhs,
121 const bool a_zeroPhi,
122 const SolverType a_type,
123 const Settings& a_settings)
124{
125 CH_TIME("EllipticSolverChain::KrylovSolver::solve");
126
127 CH_assert(m_isDefined);
128
129 // Allocate the persistent residual-correction vectors once per grid (first solve after a define()/regrid).
130 if (!m_workAllocated) {
131 this->allocateWork(a_rhs);
132 }
133
134 if (a_zeroPhi) {
135 m_op.setToZero(a_phi);
136 }
137
138 // The preconditioner drives AMRMultiGrid::AMRVCycle directly, which needs the solver's *intermediate* multigrid
139 // depths. AMRMultiGrid sets these up in init() and tears them down in revert(); the host solvers revert() after
140 // every solve (and the stand-alone GMG path re-init()s every solve), so we re-initialise here -- once per field
141 // solve, exactly as the GMG path does.
142 m_mg->init(a_phi, a_rhs, m_lmax, m_lbase);
143 m_mg->setBottomSolver(m_lmax, m_lbase);
144
145 // Zero-residual ||rhs - L(0)|| in the adapter norm. This is the scale the Krylov solvers both print against and
146 // test against, so that eps means what the host solver means by it: the hosts gate the whole solve on
147 // ||rhs - L(phi)|| <= eps*||rhs - L(0)||, and a Krylov solver that instead reduced by eps relative to its own
148 // initial residual would keep iterating long after the host was satisfied -- the better the warm start, the more
149 // of that surplus work, since the target would fall with the guess. m_dphi is the (zero) correction accumulator,
150 // reused here as the zero solution. A zero scale is passed through as-is; the solvers then fall back to their
151 // own initial residual.
152 m_op.setToZero(m_dphi);
153 m_mg->computeAMRResidual(m_r0, m_dphi, a_rhs, m_lmax, m_lbase, false, false);
154
155 const Real zeroResid = m_op.norm(m_r0, 2);
156
157 // Initial residual r0 = rhs - L_inhomogeneous(phi). This is the only inhomogeneous evaluation; the Krylov matvec
158 // and preconditioner run with homogeneous boundary conditions. computeAMRResidual returns rhs - L(phi) in
159 // Chombo's convention, so it must NOT be negated.
160 m_mg->computeAMRResidual(m_r0, a_phi, a_rhs, m_lmax, m_lbase, false, false);
161
162 const Real rNorm0 = m_op.norm(m_r0, 2);
163
164 bool converged = false;
165 int exitStatus = -1;
166 Real residualNorm = rNorm0;
167
168 switch (a_type) {
169 case SolverType::GMRES: {
170 const int restart = std::max(1, a_settings.restart);
171
172 if (!m_gmres.isDefined() || m_gmres.m_restart != restart) {
173 m_gmres.m_restart = restart;
174 m_gmres.define(&m_op, a_rhs);
175 }
176
177 m_gmres.m_eps = a_settings.eps;
178 m_gmres.m_maxIter = a_settings.maxIter;
179 m_gmres.m_verbosity = a_settings.verbosity;
180 m_gmres.m_residualScale = zeroResid;
181
182 converged = m_gmres.solve(m_dphi, m_r0);
183 exitStatus = m_gmres.m_exitStatus;
184 residualNorm = m_gmres.m_residualNorm;
185 m_lastKrylovIters = m_gmres.m_iterations;
186
187 break;
188 }
190 if (!m_bicg.isDefined()) {
191 m_bicg.define(&m_op, a_rhs);
192 }
193
194 m_bicg.m_eps = a_settings.eps;
195 m_bicg.m_maxIter = a_settings.maxIter;
196 m_bicg.m_verbosity = a_settings.verbosity;
197 m_bicg.m_residualScale = zeroResid;
198
199 converged = m_bicg.solve(m_dphi, m_r0);
200 exitStatus = m_bicg.m_exitStatus;
201 residualNorm = m_bicg.m_residualNorm;
202 m_lastKrylovIters = m_bicg.m_iterations;
203
204 break;
205 }
206 default: {
207 MayDay::Error("EllipticSolverChain::KrylovSolver::solve - SolverType must be GMRES or BiCGStab");
208
209 break;
210 }
211 }
212
213 // One-line convergence summary (the per-iteration history, if any, is printed by the solver itself). The residual
214 // is reported relative to the zero-residual (rel.res = ||r|| / ||r_zero||), the same solver-independent scale used
215 // in the per-iteration output, with the absolute norms kept for reference. The verbosity is the host's
216 // gmg_verbosity, reused for the Krylov path.
217 if (a_settings.verbosity >= 1) {
218 const Real relRes = (zeroResid > 0.0) ? residualNorm / zeroResid : residualNorm;
219
220 pout() << "EllipticSolverChain - " << solverTypeName(a_type) << (converged ? " converged" : " did NOT converge")
221 << " (rel.res = " << relRes << ", |r| = " << residualNorm << ", |r_zero| = " << zeroResid
222 << ", iters = " << m_lastKrylovIters << ", status = " << exitStatus << ")" << endl;
223 }
224
225 // phi += dphi.
226 m_op.incr(a_phi, m_dphi, 1.0);
227
228 return converged;
229}
230
231} // namespace EllipticSolverChain
232
233#include <CD_NamespaceFooter.H>
234
235#endif
Declaration of outer Krylov solves (GMRES/BiCGStab) preconditioned by an AMR-multigrid cycle.
bool keepOrRestore(Vec &a_phi, const Vec &a_rhs)
Warm-start rule applied between fallback attempts.
Definition CD_EllipticSolverChainImplem.H:76
bool solve(Vec &a_phi, const Vec &a_rhs, const bool a_zeroPhi, const SolverType a_type, const Settings &a_settings)
Solve L(phi) = rhs with the chosen outer Krylov method preconditioned by the multigrid cycle.
Definition CD_EllipticSolverChainImplem.H:119
~KrylovSolver() noexcept
Destructor. Frees the persistent work vectors and custom solvers.
Definition CD_EllipticSolverChainImplem.H:30
void snapshotGuess(Vec &a_phi, const Vec &a_rhs)
Snapshot the chain's starting guess and its residual for the fallback warm-start rule.
Definition CD_EllipticSolverChainImplem.H:62
Vector< LevelData< T > * > Vec
Multilevel data type.
Definition CD_EllipticSolverChain.H:317
void define(AMRMultiGrid< LevelData< T > > *a_mg, const Vector< RefCountedPtr< LevelData< BaseFab< bool > > > > &a_validCells, const Vector< Real > &a_dx, const int a_lbase, const int a_lmax, const int a_numVCycles)
Define the multigrid-preconditioner adapter and reset the persistent work. Call once per regrid.
Definition CD_EllipticSolverChainImplem.H:95
void allocateWork(const Vec &a_template)
Lazily allocate the persistent residual-correction vectors r0/dphi from a template hierarchy.
Definition CD_EllipticSolverChainImplem.H:52
void freeWork()
Free the persistent work vectors and undefine the custom solvers.
Definition CD_EllipticSolverChainImplem.H:37
Support code for using an AMR-multigrid cycle as a preconditioner for an outer Krylov solver.
Definition CD_EllipticSolverChain.H:37
std::string solverTypeName(const SolverType a_type)
Human-readable name for a solver type, for diagnostic output.
Definition CD_EllipticSolverChain.H:283
SolverType
Top-level solve method.
Definition CD_EllipticSolverChain.H:43
@ BiCGStab
BiCGStab preconditioned by the multigrid cycle.
@ GMRES
GMRES preconditioned by the multigrid cycle.
Outer-Krylov configuration. Defaults reproduce stand-alone multigrid (solvers == {GMG}).
Definition CD_EllipticSolverChain.H:64
Real eps
Relative residual tolerance.
Definition CD_EllipticSolverChain.H:74
int restart
GMRES restart length (unused for BiCGStab).
Definition CD_EllipticSolverChain.H:84
int maxIter
Maximum outer iterations.
Definition CD_EllipticSolverChain.H:79
int verbosity
Outer Krylov verbosity.
Definition CD_EllipticSolverChain.H:95