chombo-discharge
Loading...
Searching...
No Matches
CD_AMRMultigridKrylovOpImplem.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_AMRMULTIGRIDKRYLOVOPIMPLEM_H
14#define CD_AMRMULTIGRIDKRYLOVOPIMPLEM_H
15
16// Chombo includes
17#include <CH_Timer.H>
18
19// Our includes
21#include <CD_ParallelOps.H>
22#include <CD_NamespaceHeader.H>
23
24template <class T>
26{
27 // Unconditionally: clear() null-checks, and guarding on m_precondInit would skip scratch left by a
28 // define() that ran after the last solve.
29 this->clear(m_precondTmp);
30 this->clear(m_precondDcor);
31}
32
33template <class T>
34void
35AMRMultigridKrylovOp<T>::define(AMRMultiGrid<LevelData<T>>* a_mg,
36 const Vector<RefCountedPtr<LevelData<BaseFab<bool>>>>& a_validCells,
37 const Vector<Real>& a_dx,
38 const int a_lbase,
39 const int a_lmax,
40 const int a_numVCycles)
41{
42 CH_TIME("AMRMultigridKrylovOp::define");
43
44 m_mg = a_mg;
45 m_amrOps = a_mg->getAMROperators();
46 m_lbase = a_lbase;
47 m_lmax = a_lmax;
48 m_numVCycles = std::max(1, a_numVCycles);
49 // Release the preconditioner scratch before dropping the initialized flag. create() sizes its target
50 // with resize(n, nullptr), which is a no-op once the vector is already that long, so the null fill
51 // never reaches entries that survive from a previous define -- the loop then overwrites live
52 // pointers and the LevelDatas behind them are lost. Each carries an exchange Copier and its field
53 // data, so a redefine per regrid leaks 2 * (lmax + 1 - lbase) of them. clear() null-checks, so this
54 // is safe on the first define too.
55 this->clear(m_precondTmp);
56 this->clear(m_precondDcor);
57
58 m_precondInit = false;
59 m_volumeCached = false; // Geometry changed; the cached per-level volumes are stale.
60
61 // AMR inner-product weight dx^SpaceDim per level.
62 m_dxScale.resize(a_lmax + 1, 1.0);
63 for (int lvl = 0; lvl <= a_lmax; lvl++) {
64 m_dxScale[lvl] = D_TERM(a_dx[lvl], *a_dx[lvl], *a_dx[lvl]);
65 }
66
67 // Per-level "valid" mask (true except where covered by a finer level), used so the AMR inner product counts each
68 // cell once. This is exactly AmrMesh's valid-cell mask, so we share it (refcounted) instead of rebuilding it.
69 m_validMask = a_validCells;
70}
71
72template <class T>
73void
74AMRMultigridKrylovOp<T>::applyOp(Vector<LevelData<T>*>& a_lhs, const Vector<LevelData<T>*>& a_phi, bool a_homogeneous)
75{
76 m_mg->computeAMROperator(a_lhs, const_cast<Vector<LevelData<T>*>&>(a_phi), m_lmax, m_lbase, a_homogeneous);
77}
78
79template <class T>
80void
81AMRMultigridKrylovOp<T>::residual(Vector<LevelData<T>*>& a_lhs,
82 const Vector<LevelData<T>*>& a_phi,
83 const Vector<LevelData<T>*>& a_rhs,
84 bool a_homogeneous)
85{
86 m_mg->computeAMRResidual(a_lhs,
87 const_cast<Vector<LevelData<T>*>&>(a_phi),
88 a_rhs,
89 m_lmax,
90 m_lbase,
91 a_homogeneous,
92 false);
93}
94
95template <class T>
96void
97AMRMultigridKrylovOp<T>::preCond(Vector<LevelData<T>*>& a_cor, const Vector<LevelData<T>*>& a_residual)
98{
99 CH_TIME("AMRMultigridKrylovOp::preCond");
100
101 if (!m_precondInit) {
102 this->create(m_precondTmp, a_residual);
103 this->create(m_precondDcor, a_residual);
104 m_precondInit = true;
105 }
106
107 this->setToZero(a_cor);
108
109 for (int iter = 0; iter < m_numVCycles; iter++) {
110 // The multigrid cycle needs (residual - L(cor)) as its input and mangles that argument, so we feed it a scratch
111 // copy. On the first cycle cor == 0, so the input is just a_residual -- skip the (expensive) residual evaluation.
112 // Only multi-cycle preconditioning (numVCycles > 1) needs the recompute, which keeps the common single-cycle case
113 // as cheap as one cycle (one extra matvec per cycle would otherwise roughly double the work per Krylov iteration).
114 if (iter == 0) {
115 this->assign(m_precondTmp, a_residual);
117 else {
118 this->residual(m_precondTmp, a_cor, a_residual, true);
119 this->scale(m_precondTmp, -1.0);
120 }
121
122 this->setToZero(m_precondDcor);
123 m_mg->AMRVCycle(m_precondDcor, m_precondTmp, m_lmax, m_lmax, m_lbase);
124
125 this->incr(a_cor, m_precondDcor, 1.0);
127}
128
129template <class T>
130void
131AMRMultigridKrylovOp<T>::create(Vector<LevelData<T>*>& a_lhs, const Vector<LevelData<T>*>& a_rhs)
132{
133 a_lhs.resize(a_rhs.size(), nullptr);
134 for (int lvl = m_lbase; lvl < a_rhs.size(); lvl++) {
135 a_lhs[lvl] = new LevelData<T>();
136 m_amrOps[lvl]->create(*a_lhs[lvl], *a_rhs[lvl]);
137 }
139
140template <class T>
141void
142AMRMultigridKrylovOp<T>::clear(Vector<LevelData<T>*>& a_lhs)
143{
144 for (int lvl = 0; lvl < a_lhs.size(); lvl++) {
145 if (a_lhs[lvl] != nullptr) {
146 delete a_lhs[lvl];
147 a_lhs[lvl] = nullptr;
148 }
149 }
150}
151
152template <class T>
153void
154AMRMultigridKrylovOp<T>::assign(Vector<LevelData<T>*>& a_lhs, const Vector<LevelData<T>*>& a_rhs)
155{
156 for (int lvl = m_lbase; lvl < a_rhs.size(); lvl++) {
157 m_amrOps[lvl]->assign(*a_lhs[lvl], *a_rhs[lvl]);
158 }
159}
160
161template <class T>
162Real
163AMRMultigridKrylovOp<T>::dotProduct(const Vector<LevelData<T>*>& a_1, const Vector<LevelData<T>*>& a_2)
164{
165 CH_TIME("AMRMultigridKrylovOp::dotProduct");
166
167 using OpType = typename AMRMultigridKrylovOpTraits<T>::OpType;
168
169 const int nLevels = m_lmax - m_lbase + 1;
170 const bool needVol = !m_volumeCached;
171
172 // Rank-local partials packed into one buffer: per-level numerators, then (first call only) per-level volumes.
173 // A single MPI_Allreduce of this buffer replaces the 2*nLevels separate reductions of the old per-level path.
174 Vector<Real> partials(needVol ? 2 * nLevels : nLevels, 0.0);
175
176 for (int lvl = m_lbase; lvl <= m_lmax; lvl++) {
177 auto* op = static_cast<OpType*>(m_amrOps[lvl]);
178 Real xy = 0.0;
179 Real vol = 0.0;
180 op->dotProductMaskedLocal(xy, vol, *a_1[lvl], *a_2[lvl], *m_validMask[lvl], needVol);
181
182 partials[lvl - m_lbase] = xy;
183 if (needVol) {
184 partials[nLevels + (lvl - m_lbase)] = vol;
185 }
186 }
187
188 ParallelOps::sum(partials);
189
190 if (needVol) {
191 m_volume.resize(m_lmax + 1, 0.0);
192 for (int lvl = m_lbase; lvl <= m_lmax; lvl++) {
193 m_volume[lvl] = partials[nLevels + (lvl - m_lbase)];
194 }
195 m_volumeCached = true;
196 }
197
198 // Combine into a volume-weighted sum of per-level means: sum_lvl mean_lvl(kappa*X*Y) * dx_lvl^d. This is a
199 // self-consistent inner product (all that the outer GMRES/BiCGStab require), but note it is NOT AMRMultiGrid's
200 // internal single-normalization inner product -- so the Krylov solvers' residuals are measured in a different
201 // (though equivalent) norm than the one AMRMultiGrid reports for the inner V-cycles.
202 Real prod = 0.0;
203 for (int lvl = m_lbase; lvl <= m_lmax; lvl++) {
204 if (m_volume[lvl] > 0.0) {
205 prod += (partials[lvl - m_lbase] / m_volume[lvl]) * m_dxScale[lvl];
207 }
208
209 return prod;
210}
211
212template <class T>
213void
214AMRMultigridKrylovOp<T>::mDotProduct(const Vector<LevelData<T>*>& a_1,
215 const int a_sz,
216 const Vector<LevelData<T>*> a_2[],
217 Real a_mdots[])
219 CH_TIME("AMRMultigridKrylovOp::mDotProduct");
220
221 using OpType = typename AMRMultigridKrylovOpTraits<T>::OpType;
222
223 const int nLevels = m_lmax - m_lbase + 1;
224 const int nNum = a_sz * nLevels;
225 const bool needVol = !m_volumeCached;
226
227 // All a_sz*nLevels rank-local numerators (plus per-level volumes on the first call) in one buffer -> one reduction.
228 Vector<Real> partials(needVol ? nNum + nLevels : nNum, 0.0);
229
230 for (int lvl = m_lbase; lvl <= m_lmax; lvl++) {
231 auto* op = static_cast<OpType*>(m_amrOps[lvl]);
232 Real vol = 0.0;
233
234 for (int k = 0; k < a_sz; k++) {
235 Real xy = 0.0;
236 Real v = 0.0;
237 const bool needVolHere = needVol && (k == 0); // Volume is geometry-only; compute it once per level.
238
239 op->dotProductMaskedLocal(xy, v, *a_1[lvl], *(a_2[k][lvl]), *m_validMask[lvl], needVolHere);
240
241 partials[k * nLevels + (lvl - m_lbase)] = xy;
242 if (needVolHere) {
243 vol = v;
244 }
245 }
246
247 if (needVol) {
248 partials[nNum + (lvl - m_lbase)] = vol;
249 }
250 }
251
252 ParallelOps::sum(partials);
253
254 if (needVol) {
255 m_volume.resize(m_lmax + 1, 0.0);
256 for (int lvl = m_lbase; lvl <= m_lmax; lvl++) {
257 m_volume[lvl] = partials[nNum + (lvl - m_lbase)];
258 }
259 m_volumeCached = true;
260 }
261
262 for (int k = 0; k < a_sz; k++) {
263 Real prod = 0.0;
264 for (int lvl = m_lbase; lvl <= m_lmax; lvl++) {
265 if (m_volume[lvl] > 0.0) {
266 prod += (partials[k * nLevels + (lvl - m_lbase)] / m_volume[lvl]) * m_dxScale[lvl];
267 }
268 }
269 a_mdots[k] = prod;
270 }
271}
272
273template <class T>
274void
275AMRMultigridKrylovOp<T>::incr(Vector<LevelData<T>*>& a_lhs, const Vector<LevelData<T>*>& a_x, Real a_scale)
276{
277 for (int lvl = m_lbase; lvl < a_lhs.size(); lvl++) {
278 m_amrOps[lvl]->incr(*a_lhs[lvl], *a_x[lvl], a_scale);
279 }
280}
281
282template <class T>
283void
284AMRMultigridKrylovOp<T>::axby(Vector<LevelData<T>*>& a_lhs,
285 const Vector<LevelData<T>*>& a_x,
286 const Vector<LevelData<T>*>& a_y,
287 Real a_a,
288 Real a_b)
289{
290 for (int lvl = m_lbase; lvl < a_lhs.size(); lvl++) {
291 m_amrOps[lvl]->axby(*a_lhs[lvl], *a_x[lvl], *a_y[lvl], a_a, a_b);
292 }
293}
294
295template <class T>
296void
297AMRMultigridKrylovOp<T>::scale(Vector<LevelData<T>*>& a_lhs, const Real& a_scale)
298{
299 for (int lvl = m_lbase; lvl < a_lhs.size(); lvl++) {
300 m_amrOps[lvl]->scale(*a_lhs[lvl], a_scale);
301 }
302}
303
304template <class T>
305Real
306AMRMultigridKrylovOp<T>::norm(const Vector<LevelData<T>*>& a_rhs, int a_ord)
307{
308 if (a_ord == 2) {
309 return std::sqrt(this->dotProduct(a_rhs, a_rhs));
310 }
311
312 Real maxNorm = 0.0;
313 for (int lvl = m_lbase; lvl <= m_lmax; lvl++) {
314 maxNorm = std::max(maxNorm, m_amrOps[lvl]->norm(*a_rhs[lvl], a_ord));
315 }
316
317 return maxNorm;
318}
319
320template <class T>
321void
322AMRMultigridKrylovOp<T>::setToZero(Vector<LevelData<T>*>& a_lhs)
323{
324 for (int lvl = m_lbase; lvl < a_lhs.size(); lvl++) {
325 m_amrOps[lvl]->setToZero(*a_lhs[lvl]);
326 }
327}
328
329#include <CD_NamespaceFooter.H>
330
331#endif
Linear-operator adapter that exposes an AMR-multigrid cycle as a Krylov preconditioner.
Agglomeration of basic MPI reductions.
void residual(Vector< LevelData< T > * > &a_lhs, const Vector< LevelData< T > * > &a_phi, const Vector< LevelData< T > * > &a_rhs, bool a_homogeneous=false) override
AMR residual a_lhs = L(a_phi) - a_rhs.
Definition CD_AMRMultigridKrylovOpImplem.H:81
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 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 adapter. Call once per regrid (after the AMRMultiGrid has been defined and init'd).
Definition CD_AMRMultigridKrylovOpImplem.H:35
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 create(Vector< LevelData< T > * > &a_lhs, const Vector< LevelData< T > * > &a_rhs) override
Allocate a_lhs to mirror a_rhs.
Definition CD_AMRMultigridKrylovOpImplem.H:131
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
virtual ~AMRMultigridKrylovOp()
Destructor. Frees the preconditioner scratch storage.
Definition CD_AMRMultigridKrylovOpImplem.H:25
void clear(Vector< LevelData< T > * > &a_lhs) override
Free a holder created by create().
Definition CD_AMRMultigridKrylovOpImplem.H:142
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
Real sum(const Real &a_value) noexcept
Compute the sum across all MPI ranks.
Definition CD_ParallelOpsImplem.H:354
Trait mapping a FAB type to the concrete chombo-discharge Helmholtz operator that owns it.
Definition CD_AMRMultigridKrylovOp.H:40