Arcane  4.2.3.0
Developer documentation
Loading...
Searching...
No Matches
AMG.h
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* AMG.h (C) 2000-2026 */
9/* */
10/* An AMG preconditioner. . */
11/*---------------------------------------------------------------------------*/
12#ifndef ARCCORE_ALINA_AMG_H
13#define ARCCORE_ALINA_AMG_H
14/*---------------------------------------------------------------------------*/
15/*---------------------------------------------------------------------------*/
16/*
17 * This file is based on the work on AMGCL library (version march 2026)
18 * which can be found at https://github.com/ddemidov/amgcl.
19 *
20 * Copyright (c) 2012-2022 Denis Demidov <dennis.demidov@gmail.com>
21 * SPDX-License-Identifier: MIT
22 */
23/*---------------------------------------------------------------------------*/
24/*---------------------------------------------------------------------------*/
25
26// Temporarily add this pragma because there is a lot of conversion warnings.
27#pragma GCC diagnostic ignored "-Wconversion"
28
29/*---------------------------------------------------------------------------*/
30/*---------------------------------------------------------------------------*/
31
32#include "arccore/alina/BuiltinBackend.h"
33#include "arccore/alina/SolverUtils.h"
34#include "arccore/alina/AlinaUtils.h"
35
36#include <iostream>
37#include <iomanip>
38#include <list>
39#include <memory>
40
41/*---------------------------------------------------------------------------*/
42/*---------------------------------------------------------------------------*/
43
44namespace Arcane::Alina
45{
46
47/*---------------------------------------------------------------------------*/
48/*---------------------------------------------------------------------------*/
67template <class Backend,
68 template <class> class Coarsening,
69 template <class> class Relax>
70class AMG
71{
72 public:
73
74 typedef Backend backend_type;
75
76 typedef typename Backend::value_type value_type;
77 typedef typename Backend::col_type col_type;
78 typedef typename Backend::ptr_type ptr_type;
79 typedef typename Backend::matrix matrix;
80 typedef typename Backend::vector vector;
81
82 typedef Coarsening<Backend> coarsening_type;
83 typedef Relax<Backend> relax_type;
84
85 typedef typename BuiltinBackend<value_type, col_type, ptr_type>::matrix build_matrix;
86
87 typedef typename math::scalar_of<value_type>::type scalar_type;
88
91
98 struct params
99 {
100 typedef coarsening_type::params coarsening_params;
101 typedef relax_type::params relax_params;
102
103 coarsening_params coarsening;
104 relax_params relax;
105
113 Int32 coarse_enough = Backend::direct_solver::coarse_enough();
114
121 bool direct_coarse = true;
122
130 Int32 max_levels = std::numeric_limits<Int32>::max();
131
134
137
140
143
145 bool allow_rebuild = std::is_same<matrix, build_matrix>::value;
146
147 params() = default;
148
149 params(const PropertyTree& p)
150 : ARCCORE_ALINA_PARAMS_IMPORT_CHILD(p, coarsening)
151 , ARCCORE_ALINA_PARAMS_IMPORT_CHILD(p, relax)
152 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, coarse_enough)
153 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, direct_coarse)
154 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, max_levels)
155 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, npre)
156 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, npost)
157 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, ncycle)
158 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, pre_cycles)
159 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, allow_rebuild)
160 {
161 p.check_params( { "coarsening", "relax", "coarse_enough", "direct_coarse", "max_levels", "npre", "npost", "ncycle", "pre_cycles", "allow_rebuild" });
162
163 precondition(max_levels > 0, "max_levels should be positive");
164 }
165
166 void get(PropertyTree& p, const std::string& path = "") const
167 {
168 ARCCORE_ALINA_PARAMS_EXPORT_CHILD(p, path, coarsening);
169 ARCCORE_ALINA_PARAMS_EXPORT_CHILD(p, path, relax);
170 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, coarse_enough);
171 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, direct_coarse);
172 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, max_levels);
173 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, npre);
174 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, npost);
175 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, ncycle);
176 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, pre_cycles);
177 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, allow_rebuild);
178 }
179 };
180
189 template <class Matrix>
190 explicit AMG([[maybe_unused]] const AlinaContext& context,
191 const Matrix& M, const params& p = params(),
192 const backend_params& bprm = backend_params())
193 : prm(p)
194 {
195 auto A = std::make_shared<build_matrix>(M);
196 sort_rows(*A);
197
198 _initialize(A, bprm);
199 }
200
213 explicit AMG([[maybe_unused]] const AlinaContext& context,
214 std::shared_ptr<build_matrix> A,
215 const params& p = params(),
216 const backend_params& bprm = backend_params())
217 : prm(p)
218 {
219 _initialize(A, bprm);
220 }
221
228 template <class Matrix>
229 void rebuild(const Matrix& M,
230 const backend_params& bprm = backend_params())
231 {
232 auto A = std::make_shared<build_matrix>(M);
233 sort_rows(*A);
234 rebuild(A, bprm);
235 }
236
243 void rebuild(std::shared_ptr<build_matrix> A,
244 const backend_params& bprm = backend_params())
245 {
246 precondition(prm.allow_rebuild, "allow_rebuild is not set!");
247 precondition(backend::nbRow(*A) == backend::nbRow(system_matrix()) &&
248 backend::nbColumn(*A) == backend::nbRow(*A),
249 "Matrix dimensions differ from the original ones!");
250
251 ARCCORE_ALINA_TIC("rebuild");
252 coarsening_type C(prm.coarsening);
253 for (auto& level : levels) {
254 A = level.rebuild(A, C, prm, bprm);
255 }
256 ARCCORE_ALINA_TOC("rebuild");
257 }
258
265 template <class Vec1, class Vec2>
266 void cycle(const Vec1& rhs, Vec2&& x) const
267 {
268 cycle(levels.begin(), rhs, x);
269 }
270
279 template <class Vec1, class Vec2>
280 void apply(const Vec1& rhs, Vec2&& x) const
281 {
282 if (prm.pre_cycles) {
283 backend::clear(x);
284 for (unsigned i = 0; i < prm.pre_cycles; ++i)
285 cycle(rhs, x);
286 }
287 else {
288 backend::copy(rhs, x);
289 }
290 }
291
293 std::shared_ptr<matrix> system_matrix_ptr() const
294 {
295 return levels.front().A;
296 }
297
298 const matrix& system_matrix() const
299 {
300 return *system_matrix_ptr();
301 }
302
303 size_t bytes() const
304 {
305 size_t b = 0;
306 for (const auto& lvl : levels)
307 b += lvl.bytes();
308 return b;
309 }
310
311 public:
312
313 params prm;
314
315 private:
316
317 struct AMGLevel
318 {
319 size_t m_rows = 0;
320 size_t m_nonzeros = 0;
321
322 std::shared_ptr<vector> f;
323 std::shared_ptr<vector> u;
324 std::shared_ptr<vector> t;
325
326 std::shared_ptr<matrix> A;
327 std::shared_ptr<matrix> P;
328 std::shared_ptr<matrix> R;
329
330 std::shared_ptr<build_matrix> bP;
331 std::shared_ptr<build_matrix> bR;
332
333 std::shared_ptr<typename Backend::direct_solver> solve;
334
335 std::shared_ptr<relax_type> relax;
336
337 size_t bytes() const
338 {
339 size_t b = 0;
340
341 if (f)
342 b += backend::bytes(*f);
343 if (u)
344 b += backend::bytes(*u);
345 if (t)
346 b += backend::bytes(*t);
347
348 if (A)
349 b += backend::bytes(*A);
350 if (P)
351 b += backend::bytes(*P);
352 if (R)
353 b += backend::bytes(*R);
354
355 if (solve)
356 b += backend::bytes(*solve);
357 if (relax)
358 b += backend::bytes(*relax);
359
360 return b;
361 }
362
363 AMGLevel() = default;
364
365 AMGLevel(std::shared_ptr<build_matrix> A, params& prm, const backend_params& bprm)
366 : m_rows(backend::nbRow(*A))
367 , m_nonzeros(backend::nonzeros(*A))
368 {
369 ARCCORE_ALINA_TIC("move to backend");
370 f = Backend::create_vector(m_rows, bprm);
371 u = Backend::create_vector(m_rows, bprm);
372 t = Backend::create_vector(m_rows, bprm);
373 this->A = Backend::copy_matrix(A, bprm);
374 ARCCORE_ALINA_TOC("move to backend");
375
376 ARCCORE_ALINA_TIC("relaxation");
377 relax = std::make_shared<relax_type>(*A, prm.relax, bprm);
378 ARCCORE_ALINA_TOC("relaxation");
379 }
380
381 std::shared_ptr<build_matrix>
382 step_down(std::shared_ptr<build_matrix> A, coarsening_type& C,
383 const backend_params& bprm, bool allow_rebuild)
384 {
385 ARCCORE_ALINA_TIC("transfer operators");
386 std::shared_ptr<build_matrix> P, R;
387
388 try {
389 std::tie(P, R) = C.transfer_operators(*A);
390 }
391 catch (error::empty_level) {
392 ARCCORE_ALINA_TOC("transfer operators");
393 return std::shared_ptr<build_matrix>();
394 }
395
396 sort_rows(*P);
397 sort_rows(*R);
398
399 if (allow_rebuild) {
400 bP = P;
401 bR = R;
402 }
403 ARCCORE_ALINA_TOC("transfer operators");
404
405 ARCCORE_ALINA_TIC("move to backend");
406 this->P = Backend::copy_matrix(P, bprm);
407 this->R = Backend::copy_matrix(R, bprm);
408 ARCCORE_ALINA_TOC("move to backend");
409
410 ARCCORE_ALINA_TIC("coarse operator");
411 A = C.coarse_operator(*A, *P, *R);
412 sort_rows(*A);
413 ARCCORE_ALINA_TOC("coarse operator");
414
415 return A;
416 }
417
418 void create_coarse(std::shared_ptr<build_matrix> A,
419 const backend_params& bprm, bool single_level)
420 {
421 m_rows = backend::nbRow(*A);
422 m_nonzeros = backend::nonzeros(*A);
423
424 u = Backend::create_vector(m_rows, bprm);
425 f = Backend::create_vector(m_rows, bprm);
426
427 solve = Backend::create_solver(A, bprm);
428 if (single_level)
429 this->A = Backend::copy_matrix(A, bprm);
430 }
431
432 std::shared_ptr<build_matrix>
433 rebuild(std::shared_ptr<build_matrix> A,
434 const coarsening_type& C,
435 const params& prm,
436 const backend_params& bprm)
437 {
438 if (this->A) {
439 ARCCORE_ALINA_TIC("move to backend");
440 this->A = Backend::copy_matrix(A, bprm);
441 ARCCORE_ALINA_TOC("move to backend");
442 }
443
444 if (relax) {
445 ARCCORE_ALINA_TIC("relaxation");
446 relax = std::make_shared<relax_type>(*A, prm.relax, bprm);
447 ARCCORE_ALINA_TOC("relaxation");
448 }
449
450 if (solve) {
451 ARCCORE_ALINA_TIC("coarsest level");
452 solve = Backend::create_solver(A, bprm);
453 ARCCORE_ALINA_TOC("coarsest level");
454 }
455
456 if (bP && bR) {
457 ARCCORE_ALINA_TIC("coarse operator");
458 A = C.coarse_operator(*A, *bP, *bR);
459 sort_rows(*A);
460 ARCCORE_ALINA_TOC("coarse operator");
461 }
462
463 return A;
464 }
465
466 size_t rows() const
467 {
468 return m_rows;
469 }
470
471 size_t nonzeros() const
472 {
473 return m_nonzeros;
474 }
475 };
476
477 typedef std::list<AMGLevel>::const_iterator level_iterator;
478
479 std::list<AMGLevel> levels;
480
481 void _initialize(std::shared_ptr<build_matrix> A,
482 const backend_params& bprm = backend_params())
483 {
484 precondition(backend::nbRow(*A) == backend::nbColumn(*A), "Matrix should be square!");
485
486 bool direct_coarse_solve = true;
487
488 coarsening_type C(prm.coarsening);
489 std::cout << "DoInit AMG coarse_enough=" << prm.coarse_enough << "\n";
490 while (backend::nbRow(*A) > prm.coarse_enough) {
491 std::cout << "DoIteration nb_row=" << backend::nbRow(*A) << "\n";
492 levels.push_back(AMGLevel(A, prm, bprm));
493
494 if (levels.size() >= prm.max_levels)
495 break;
496
497 A = levels.back().step_down(A, C, bprm, prm.allow_rebuild);
498 if (!A) {
499 // Zero-sized coarse level. Probably the system matrix on
500 // this level is diagonal, should be easily solvable with a
501 // couple of smoother iterations.
502 direct_coarse_solve = false;
503 break;
504 }
505 }
506
507 if (!A || backend::nbRow(*A) > prm.coarse_enough) {
508 // The coarse matrix is still too big to be solved directly.
509 direct_coarse_solve = false;
510 }
511
512 if (direct_coarse_solve) {
513 ARCCORE_ALINA_TIC("coarsest level");
514 if (prm.direct_coarse) {
515 AMGLevel l;
516 l.create_coarse(A, bprm, levels.empty());
517 levels.push_back(l);
518 }
519 else {
520 levels.push_back(AMGLevel(A, prm, bprm));
521 }
522 ARCCORE_ALINA_TOC("coarsest level");
523 }
524 }
525
526 template <class Vec1, class Vec2>
527 void cycle(level_iterator lvl, const Vec1& rhs, Vec2& x) const
528 {
529 level_iterator nxt = lvl, end = levels.end();
530 ++nxt;
531
532 if (nxt == end) {
533 if (lvl->solve) {
534 ARCCORE_ALINA_TIC("coarse");
535 (*lvl->solve)(rhs, x);
536 ARCCORE_ALINA_TOC("coarse");
537 }
538 else {
539 ARCCORE_ALINA_TIC("relax");
540 for (size_t i = 0; i < prm.npre; ++i)
541 lvl->relax->apply_pre(*lvl->A, rhs, x, *lvl->t);
542 for (size_t i = 0; i < prm.npost; ++i)
543 lvl->relax->apply_post(*lvl->A, rhs, x, *lvl->t);
544 ARCCORE_ALINA_TOC("relax");
545 }
546 }
547 else {
548 for (size_t j = 0; j < prm.ncycle; ++j) {
549 ARCCORE_ALINA_TIC("relax");
550 for (size_t i = 0; i < prm.npre; ++i)
551 lvl->relax->apply_pre(*lvl->A, rhs, x, *lvl->t);
552 ARCCORE_ALINA_TOC("relax");
553
554 backend::residual(rhs, *lvl->A, x, *lvl->t);
555
556 backend::spmv(math::identity<scalar_type>(), *lvl->R, *lvl->t, math::zero<scalar_type>(), *nxt->f);
557
558 backend::clear(*nxt->u);
559 cycle(nxt, *nxt->f, *nxt->u);
560
561 backend::spmv(math::identity<scalar_type>(), *lvl->P, *nxt->u, math::identity<scalar_type>(), x);
562
563 ARCCORE_ALINA_TIC("relax");
564 for (size_t i = 0; i < prm.npost; ++i)
565 lvl->relax->apply_post(*lvl->A, rhs, x, *lvl->t);
566 ARCCORE_ALINA_TOC("relax");
567 }
568 }
569 }
570
571 template <class B, template <class> class C, template <class> class R>
572 friend std::ostream& operator<<(std::ostream& os, const AMG<B, C, R>& a);
573};
574
575/*---------------------------------------------------------------------------*/
576/*---------------------------------------------------------------------------*/
577
579template <class B, template <class> class C, template <class> class R>
580std::ostream& operator<<(std::ostream& os, const AMG<B, C, R>& a)
581{
582 typedef typename AMG<B, C, R>::AMGLevel level;
584
585 size_t sum_dof = 0;
586 size_t sum_nnz = 0;
587 size_t sum_mem = 0;
588
589 for (const level& lvl : a.levels) {
590 sum_dof += lvl.rows();
591 sum_nnz += lvl.nonzeros();
592 sum_mem += lvl.bytes();
593 }
594
595 os << "Number of levels: " << a.levels.size()
596 << "\nOperator complexity: " << std::fixed << std::setprecision(2)
597 << 1.0 * sum_nnz / a.levels.front().nonzeros()
598 << "\nGrid complexity: " << std::fixed << std::setprecision(2)
599 << 1.0 * sum_dof / a.levels.front().rows()
600 << "\nMemory footprint: " << human_readable_memory(sum_mem)
601 << "\n\n"
602 "level unknowns nonzeros memory\n"
603 "---------------------------------------------\n";
604
605 size_t depth = 0;
606 for (const level& lvl : a.levels) {
607 os << std::setw(5) << depth++
608 << std::setw(13) << lvl.rows()
609 << std::setw(15) << lvl.nonzeros()
610 << std::setw(12) << human_readable_memory(lvl.bytes())
611 << " (" << std::setw(5) << std::fixed << std::setprecision(2)
612 << 100.0 * lvl.nonzeros() / sum_nnz
613 << "%)" << std::endl;
614 }
615
616 return os;
617}
618
619/*---------------------------------------------------------------------------*/
620/*---------------------------------------------------------------------------*/
621
622} // namespace Arcane::Alina
623
624/*---------------------------------------------------------------------------*/
625/*---------------------------------------------------------------------------*/
626
627#endif
void cycle(const Vec1 &rhs, Vec2 &&x) const
Performs single V-cycle for the given right-hand side and solution.
Definition AMG.h:266
std::shared_ptr< matrix > system_matrix_ptr() const
Returns the system matrix from the finest level.
Definition AMG.h:293
void apply(const Vec1 &rhs, Vec2 &&x) const
Performs single V-cycle after clearing x.
Definition AMG.h:280
AMG(const AlinaContext &context, const Matrix &M, const params &p=params(), const backend_params &bprm=backend_params())
Builds the AMG hierarchy for the system matrix.
Definition AMG.h:190
friend std::ostream & operator<<(std::ostream &os, const AMG< B, C, R > &a)
Sends information about the AMG hierarchy to output stream.
Definition AMG.h:580
Backend::params backend_params
Backend parameters.
Definition AMG.h:90
void rebuild(const Matrix &M, const backend_params &bprm=backend_params())
Rebuild the hierarchy using the new system matrix.
Definition AMG.h:229
void rebuild(std::shared_ptr< build_matrix > A, const backend_params &bprm=backend_params())
Rebuild the hierarchy using the new system matrix.
Definition AMG.h:243
AMG(const AlinaContext &context, std::shared_ptr< build_matrix > A, const params &p=params(), const backend_params &bprm=backend_params())
Builds the AMG hierarchy for the system matrix.
Definition AMG.h:213
Class to store parameters as a hierarchical key/value tree.
Definition AlinaUtils.h:113
Save ostream flags in constructor, restore in destructor.
Matrix class, to be used by user.
std::int32_t Int32
Signed integer type of 32 bits.
Parameters of the method.
Definition AMG.h:99
bool allow_rebuild
Keep matrices in internal format to allow for quick rebuild of the hierarchy.
Definition AMG.h:145
Int32 npost
Number of post-relaxations.
Definition AMG.h:136
coarsening_params coarsening
Coarsening parameters.
Definition AMG.h:103
Int32 coarse_enough
Specifies when level is coarse enough to be solved directly.
Definition AMG.h:113
bool direct_coarse
Use direct solver at the coarsest level.
Definition AMG.h:121
Int32 pre_cycles
Number of cycles to make as part of preconditioning.
Definition AMG.h:142
relax_params relax
Relaxation parameters.
Definition AMG.h:104
Int32 max_levels
Maximum number of levels.
Definition AMG.h:130
Int32 ncycle
Number of cycles (1 for V-cycle, 2 for W-cycle, etc.).
Definition AMG.h:139
Int32 npre
Number of pre-relaxations.
Definition AMG.h:133