Block-Structured AMR Software Framework
Loading...
Searching...
No Matches
AMReX_Smoother_MV.H
Go to the documentation of this file.
1#ifndef AMREX_SMOOTHER_MV_H_
2#define AMREX_SMOOTHER_MV_H_
3
4#include <AMReX_Algebra.H>
5#include <AMReX_Array.H>
6#include <AMReX_BLProfiler.H>
7#include <AMReX_OpenMP.H>
8#include <cmath>
9#include <memory>
10#include <utility>
11
12namespace amrex {
13
30template <typename T>
32{
33public:
40 explicit JacobiSmoother (SpMatrix<T> const* a_A, bool a_l1 = false)
41 : m_A(a_A), m_l1(a_l1), m_weight(a_l1 ? T(1) : T(2./3.)) {}
42
49 int setNumIters (int a_niters) { return std::exchange(m_niters, a_niters); }
50
52 void setWeight (T w) { m_weight = w; }
53
56 void setL1 (bool b) { m_l1 = b; m_weight = b ? T(1) : T(2./3.); m_scaling.reset(); }
57
65 void operator() (AlgVector<T>& xvec, AlgVector<T> const& bvec,
66 bool with_initial_guess = false)
67 {
68 BL_PROFILE("JacobiSmoother");
69 AMREX_ASSERT(xvec.partition() == m_A->partition());
70 auto const& diag = scaling();
71 if ( ! with_initial_guess) {
72 xvec.setValAsync(0);
73 }
74 T const w = m_weight;
75 for (int iter = 0; iter < m_niters; ++iter) {
76 if ((iter == 0) && ! with_initial_guess) {
77 // A x is zero, so this sweep is just x = w b / d.
78 ForEach(xvec, bvec, diag,
79 [=] AMREX_GPU_DEVICE (T& x, T const& b, T const& d)
80 {
81 if (d != T(0)) {
82 x += w * b/d;
83 }
84 });
85 } else {
86 auto& Axvec = workspace(xvec.partition()); // allocated on first need
87 SpMV(Axvec, *m_A, xvec);
88 ForEach(xvec, Axvec, bvec, diag,
89 [=] AMREX_GPU_DEVICE (T& x, T const& ax, T const& b, T const& d)
90 {
91 if (d != T(0)) {
92 x += w * (b-ax)/d;
93 }
94 });
95 }
96 }
98 }
99
100private:
101 SpMatrix<T> const* m_A;
102 bool m_l1 = false;
103 T m_weight;
104 int m_niters = 4;
105 // l1 row norms, built on first use. Shared so that the smoother stays
106 // copyable (it is used as a std::function preconditioner).
107 std::shared_ptr<AlgVector<T>> m_scaling;
108 std::shared_ptr<AlgVector<T>> m_work;
109
110 AlgVector<T>& workspace (AlgPartition const& partition)
111 {
112 if (!m_work || m_work->partition() != partition) {
113 m_work = std::make_shared<AlgVector<T>>(partition);
114 }
115 return *m_work;
116 }
117
118public: // NOLINT Private, but public for CUDA
121 {
122 if (!m_l1) { return m_A->diagonalVector(); }
123 if (!m_scaling) {
124 // The matrix might not have been split yet (e.g., no SpMV so far).
125 const_cast<SpMatrix<T>*>(m_A)->setColumnPartition(m_A->partition());
126 m_scaling = std::make_shared<AlgVector<T>>(m_A->partition());
127 auto* AMREX_RESTRICT p = m_scaling->data();
128 auto const& a = m_A->const_parcsr();
129 auto const& d = m_A->diagonalVector();
130 auto const* AMREX_RESTRICT pd = d.data();
131 ParallelForOMP(m_scaling->numLocalRows(), [=] AMREX_GPU_DEVICE (Long i) noexcept
132 {
133 T s = T(0);
134 for (auto idx = a.csr0.row_offset[i]; idx < a.csr0.row_offset[i+1]; ++idx) {
135 s += std::abs(a.csr0.mat[idx]);
136 }
137 if (detail::has_remote_row(a, i)) {
138 auto ii = a.row_map[i];
139 for (auto idx = a.csr1.row_offset[ii]; idx < a.csr1.row_offset[ii+1]; ++idx) {
140 s += std::abs(a.csr1.mat[idx]);
141 }
142 }
143 p[i] = std::copysign(s, pd[i]);
144 });
146 }
147 return *m_scaling;
148 }
149};
150
161template <typename T>
163{
164public:
169 explicit ChebyshevSmoother (SpMatrix<T> const* a_A, bool a_l1 = false)
170 : m_jacobi(a_A, a_l1), m_A(a_A), m_l1(a_l1) {}
171
173 void setDegree (int d) { m_degree = d; }
176 void setEigRatio (T r) {
177 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(r > T(1), "ChebyshevSmoother: the ratio must exceed 1");
178 m_ratio = r;
179 }
181 int setNumIters (int a_niters) { return std::exchange(m_niters, a_niters); }
183 void setLambdaMax (T l) { m_lambda_max = l; }
187 {
188 if (m_lambda_max <= T(0)) { bound(); }
189 return m_lambda_max;
190 }
191
199 void operator() (AlgVector<T>& xvec, AlgVector<T> const& bvec,
200 bool with_initial_guess = false)
201 {
202 BL_PROFILE("ChebyshevSmoother");
203 AMREX_ASSERT(xvec.partition() == m_A->partition());
204 T const lmax = lambdaMax();
205 T const lmin = lmax / m_ratio;
206 T const theta = (lmax + lmin) / T(2);
207 T const delta = (lmax - lmin) / T(2);
208 T const sigma = theta / delta;
209
210 auto const& diag = m_jacobi.scaling();
211 if (!m_work || (*m_work)[0].partition() != xvec.partition()) {
212 m_work = std::make_shared<Array<AlgVector<T>,3>>();
213 for (auto& v : *m_work) { v.define(xvec.partition()); }
214 }
215 auto& r = (*m_work)[0];
216 auto& d = (*m_work)[1];
217 auto& ad = (*m_work)[2];
218 if ( ! with_initial_guess) { xvec.setValAsync(0); }
219
220 T const c0 = T(1) / theta;
221 for (int it = 0; it < m_niters; ++it) {
222 // r = b - A x; d = D^{-1} r / theta; x += d
223 if (it == 0 && ! with_initial_guess) {
224 ForEach(xvec, d, r, bvec, diag,
225 [=] AMREX_GPU_DEVICE (T& xi, T& di, T& ri, T const& bi,
226 T const& dd)
227 {
228 ri = bi;
229 di = (dd != T(0)) ? c0 * ri / dd : T(0);
230 xi += di;
231 });
232 } else {
233 SpMV(r, *m_A, xvec);
234 ForEach(xvec, d, r, bvec, diag,
235 [=] AMREX_GPU_DEVICE (T& xi, T& di, T& ri, T const& bi,
236 T const& dd)
237 {
238 ri = bi - ri;
239 di = (dd != T(0)) ? c0 * ri / dd : T(0);
240 xi += di;
241 });
242 }
243 T rho_old = T(1) / sigma;
244 for (int k = 1; k < m_degree; ++k) {
245 // r -= A d; rho = 1/(2 sigma - rho_old);
246 // d = rho rho_old d + (2 rho / delta) D^{-1} r; x += d
247 SpMV(ad, *m_A, d);
248 T const rho = T(1) / (T(2)*sigma - rho_old);
249 T const c1 = rho * rho_old;
250 T const c2 = T(2) * rho / delta;
251 ForEach(xvec, d, r, ad, diag,
252 [=] AMREX_GPU_DEVICE (T& xi, T& di, T& ri, T const& adi,
253 T const& dd)
254 {
255 ri -= adi;
256 di = c1 * di + ((dd != T(0)) ? c2 * ri / dd : T(0));
257 xi += di;
258 });
259 rho_old = rho;
260 }
261 }
263 }
264
265private:
266 JacobiSmoother<T> m_jacobi; // supplies the scaling vector
267 SpMatrix<T> const* m_A;
268 bool m_l1 = false;
269 int m_degree = 2;
270 T m_ratio = T(20);
271 int m_niters = 1;
272 T m_lambda_max = T(-1);
273 std::shared_ptr<Array<AlgVector<T>,3>> m_work; // r, d, A d
274
275public: // NOLINT Private, but public for CUDA
277 void bound ()
278 {
279 if (m_l1) { m_lambda_max = T(1); return; }
280 // The matrix might not have been split yet (e.g., no SpMV so far).
281 const_cast<SpMatrix<T>*>(m_A)->setColumnPartition(m_A->partition());
282 auto const& a = m_A->const_parcsr();
283 auto const* AMREX_RESTRICT pd = m_A->diagonalVector().data();
284 T lmax = Reduce::Max<T>(m_A->numLocalRows(), [=] AMREX_GPU_DEVICE (Long i) -> T
285 {
286 if (pd[i] == T(0)) { return T(0); }
287 T s = T(0); // sum_j |a_ij|
288 for (auto idx = a.csr0.row_offset[i]; idx < a.csr0.row_offset[i+1]; ++idx) {
289 s += std::abs(a.csr0.mat[idx]);
290 }
291 if (detail::has_remote_row(a, i)) {
292 auto ii = a.row_map[i];
293 for (auto idx = a.csr1.row_offset[ii]; idx < a.csr1.row_offset[ii+1]; ++idx) {
294 s += std::abs(a.csr1.mat[idx]);
295 }
296 }
297 return s / std::abs(pd[i]);
298 });
300 m_lambda_max = (lmax > T(0)) ? lmax : T(1);
301 }
302};
303
316template <typename T>
318{
319public:
325 explicit L1GaussSeidelSmoother (SpMatrix<T> const* a_A) : m_A(a_A) {}
326
328 int setNumIters (int a_niters) { return std::exchange(m_niters, a_niters); }
329
338 void operator() (AlgVector<T>& xvec, AlgVector<T> const& bvec,
339 bool with_initial_guess = false, bool backward = false)
340 {
341#ifdef AMREX_USE_GPU
342 amrex::ignore_unused(xvec, bvec, with_initial_guess, backward);
343 amrex::Abort("L1GaussSeidelSmoother is only available in CPU builds");
344#else
345 BL_PROFILE("L1GaussSeidelSmoother");
346 AMREX_ASSERT(xvec.partition() == m_A->partition());
347 auto& A = *const_cast<SpMatrix<T>*>(m_A);
348 // The matrix might not have been split yet (e.g., no SpMV so far).
349 A.setColumnPartition(A.partition());
350 auto const& a = A.const_parcsr();
351 Long const nrows = xvec.numLocalRows();
352 T const* AMREX_RESTRICT dl1 = l1_diagonal().data();
353 int const nblocks = m_nblocks;
354 bool const has_remote = this->has_remote();
355 T const* AMREX_RESTRICT b = bvec.data();
356 T* AMREX_RESTRICT x = xvec.data();
357
358 // The previous iterate is needed for couplings between row blocks,
359 // and the remote part of A x for couplings to other processes.
360 if (!m_work) {
361 m_work = std::make_shared<Array<AlgVector<T>,2>>();
362 if (nblocks > 1) { (*m_work)[0].define(xvec.partition()); }
363 if (has_remote) { (*m_work)[1].define(xvec.partition()); }
364 }
365 auto& xold = (*m_work)[0];
366 auto& yrem = (*m_work)[1];
367 T const* AMREX_RESTRICT px_old = (nblocks > 1) ? xold.data() : nullptr;
368 T const* AMREX_RESTRICT py_rem = has_remote ? yrem.data() : nullptr;
369
370 if ( ! with_initial_guess) { xvec.setVal(0); }
371
372 // x_i += (b_i - (A x)_i) / d_i, row by row, with d_i = a_ii + l1 term.
373 auto sweep = [&] (auto multi_block, auto remote)
374 {
375#ifdef AMREX_USE_OMP
376#pragma omp parallel for
377#endif
378 for (int iblock = 0; iblock < nblocks; ++iblock) {
379 auto const [rlo, rhi] = row_block(nrows, iblock, nblocks);
380 auto const& c0 = a.csr0;
381 for (Long n = 0; n < rhi-rlo; ++n) {
382 Long const i = backward ? (rhi-1-n) : (rlo+n);
383 T r = b[i];
384 if constexpr (remote) { r -= py_rem[i]; }
385 for (Long idx = c0.row_offset[i]; idx < c0.row_offset[i+1]; ++idx) {
386 Long const j = c0.col_index[idx];
387 if constexpr (multi_block) {
388 // Rows of this block use the current iterate, others the old one.
389 r -= c0.mat[idx] * ((j >= rlo && j < rhi) ? x[j] : px_old[j]);
390 } else {
391 r -= c0.mat[idx] * x[j];
392 }
393 }
394 if (dl1[i] != T(0)) { x[i] += r / dl1[i]; }
395 }
396 }
397 };
398
399 for (int iter = 0; iter < m_niters; ++iter) {
400 bool const zero_guess = (iter == 0 && ! with_initial_guess);
401 if (nblocks > 1) {
402 if (zero_guess) { xold.setVal(0); } else { xold.copy(xvec); }
403 }
404 if (has_remote) {
405 // Remote contribution with the previous iterate.
406 yrem.setVal(0);
407 if (!zero_guess) {
408 A.startComm_mv(xvec);
409 A.finishComm_mv(yrem);
410 }
411 }
412 if (nblocks > 1) {
413 if (has_remote) { sweep(std::true_type{}, std::true_type{}); }
414 else { sweep(std::true_type{}, std::false_type{}); }
415 } else {
416 if (has_remote) { sweep(std::false_type{}, std::true_type{}); }
417 else { sweep(std::false_type{}, std::false_type{}); }
418 }
419 }
420#endif
421 }
422
423private:
424 SpMatrix<T> const* m_A;
425 int m_niters = 2;
426 int m_nblocks = -1;
427 int m_has_remote = -1; // any rank couples to another rank; -1: not yet known
428 std::shared_ptr<AlgVector<T>> m_dl1; // a_ii + l1 term for the current row blocks
429 std::shared_ptr<Array<AlgVector<T>,2>> m_work; // previous iterate, remote part
430
433 bool has_remote ()
434 {
435 if (m_has_remote < 0) {
436 int r = (m_A->const_parcsr().csr1.nnz > 0) ? 1 : 0;
438 m_has_remote = r;
439 }
440 return m_has_remote == 1;
441 }
442
444 static std::pair<Long,Long> row_block (Long nrows, int iblock, int nblocks)
445 {
446 Long const lo = (nrows * iblock) / nblocks;
447 Long const hi = (nrows * (iblock+1)) / nblocks;
448 return {lo, hi};
449 }
450
451 template <typename C>
452 static T diag_of (C const& c0, Long i)
453 {
454 for (Long idx = c0.row_offset[i]; idx < c0.row_offset[i+1]; ++idx) {
455 if (c0.col_index[idx] == i) { return c0.mat[idx]; }
456 }
457 return T(0);
458 }
459
460#ifndef AMREX_USE_GPU
461 AlgVector<T> const& l1_diagonal ()
462 {
463 int const nb = OpenMP::get_max_threads();
464 if (!m_dl1 || nb != m_nblocks) {
465 m_nblocks = nb;
466 m_work.reset(); // its vectors depend on the number of blocks
467 m_dl1 = std::make_shared<AlgVector<T>>(m_A->partition());
468 T* AMREX_RESTRICT p = m_dl1->data();
469 auto const& a = m_A->const_parcsr();
470 Long const nrows = m_dl1->numLocalRows();
471#ifdef AMREX_USE_OMP
472#pragma omp parallel for
473#endif
474 for (int iblock = 0; iblock < nb; ++iblock) {
475 auto const [rlo, rhi] = row_block(nrows, iblock, nb);
476 auto const& c0 = a.csr0;
477 for (Long i = rlo; i < rhi; ++i) {
478 T const aii = diag_of(c0, i); // same entry the sweep uses
479 T l1 = T(0);
480 for (Long idx = c0.row_offset[i]; idx < c0.row_offset[i+1]; ++idx) {
481 Long const j = c0.col_index[idx];
482 if (j != i && (j < rlo || j >= rhi)) {
483 l1 += std::abs(c0.mat[idx]);
484 }
485 }
486 if (detail::has_remote_row(a, i)) {
487 Long const ii = a.row_map[i];
488 for (Long idx = a.csr1.row_offset[ii]; idx < a.csr1.row_offset[ii+1]; ++idx) {
489 l1 += std::abs(a.csr1.mat[idx]);
490 }
491 }
492 p[i] = aii + std::copysign(l1, aii);
493 }
494 }
495 }
496 return *m_dl1;
497 }
498#endif
499};
500
501}
502
503#endif
Fixed-size array types for use on GPU and CPU.
#define BL_PROFILE(a)
Definition AMReX_BLProfiler.H:562
#define AMREX_ALWAYS_ASSERT_WITH_MESSAGE(EX, MSG)
Definition AMReX_BLassert.H:49
#define AMREX_ASSERT(EX)
Definition AMReX_BLassert.H:38
#define AMREX_RESTRICT
Definition AMReX_Extension.H:37
#define AMREX_GPU_DEVICE
Definition AMReX_GpuQualifiers.H:18
S sigma
Definition AMReX_MLEBNodeFDLaplacian.cpp:1876
GpuArray< MultiArray4< Real const >, 3 > s
Definition AMReX_MLEBNodeFDLaplacian.cpp:214
Definition AMReX_AlgPartition.H:26
Distributed dense vector that mirrors the layout of an AlgPartition.
Definition AMReX_AlgVector.H:29
void setVal(T val)
Definition AMReX_AlgVector.H:273
Long numLocalRows() const
Number of entries stored on this rank.
Definition AMReX_AlgVector.H:74
AlgPartition const & partition() const
Partition describing the global layout of the vector.
Definition AMReX_AlgVector.H:71
void setValAsync(T val)
Definition AMReX_AlgVector.H:280
T const * data() const
Definition AMReX_AlgVector.H:85
Chebyshev polynomial smoother for AlgVector/SpMatrix systems.
Definition AMReX_Smoother_MV.H:163
void setLambdaMax(T l)
Set the largest eigenvalue of the scaled operator instead of the bound.
Definition AMReX_Smoother_MV.H:183
void operator()(AlgVector< T > &xvec, AlgVector< T > const &bvec, bool with_initial_guess=false)
Apply the smoother to solve approximately for xvec.
Definition AMReX_Smoother_MV.H:199
void bound()
Gershgorin bound of the eigenvalues of D^{-1} A.
Definition AMReX_Smoother_MV.H:277
void setDegree(int d)
Polynomial degree (number of matrix-vector products per application, 2 by default).
Definition AMReX_Smoother_MV.H:173
ChebyshevSmoother(SpMatrix< T > const *a_A, bool a_l1=false)
Definition AMReX_Smoother_MV.H:169
T lambdaMax()
Definition AMReX_Smoother_MV.H:186
int setNumIters(int a_niters)
Number of polynomial applications per call (1 by default).
Definition AMReX_Smoother_MV.H:181
void setEigRatio(T r)
Definition AMReX_Smoother_MV.H:176
Jacobi smoother for AlgVector/SpMatrix linear systems.
Definition AMReX_Smoother_MV.H:32
JacobiSmoother(SpMatrix< T > const *a_A, bool a_l1=false)
Construct a smoother that operates on a_A.
Definition AMReX_Smoother_MV.H:40
int setNumIters(int a_niters)
Update how many Jacobi sweeps to perform per apply.
Definition AMReX_Smoother_MV.H:49
void setL1(bool b)
Definition AMReX_Smoother_MV.H:56
void operator()(AlgVector< T > &xvec, AlgVector< T > const &bvec, bool with_initial_guess=false)
Apply the smoother to solve approximately for xvec.
Definition AMReX_Smoother_MV.H:65
AlgVector< T > const & scaling()
Diagonal or l1 scaling vector. The first call is collective.
Definition AMReX_Smoother_MV.H:120
void setWeight(T w)
Set the relaxation weight. Call this after setL1(), which resets it.
Definition AMReX_Smoother_MV.H:52
l1 hybrid Gauss-Seidel smoother for CPUs.
Definition AMReX_Smoother_MV.H:318
L1GaussSeidelSmoother(SpMatrix< T > const *a_A)
Construct a smoother that operates on a_A.
Definition AMReX_Smoother_MV.H:325
int setNumIters(int a_niters)
Number of sweeps per application (2 by default).
Definition AMReX_Smoother_MV.H:328
void operator()(AlgVector< T > &xvec, AlgVector< T > const &bvec, bool with_initial_guess=false, bool backward=false)
Apply the smoother to solve approximately for xvec.
Definition AMReX_Smoother_MV.H:338
Distributed CSR matrix that manages storage and GPU-friendly partitions.
Definition AMReX_SpMatrix.H:65
void setColumnPartition(AlgPartition const &col_partition)
Set the column partition and split the matrix into local and remote blocks with 32-bit column indices...
Definition AMReX_SpMatrix.H:1019
T * data()
Don't use this beyond initial setup.
Definition AMReX_SpMatrix.H:206
ParCsr< T const > const_parcsr() const
Const-qualified alias of parcsr() for convenience.
Definition AMReX_SpMatrix.H:1226
Long numLocalRows() const
Number of rows owned by this rank.
Definition AMReX_SpMatrix.H:194
AlgPartition const & partition() const
Row partition describing how matrix rows are distributed across ranks.
Definition AMReX_SpMatrix.H:182
AlgVector< T, AllocT > const & diagonalVector() const
Return diagonal elements in a square matrix.
Definition AMReX_SpMatrix.H:1145
amrex_long Long
Definition AMReX_INT.H:30
void ParallelForOMP(T n, L const &f) noexcept
Performance-portable kernel launch function with optional OpenMP threading.
Definition AMReX_GpuLaunch.H:328
void Max(KeyValuePair< K, V > &vi, MPI_Comm comm)
Definition AMReX_ParallelReduce.H:133
void streamSynchronize() noexcept
Definition AMReX_GpuDevice.H:310
constexpr int get_max_threads()
Definition AMReX_OpenMP.H:36
MPI_Comm CommunicatorSub() noexcept
sub-communicator for current frame
Definition AMReX_ParallelContext.H:70
Definition AMReX_Amr.cpp:50
__host__ __device__ void ignore_unused(const Ts &...)
No-op helper that marks variables as intentionally unused.
Definition AMReX.H:273
constexpr void ForEach(TypeList< Ts... >, F &&f)
For each type t in TypeList, call f(t)
Definition AMReX_TypeList.H:83
void Abort(const std::string &msg)
Print a fatal-error message to stderr and abort execution.
Definition AMReX.cpp:244
void SpMV(Long nrows, Long ncols, T *__restrict__ py, CsrView< T const, I > const &A, T const *__restrict__ px)
Perform y = A * x using CSR data (GPU/CPU aware).
Definition AMReX_SpMV.H:30