31#ifndef PECLET_CORE_SOLVER_GRAPH_AMG_HPP
32#define PECLET_CORE_SOLVER_GRAPH_AMG_HPP
56 std::vector<Index>
nbr;
60 void apply(
const std::vector<double>& x, std::vector<double>& y)
const {
62 double s =
diag[
static_cast<std::size_t
>(
i)] * x[
static_cast<std::size_t
>(
i)];
65 s +=
coef[
static_cast<std::size_t
>(
k)] *
66 x[
static_cast<std::size_t
>(
nbr[
static_cast<std::size_t
>(
k)])];
67 y[
static_cast<std::size_t
>(
i)] = s;
94 levels_.emplace_back();
97 finalizeSmoother(levels_[
static_cast<std::size_t
>(
L)]);
98 if (levels_[
static_cast<std::size_t
>(
L)].A.
n <= prm_.
coarsest)
101 if (!coarsen(levels_[
static_cast<std::size_t
>(
L)],
next))
103 levels_.push_back(std::move(
next));
105 finalizeSmoother(levels_.back());
106 for (
auto&
lv : levels_)
111 void apply(
const std::vector<double>& r, std::vector<double>& z)
const {
114 std::fill(
l0.x.begin(),
l0.x.end(), 0.0);
119 int numLevels()
const {
return static_cast<int>(levels_.size()); }
120 Index size(
int L = 0)
const {
return levels_[
static_cast<std::size_t
>(
L)].A.
n; }
125 double nnz0 =
static_cast<double>(levels_[0].A.coef.size() + levels_[0].A.diag.size());
127 for (
auto&
lv : levels_)
128 tot +=
static_cast<double>(
lv.A.coef.size() +
lv.A.diag.size());
148 const std::vector<Level>&
levels()
const {
return levels_; }
153 mutable std::vector<Level> levels_;
155 static void allocScratch(Level&
lv) {
156 const std::size_t n =
static_cast<std::size_t
>(
lv.A.n);
159 lv.res.assign(n, 0.0);
160 lv.t0.assign(n, 0.0);
161 lv.t1.assign(n, 0.0);
165 void finalizeSmoother(Level&
lv) {
166 const std::size_t n =
static_cast<std::size_t
>(
lv.A.n);
167 lv.invDiag.assign(n, 0.0);
168 for (std::size_t
i = 0;
i < n; ++
i) {
169 const double d =
lv.A.diag[
i];
170 lv.invDiag[
i] = (std::fabs(
d) > 1e-300) ? 1.0 /
d : 0.0;
177 std::vector<double> v(n, 0.0), w(n, 0.0);
178 for (std::size_t
i = 0;
i < n; ++
i) {
183 std::uint64_t z =
static_cast<std::uint64_t
>(
i) + 0x9E3779B97F4A7C15ULL;
184 z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
185 z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;
187 v[
i] = (z & 1ULL) ? 1.0 : -1.0;
189 double nrm = std::sqrt(dot(v, v));
199 for (std::size_t
i = 0;
i < n; ++
i)
200 w[
i] *=
lv.invDiag[
i];
202 const double wn = std::sqrt(dot(w, w));
205 for (std::size_t
i = 0;
i < n; ++
i)
208 lv.lmax = (lam > 0.0) ? lam : 1.0;
213 bool coarsen(Level&
lv, Level&
next) {
220 std::vector<std::map<Index, double>>
blk(
static_cast<std::size_t
>(
nNodes));
221 std::vector<double>
ddiag(
static_cast<std::size_t
>(
nNodes), 0.0);
222 for (Index
i = 0;
i < n; ++
i) {
224 ddiag[
static_cast<std::size_t
>(
I)] +=
225 lv.A.diag[
static_cast<std::size_t
>(
i)] *
lv.A.diag[
static_cast<std::size_t
>(
i)];
226 for (Index
k =
lv.A.start[
static_cast<std::size_t
>(
i)];
227 k <
lv.A.start[
static_cast<std::size_t
>(
i) + 1]; ++
k) {
228 const Index J =
lv.A.nbr[
static_cast<std::size_t
>(
k)] / s;
229 const double c =
lv.A.coef[
static_cast<std::size_t
>(
k)];
231 ddiag[
static_cast<std::size_t
>(
I)] += c * c;
233 blk[
static_cast<std::size_t
>(
I)][
J] += c * c;
237 std::vector<std::vector<Index>>
strong(
static_cast<std::size_t
>(
nNodes));
247 const auto& s =
strong[
static_cast<std::size_t
>(
I)];
248 return std::binary_search(s.begin(), s.end(),
J);
252 std::vector<Index>
agg(
static_cast<std::size_t
>(
nNodes), -1);
257 if (
agg[
static_cast<std::size_t
>(
I)] != -1)
267 agg[
static_cast<std::size_t
>(
I)] =
nAgg;
274 if (
agg[
static_cast<std::size_t
>(
I)] != -1)
279 const Index a =
agg[
static_cast<std::size_t
>(
J)];
282 const double w =
blk[
static_cast<std::size_t
>(
I)][
J];
289 agg[
static_cast<std::size_t
>(
I)] =
best;
294 if (
agg[
static_cast<std::size_t
>(
I)] != -1)
296 agg[
static_cast<std::size_t
>(
I)] =
nAgg;
308 std::vector<Index>
cnt(
static_cast<std::size_t
>(
nAgg), 0);
310 ++
cnt[
static_cast<std::size_t
>(
agg[
static_cast<std::size_t
>(
I)])];
312 for (Index a = 0; a <
nAgg; ++a)
314 1.0 / std::sqrt(
static_cast<double>(
cnt[
static_cast<std::size_t
>(a)]));
328 return agg[
static_cast<std::size_t
>(
I)] * s +
d;
331 return invSqrtCnt[
static_cast<std::size_t
>(
agg[
static_cast<std::size_t
>(
i / s)])];
333 std::vector<Index> Pstart(
static_cast<std::size_t
>(n) + 1, 0), Pcol;
334 std::vector<double> Pval;
335 std::map<Index, double>
row;
336 for (Index
i = 0;
i < n; ++
i) {
339 double dF =
lv.A.diag[
static_cast<std::size_t
>(
i)];
340 for (Index
k =
lv.A.start[
static_cast<std::size_t
>(
i)];
341 k <
lv.A.start[
static_cast<std::size_t
>(
i) + 1]; ++
k) {
342 const Index J =
lv.A.nbr[
static_cast<std::size_t
>(
k)] / s;
344 dF +=
lv.A.coef[
static_cast<std::size_t
>(
k)];
349 const double f = (std::fabs(
dF) > 1e-300) ? (-
sc /
dF) : 0.0;
351 for (Index
k =
lv.A.start[
static_cast<std::size_t
>(
i)];
352 k <
lv.A.start[
static_cast<std::size_t
>(
i) + 1]; ++
k) {
353 const Index j =
lv.A.nbr[
static_cast<std::size_t
>(
k)];
359 for (
auto& [c,
val] :
row)
364 Pstart[
static_cast<std::size_t
>(
i) + 1] =
static_cast<Index>(Pcol.size());
370 std::vector<std::map<Index, double>>
Ac(
static_cast<std::size_t
>(
ncoarse));
371 std::map<Index, double>
apRow;
372 for (Index
i = 0;
i < n; ++
i) {
374 const double di =
lv.A.diag[
static_cast<std::size_t
>(
i)];
375 for (Index
k = Pstart[
static_cast<std::size_t
>(
i)];
377 apRow[Pcol[
static_cast<std::size_t
>(
k)]] +=
di * Pval[
static_cast<std::size_t
>(
k)];
378 for (Index
k =
lv.A.start[
static_cast<std::size_t
>(
i)];
379 k <
lv.A.start[
static_cast<std::size_t
>(
i) + 1]; ++
k) {
380 const Index j =
lv.A.nbr[
static_cast<std::size_t
>(
k)];
381 const double c =
lv.A.coef[
static_cast<std::size_t
>(
k)];
382 for (Index
kk = Pstart[
static_cast<std::size_t
>(
j)];
384 apRow[Pcol[
static_cast<std::size_t
>(
kk)]] += c * Pval[
static_cast<std::size_t
>(
kk)];
386 for (Index
k = Pstart[
static_cast<std::size_t
>(
i)];
388 const Index c1 = Pcol[
static_cast<std::size_t
>(
k)];
389 const double p1 = Pval[
static_cast<std::size_t
>(
k)];
390 auto&
r1 =
Ac[
static_cast<std::size_t
>(
c1)];
398 next.A.diag.assign(
static_cast<std::size_t
>(
ncoarse), 0.0);
399 next.A.start.assign(
static_cast<std::size_t
>(
ncoarse) + 1, 0);
402 for (Index c = 0; c <
ncoarse; ++c) {
405 next.A.diag[
static_cast<std::size_t
>(c)] =
val;
406 }
else if (
val != 0.0) {
411 next.A.start[
static_cast<std::size_t
>(c) + 1] =
static_cast<Index>(
next.A.nbr.size());
415 lv.Pstart = std::move(Pstart);
416 lv.Pcol = std::move(Pcol);
417 lv.Pval = std::move(Pval);
423 void vcycle(
int L)
const {
424 Level&
lv = levels_[
static_cast<std::size_t
>(
L)];
425 if (
L + 1 ==
static_cast<int>(levels_.size())) {
429 smooth(
lv, prm_.
pre);
432 for (std::size_t
i = 0;
i <
lv.res.size(); ++
i)
435 Level&
cl = levels_[
static_cast<std::size_t
>(
L + 1)];
436 std::fill(
cl.b.begin(),
cl.b.end(), 0.0);
437 for (Index
i = 0;
i <
lv.A.n; ++
i)
438 for (Index
k =
lv.Pstart[
static_cast<std::size_t
>(
i)];
439 k <
lv.Pstart[
static_cast<std::size_t
>(
i) + 1]; ++
k)
440 cl.b[
static_cast<std::size_t
>(
lv.Pcol[
static_cast<std::size_t
>(
k)])] +=
441 lv.Pval[
static_cast<std::size_t
>(
k)] *
lv.res[
static_cast<std::size_t
>(
i)];
442 std::fill(
cl.x.begin(),
cl.x.end(), 0.0);
445 for (Index
i = 0;
i <
lv.A.n; ++
i) {
447 for (Index
k =
lv.Pstart[
static_cast<std::size_t
>(
i)];
448 k <
lv.Pstart[
static_cast<std::size_t
>(
i) + 1]; ++
k)
449 s +=
lv.Pval[
static_cast<std::size_t
>(
k)] *
450 cl.x[
static_cast<std::size_t
>(
lv.Pcol[
static_cast<std::size_t
>(
k)])];
451 lv.x[
static_cast<std::size_t
>(
i)] += s;
459 void smooth(
const Level&
lv,
int sweeps)
const {
463 for (
int s = 0; s <
sweeps; ++s)
467 for (
int s = 0; s <
sweeps; ++s)
471 void jacobiSweep(
const Level&
lv)
const {
479 for (std::size_t
i = 0;
i <
lv.x.size(); ++
i)
487 void chebSweep(
const Level&
lv)
const {
495 for (std::size_t
i = 0;
i < r.size(); ++
i)
496 r[
i] =
lv.b[
i] - r[
i];
497 std::fill(
d.begin(),
d.end(), 0.0);
498 for (
int i = 1;
i <=
k; ++
i) {
499 const double c1 = (2.0 *
i - 3.0) / (2.0 *
i + 1.0);
500 const double c2 = (8.0 *
i - 4.0) / ((2.0 *
i + 1.0) * lam);
501 for (std::size_t
j = 0;
j <
d.size(); ++
j)
503 for (std::size_t
j = 0;
j <
lv.x.size(); ++
j)
507 for (std::size_t
j = 0;
j < r.size(); ++
j)
515 void coarseSolve(Level&
lv)
const {
526 for (Index
i = 0;
i < n; ++
i)
527 r[
static_cast<std::size_t
>(
i)] =
528 lv.b[
static_cast<std::size_t
>(
i)] - r[
static_cast<std::size_t
>(
i)];
530 double rr = dot(r, r);
531 const double rr0 =
rr;
532 const int maxit =
static_cast<int>(std::min<Index>(n, 200));
535 const double pAp = dot(p,
Ap);
539 for (Index
i = 0;
i < n; ++
i) {
540 x[
static_cast<std::size_t
>(
i)] +=
alpha * p[
static_cast<std::size_t
>(
i)];
541 r[
static_cast<std::size_t
>(
i)] -=
alpha *
Ap[
static_cast<std::size_t
>(
i)];
543 const double rrn = dot(r, r);
545 for (Index
i = 0;
i < n; ++
i)
546 p[
static_cast<std::size_t
>(
i)] =
547 r[
static_cast<std::size_t
>(
i)] +
beta * p[
static_cast<std::size_t
>(
i)];
552 static double dot(
const std::vector<double>& a,
const std::vector<double>& b) {
554 for (std::size_t
i = 0;
i < a.size(); ++
i)
571 const std::size_t
nn =
static_cast<std::size_t
>(n);
572 std::vector<double> r(
nn), z(
nn), p(
nn),
Ap(
nn);
573 auto dot = [&](
const std::vector<double>& u,
const std::vector<double>& v) {
575 for (std::size_t
i = 0;
i <
nn; ++
i)
580 for (std::size_t
i = 0;
i <
nn; ++
i)
588 double rz =
dot(r, z);
597 for (std::size_t
i = 0;
i <
nn; ++
i) {
609 for (std::size_t
i = 0;
i <
nn; ++
i)
Smoothed-aggregation AMG hierarchy usable as z = M⁻¹ r (one symmetric V-cycle from a zero initial gue...
void apply(const std::vector< double > &r, std::vector< double > &z) const
z = M⁻¹ r: one V-cycle (correction scheme) from a zero initial guess.
void build(const HostCsrOp &A, const AmgParams &prm={})
const AmgParams & params() const
const std::vector< Level > & levels() const
Index size(int L=0) const
double operatorComplexity() const
Total nonzeros / finest nonzeros — the grid+operator complexity (should stay ~<2 for a healthy hierar...
CgResult amgPcg(const HostCsrOp &A, std::vector< double > &x, const std::vector< double > &b, const GraphAMG &M, int maxIters=500, double tol=1e-8)
PECLET_HD Real dot(Vec3< Real > a, Vec3< Real > b)
Kokkos::View< T *, MemSpace > View
1D device array.
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
int coarseSweeps
coarsest level: 0 ⇒ a near-exact CG solve; >0 ⇒ this many smoother sweeps instead (with maxLevels=1 t...
double jacobiOmega
damped-Jacobi smoother relaxation (used when chebDegree == 0)
int chebDegree
Chebyshev smoother polynomial degree; 0 ⇒ damped-Jacobi smoother.
double theta
strength-of-connection threshold (fraction of √(A_ii·A_jj))
int ndofPerNode
block size for nodal aggregation (Voronoi positions ⇒ 3, +weight ⇒ 4)
Index coarsest
stop coarsening once a level has ≤ this many DOFs
int post
smoother sweeps per level (pre == post keeps the V-cycle symmetric)
int maxLevels
hierarchy depth cap
double smoothOmega
prolongator-smoothing damping (× 1/λ_max)
int eigIters
power-iteration steps for the per-level λ_max(D⁻¹A) estimate
PCG on A x = b preconditioned by an already-built GraphAMG (one V-cycle per iteration).
std::vector< double > invDiag
std::vector< double > res
std::vector< Index > Pstart
std::vector< double > Pval
std::vector< Index > Pcol
A general assembled sparse operator in CSR form: the diagonal is stored separately and the CSR (start...
std::vector< double > coef
size nnz (off-diagonal values)
std::vector< Index > start
size n+1, off-diagonal row offsets
std::vector< Index > nbr
size nnz (off-diagonal column indices)
std::vector< double > diag
size n
void apply(const std::vector< double > &x, std::vector< double > &y) const
y = A x.