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 {
112 Level&
l0 = levels_[0];
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());
137 std::vector<Index> Pstart, Pcol;
138 std::vector<double> Pval;
141 std::vector<double> invDiag;
144 mutable std::vector<double> x, b, res, t0, t1;
148 mutable std::vector<Level> levels_;
150 static void allocScratch(Level&
lv) {
151 const std::size_t n =
static_cast<std::size_t
>(
lv.A.n);
154 lv.res.assign(n, 0.0);
155 lv.t0.assign(n, 0.0);
156 lv.t1.assign(n, 0.0);
160 void finalizeSmoother(Level&
lv) {
161 const std::size_t n =
static_cast<std::size_t
>(
lv.A.n);
162 lv.invDiag.assign(n, 0.0);
163 for (std::size_t
i = 0;
i < n; ++
i) {
164 const double d =
lv.A.diag[
i];
165 lv.invDiag[
i] = (std::fabs(d) > 1
e-300) ? 1.0 / d : 0.0;
171 std::vector<double>
v(n, 0.0),
w(n, 0.0);
172 for (std::size_t
i = 0;
i < n; ++
i) {
177 std::uint64_t
z =
static_cast<std::uint64_t
>(
i) + 0x9E3779B97F4A7C15ULL;
178 z = (
z ^ (
z >> 30)) * 0xBF58476D1CE4E5B9ULL;
179 z = (
z ^ (
z >> 27)) * 0x94D049BB133111EBULL;
181 v[
i] = (
z & 1ULL) ? 1.0 : -1.0;
183 double nrm = std::sqrt(dot(
v,
v));
191 for (
int k = 0; k < prm_.eigIters; ++k) {
193 for (std::size_t
i = 0;
i < n; ++
i)
194 w[
i] *=
lv.invDiag[
i];
196 const double wn = std::sqrt(dot(
w,
w));
199 for (std::size_t
i = 0;
i < n; ++
i)
207 bool coarsen(Level&
lv, Level&
next) {
209 const int s = prm_.ndofPerNode;
214 std::vector<std::map<Index, double>>
blk(
static_cast<std::size_t
>(
nNodes));
215 std::vector<double>
ddiag(
static_cast<std::size_t
>(
nNodes), 0.0);
216 for (Index
i = 0;
i < n; ++
i) {
218 ddiag[
static_cast<std::size_t
>(
I)] +=
lv.A.diag[
static_cast<std::size_t
>(
i)] *
219 lv.A.diag[
static_cast<std::size_t
>(
i)];
220 for (Index k =
lv.A.start[
static_cast<std::size_t
>(
i)];
221 k <
lv.A.start[
static_cast<std::size_t
>(
i) + 1]; ++k) {
222 const Index J =
lv.A.nbr[
static_cast<std::size_t
>(k)] /
s;
223 const double c =
lv.A.coef[
static_cast<std::size_t
>(k)];
225 ddiag[
static_cast<std::size_t
>(
I)] +=
c *
c;
227 blk[
static_cast<std::size_t
>(
I)][
J] +=
c *
c;
231 std::vector<std::vector<Index>>
strong(
static_cast<std::size_t
>(
nNodes));
232 const double th2 = prm_.theta * prm_.theta;
241 const auto&
s =
strong[
static_cast<std::size_t
>(
I)];
242 return std::binary_search(
s.begin(),
s.end(),
J);
246 std::vector<Index>
agg(
static_cast<std::size_t
>(
nNodes), -1);
251 if (
agg[
static_cast<std::size_t
>(
I)] != -1)
261 agg[
static_cast<std::size_t
>(
I)] =
nAgg;
268 if (
agg[
static_cast<std::size_t
>(
I)] != -1)
273 const Index a =
agg[
static_cast<std::size_t
>(
J)];
276 const double w =
blk[
static_cast<std::size_t
>(
I)][
J];
283 agg[
static_cast<std::size_t
>(
I)] =
best;
288 if (
agg[
static_cast<std::size_t
>(
I)] != -1)
290 agg[
static_cast<std::size_t
>(
I)] =
nAgg;
302 std::vector<Index>
cnt(
static_cast<std::size_t
>(
nAgg), 0);
304 ++
cnt[
static_cast<std::size_t
>(
agg[
static_cast<std::size_t
>(
I)])];
306 for (Index
a = 0;
a <
nAgg; ++
a)
308 1.0 / std::sqrt(
static_cast<double>(
cnt[
static_cast<std::size_t
>(
a)]));
319 const double sc = prm_.smoothOmega /
lv.lmax;
322 return agg[
static_cast<std::size_t
>(
I)] *
s + d;
325 return invSqrtCnt[
static_cast<std::size_t
>(
agg[
static_cast<std::size_t
>(
i /
s)])];
327 std::vector<Index> Pstart(
static_cast<std::size_t
>(n) + 1, 0), Pcol;
328 std::vector<double> Pval;
329 std::map<Index, double>
row;
330 for (Index
i = 0;
i < n; ++
i) {
333 double dF =
lv.A.diag[
static_cast<std::size_t
>(
i)];
334 for (Index k =
lv.A.start[
static_cast<std::size_t
>(
i)];
335 k <
lv.A.start[
static_cast<std::size_t
>(
i) + 1]; ++k) {
336 const Index J =
lv.A.nbr[
static_cast<std::size_t
>(k)] /
s;
338 dF +=
lv.A.coef[
static_cast<std::size_t
>(k)];
343 const double f = (std::fabs(
dF) > 1
e-300) ? (-
sc /
dF) : 0.0;
345 for (Index k =
lv.A.start[
static_cast<std::size_t
>(
i)];
346 k <
lv.A.start[
static_cast<std::size_t
>(
i) + 1]; ++k) {
347 const Index j =
lv.A.nbr[
static_cast<std::size_t
>(k)];
358 Pstart[
static_cast<std::size_t
>(
i) + 1] =
static_cast<Index>(Pcol.size());
364 std::vector<std::map<Index, double>>
Ac(
static_cast<std::size_t
>(
ncoarse));
365 std::map<Index, double>
apRow;
366 for (Index
i = 0;
i < n; ++
i) {
368 const double di =
lv.A.diag[
static_cast<std::size_t
>(
i)];
369 for (Index k = Pstart[
static_cast<std::size_t
>(
i)];
371 apRow[Pcol[
static_cast<std::size_t
>(k)]] +=
di * Pval[
static_cast<std::size_t
>(k)];
372 for (Index k =
lv.A.start[
static_cast<std::size_t
>(
i)];
373 k <
lv.A.start[
static_cast<std::size_t
>(
i) + 1]; ++k) {
374 const Index j =
lv.A.nbr[
static_cast<std::size_t
>(k)];
375 const double c =
lv.A.coef[
static_cast<std::size_t
>(k)];
376 for (Index
kk = Pstart[
static_cast<std::size_t
>(
j)];
378 apRow[Pcol[
static_cast<std::size_t
>(
kk)]] +=
c * Pval[
static_cast<std::size_t
>(
kk)];
380 for (Index k = Pstart[
static_cast<std::size_t
>(
i)];
382 const Index c1 = Pcol[
static_cast<std::size_t
>(k)];
383 const double p1 = Pval[
static_cast<std::size_t
>(k)];
384 auto&
r1 =
Ac[
static_cast<std::size_t
>(
c1)];
392 next.A.diag.assign(
static_cast<std::size_t
>(
ncoarse), 0.0);
393 next.A.start.assign(
static_cast<std::size_t
>(
ncoarse) + 1, 0);
399 next.A.diag[
static_cast<std::size_t
>(
c)] =
val;
400 }
else if (
val != 0.0) {
405 next.A.start[
static_cast<std::size_t
>(
c) + 1] =
static_cast<Index>(
next.A.nbr.size());
409 lv.Pstart = std::move(Pstart);
410 lv.Pcol = std::move(Pcol);
411 lv.Pval = std::move(Pval);
417 void vcycle(
int L)
const {
418 Level&
lv = levels_[
static_cast<std::size_t
>(
L)];
419 if (
L + 1 ==
static_cast<int>(levels_.size())) {
423 smooth(
lv, prm_.pre);
426 for (std::size_t
i = 0;
i <
lv.res.size(); ++
i)
429 Level&
cl = levels_[
static_cast<std::size_t
>(
L + 1)];
430 std::fill(
cl.b.begin(),
cl.b.end(), 0.0);
431 for (Index
i = 0;
i <
lv.A.n; ++
i)
432 for (Index k =
lv.Pstart[
static_cast<std::size_t
>(
i)];
433 k <
lv.Pstart[
static_cast<std::size_t
>(
i) + 1]; ++k)
434 cl.b[
static_cast<std::size_t
>(
lv.Pcol[
static_cast<std::size_t
>(k)])] +=
435 lv.Pval[
static_cast<std::size_t
>(k)] *
lv.res[
static_cast<std::size_t
>(
i)];
436 std::fill(
cl.x.begin(),
cl.x.end(), 0.0);
439 for (Index
i = 0;
i <
lv.A.n; ++
i) {
441 for (Index k =
lv.Pstart[
static_cast<std::size_t
>(
i)];
442 k <
lv.Pstart[
static_cast<std::size_t
>(
i) + 1]; ++k)
443 s +=
lv.Pval[
static_cast<std::size_t
>(k)] *
444 cl.x[
static_cast<std::size_t
>(
lv.Pcol[
static_cast<std::size_t
>(k)])];
445 lv.x[
static_cast<std::size_t
>(
i)] +=
s;
447 smooth(
lv, prm_.post);
453 void smooth(
const Level&
lv,
int sweeps)
const {
456 if (prm_.chebDegree <= 0) {
465 void jacobiSweep(
const Level&
lv)
const {
470 const double step = prm_.jacobiOmega /
lv.lmax;
472 for (std::size_t
i = 0;
i <
lv.x.size(); ++
i)
473 lv.x[
i] += step *
lv.invDiag[
i] * (
lv.b[
i] -
lv.res[
i]);
480 void chebSweep(
const Level&
lv)
const {
481 const int k = prm_.chebDegree;
482 const double lam = 1.1 *
lv.lmax;
487 for (std::size_t
i = 0;
i <
r.size(); ++
i)
489 std::fill(d.begin(), d.end(), 0.0);
490 for (
int i = 1;
i <= k; ++
i) {
491 const double c1 = (2.0 *
i - 3.0) / (2.0 *
i + 1.0);
492 const double c2 = (8.0 *
i - 4.0) / ((2.0 *
i + 1.0) *
lam);
493 for (std::size_t
j = 0;
j < d.size(); ++
j)
495 for (std::size_t
j = 0;
j <
lv.x.size(); ++
j)
499 for (std::size_t
j = 0;
j <
r.size(); ++
j)
507 void coarseSolve(Level&
lv)
const {
508 if (prm_.coarseSweeps > 0) {
509 smooth(
lv, prm_.coarseSweeps);
518 for (Index
i = 0;
i < n; ++
i)
519 r[
static_cast<std::size_t
>(
i)] =
520 lv.b[
static_cast<std::size_t
>(
i)] -
r[
static_cast<std::size_t
>(
i)];
522 double rr = dot(
r,
r);
523 const double rr0 =
rr;
524 const int maxit =
static_cast<int>(std::min<Index>(n, 200));
527 const double pAp = dot(p,
Ap);
530 const double alpha =
rr /
pAp;
531 for (Index
i = 0;
i < n; ++
i) {
532 x[
static_cast<std::size_t
>(
i)] += alpha * p[
static_cast<std::size_t
>(
i)];
533 r[
static_cast<std::size_t
>(
i)] -= alpha *
Ap[
static_cast<std::size_t
>(
i)];
535 const double rrn = dot(
r,
r);
536 const double beta =
rrn /
rr;
537 for (Index
i = 0;
i < n; ++
i)
538 p[
static_cast<std::size_t
>(
i)] =
539 r[
static_cast<std::size_t
>(
i)] + beta * p[
static_cast<std::size_t
>(
i)];
544 static double dot(
const std::vector<double>&
a,
const std::vector<double>& b) {
546 for (std::size_t
i = 0;
i <
a.size(); ++
i)
563 const std::size_t
nn =
static_cast<std::size_t
>(n);
565 auto dot = [&](
const std::vector<double>&
u,
const std::vector<double>&
v) {
567 for (std::size_t
i = 0;
i <
nn; ++
i)
572 for (std::size_t
i = 0;
i <
nn; ++
i)
580 double rz = dot(
r,
z);
585 const double pAp = dot(p,
Ap);
588 const double alpha =
rz /
pAp;
589 for (std::size_t
i = 0;
i <
nn; ++
i) {
590 x[
i] += alpha * p[
i];
591 r[
i] -= alpha *
Ap[
i];
599 const double rzNew = dot(
r,
z);
600 const double beta =
rzNew /
rz;
601 for (std::size_t
i = 0;
i <
nn; ++
i)
602 p[
i] =
z[
i] + beta * p[
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={})
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)
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).
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.