Arcane  4.2.3.0
Developer documentation
Loading...
Searching...
No Matches
DistributedAMG.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/* DistributedAMG.h (C) 2000-2026 */
9/* */
10/* Distributed memory AMG preconditioner. */
11/*---------------------------------------------------------------------------*/
12#ifndef ARCCORE_ALINA_DISTRIBUTEDAMG_H
13#define ARCCORE_ALINA_DISTRIBUTEDAMG_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#include <iostream>
27#include <iomanip>
28#include <list>
29#include <memory>
30
31#include "arccore/alina/BackendInterface.h"
32#include "arccore/alina/MessagePassingUtils.h"
33#include "arccore/alina/DistributedMatrix.h"
34#include "arccore/alina/DistributedSkylineLUDirectSolver.h"
35#include "arccore/alina/SimpleMatrixPartitioner.h"
36
37/*---------------------------------------------------------------------------*/
38/*---------------------------------------------------------------------------*/
39
40namespace Arcane::Alina
41{
42
43/*---------------------------------------------------------------------------*/
44/*---------------------------------------------------------------------------*/
45
46template <class Backend,
47 class Coarsening,
48 class Relaxation,
50 class Repartition = SimpleMatrixPartitioner<Backend>>
51class DistributedAMG
52{
53 public:
54
55 using backend_type = Backend;
56 using BackendType = Backend;
57
58 typedef typename Backend::params backend_params;
59 typedef typename Backend::value_type value_type;
60 typedef typename math::scalar_of<value_type>::type scalar_type;
61 typedef DistributedMatrix<Backend> matrix;
62 typedef typename Backend::vector vector;
63
64 struct params
65 {
66 typedef typename Coarsening::params coarsening_params;
67 typedef typename Relaxation::params relax_params;
68 typedef typename DirectSolver::params direct_params;
69 typedef typename Repartition::params repart_params;
70
71 coarsening_params coarsening;
72 relax_params relax;
73 direct_params direct;
74 repart_params repart;
75
83 Int32 coarse_enough = DirectSolver::coarse_enough();
84
91 bool direct_coarse = true;
92
100 Int32 max_levels = std::numeric_limits<Int32>::max();
101
104
107
110
113
115 bool allow_rebuild = false;
116
117 params() = default;
118
119 params(const PropertyTree& p)
120 : ARCCORE_ALINA_PARAMS_IMPORT_CHILD(p, coarsening)
121 , ARCCORE_ALINA_PARAMS_IMPORT_CHILD(p, relax)
122 , ARCCORE_ALINA_PARAMS_IMPORT_CHILD(p, direct)
123 , ARCCORE_ALINA_PARAMS_IMPORT_CHILD(p, repart)
124 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, coarse_enough)
125 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, direct_coarse)
126 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, max_levels)
127 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, npre)
128 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, npost)
129 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, ncycle)
130 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, pre_cycles)
131 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, allow_rebuild)
132 {
133 p.check_params({ "coarsening", "relax", "direct", "repart", "coarse_enough", "direct_coarse", "max_levels", "npre", "npost", "ncycle", "pre_cycles", "allow_rebuild" });
134
135 Alina::precondition(max_levels > 0, "max_levels should be positive");
136 }
137
138 void get(Alina::PropertyTree& p, const std::string& path = "") const
139 {
140 ARCCORE_ALINA_PARAMS_EXPORT_CHILD(p, path, coarsening);
141 ARCCORE_ALINA_PARAMS_EXPORT_CHILD(p, path, relax);
142 ARCCORE_ALINA_PARAMS_EXPORT_CHILD(p, path, direct);
143 ARCCORE_ALINA_PARAMS_EXPORT_CHILD(p, path, repart);
144 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, coarse_enough);
145 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, direct_coarse);
146 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, max_levels);
147 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, npre);
148 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, npost);
149 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, ncycle);
150 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, pre_cycles);
151 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, allow_rebuild);
152 }
153 } prm;
154
155 template <class Matrix>
156 DistributedAMG([[maybe_unused]] const AlinaContext& context,
157 AlinaCommunicator comm,
158 const Matrix& A,
159 const params& prm = params(),
160 const backend_params& bprm = backend_params())
161 : prm(prm)
162 , comm(comm)
163 , repart(prm.repart)
164 {
165 init(std::make_shared<matrix>(comm, A, backend::nbRow(A)), bprm);
166 }
167
168 DistributedAMG([[maybe_unused]] const AlinaContext& context,
169 AlinaCommunicator comm,
170 std::shared_ptr<matrix> A,
171 const params& prm = params(),
172 const backend_params& bprm = backend_params())
173 : prm(prm)
174 , comm(comm)
175 , repart(prm.repart)
176 {
177 init(A, bprm);
178 }
179
186 template <class Matrix>
187 void rebuild(const Matrix& M,
188 const backend_params& bprm = backend_params())
189 {
190 rebuild(std::make_shared<matrix>(comm, M, backend::nbRow(M)), bprm);
191 }
192
193 template <class OtherBackend>
194 typename std::enable_if<!std::is_same<Backend, OtherBackend>::value, void>::type
195 rebuild(std::shared_ptr<DistributedMatrix<OtherBackend>> A,
196 const backend_params& bprm = backend_params())
197 {
198 return rebuild(std::make_shared<matrix>(*A), bprm);
199 }
200
201 void rebuild(std::shared_ptr<matrix> A,
202 const backend_params& bprm = backend_params())
203 {
204 precondition(prm.allow_rebuild, "allow_rebuild is not set!");
205 precondition(A->glob_rows() == system_matrix().glob_rows() &&
206 A->glob_cols() == system_matrix().glob_cols(),
207 "Matrix dimensions differ from the original ones!");
208
209 ARCCORE_ALINA_TIC("rebuild");
210 this->A = A;
211 Coarsening C(prm.coarsening);
212 for (auto& level : levels) {
213 A = level.rebuild(A, C, prm, bprm);
214 }
215 this->A->move_to_backend(bprm);
216 ARCCORE_ALINA_TOC("rebuild");
217 }
218
219 template <class Vec1, class Vec2>
220 void cycle(const Vec1& rhs, Vec2&& x) const
221 {
222 cycle(levels.begin(), rhs, x);
223 }
224
225 template <class Vec1, class Vec2>
226 void apply(const Vec1& rhs, Vec2&& x) const
227 {
228 if (prm.pre_cycles) {
229 backend::clear(x);
230 for (unsigned i = 0; i < prm.pre_cycles; ++i)
231 cycle(levels.begin(), rhs, x);
232 }
233 else {
234 backend::copy(rhs, x);
235 }
236 }
237
239 std::shared_ptr<matrix> system_matrix_ptr() const
240 {
241 return A;
242 }
243
244 const matrix& system_matrix() const
245 {
246 return *system_matrix_ptr();
247 }
248
249 private:
250
251 struct DistributedAMGLevel
252 {
253 ptrdiff_t nrows, nnz;
254 int active_procs;
255
256 std::shared_ptr<matrix> A, P, R;
257 std::shared_ptr<vector> f, u, t;
258 std::shared_ptr<Relaxation> relax;
259 std::shared_ptr<DirectSolver> solve;
260
261 DistributedAMGLevel() = default;
262
263 DistributedAMGLevel(std::shared_ptr<matrix> a,
264 params& prm,
265 const backend_params& bprm,
266 bool direct = false)
267 : nrows(a->glob_rows())
268 , nnz(a->glob_nonzeros())
269 , f(Backend::create_vector(a->loc_rows(), bprm))
270 , u(Backend::create_vector(a->loc_rows(), bprm))
271 {
272 int active = (a->loc_rows() > 0);
273 active_procs = a->comm().reduceSum(active);
274
275 sort_rows(*a);
276
277 if (direct) {
278 ARCCORE_ALINA_TIC("direct solver");
279 solve = std::make_shared<DirectSolver>(a->comm(), *a, prm.direct);
280 ARCCORE_ALINA_TOC("direct solver");
281 }
282 else {
283 A = a;
284 t = Backend::create_vector(a->loc_rows(), bprm);
285
286 ARCCORE_ALINA_TIC("relaxation");
287 relax = std::make_shared<Relaxation>(*a, prm.relax, bprm);
288 ARCCORE_ALINA_TOC("relaxation");
289 }
290 }
291
292 std::shared_ptr<matrix> step_down(Coarsening& C, const Repartition& repart)
293 {
294 ARCCORE_ALINA_TIC("transfer operators");
295 std::tie(P, R) = C.transfer_operators(*A);
296
297 ARCCORE_ALINA_TIC("sort");
298 sort_rows(*P);
299 sort_rows(*R);
300 ARCCORE_ALINA_TOC("sort");
301
302 ARCCORE_ALINA_TOC("transfer operators");
303
304 if (P->glob_cols() == 0) {
305 // Zero-sized coarse level in AMG (diagonal matrix?)
306 return std::shared_ptr<matrix>();
307 }
308
309 ARCCORE_ALINA_TIC("coarse operator");
310 auto Ac = C.coarse_operator(*A, *P, *R);
311 ARCCORE_ALINA_TOC("coarse operator");
312
313 if (repart.is_needed(*Ac)) {
314 ARCCORE_ALINA_TIC("partition");
315 auto I = repart(*Ac, block_size(C));
316 auto J = transpose(*I);
317
318 P = product(*P, *I);
319 R = product(*J, *R);
320 Ac = product(*J, *product(*Ac, *I));
321 ARCCORE_ALINA_TOC("partition");
322 }
323
324 return Ac;
325 }
326
327 std::shared_ptr<matrix> rebuild(std::shared_ptr<matrix> A,
328 const Coarsening& C,
329 const params& prm,
330 const backend_params& bprm)
331 {
332 if (relax) {
333 relax = std::make_shared<Relaxation>(*A, prm.relax, bprm);
334 std::cout << "DistributedAMG: relaxation=" << relax << "\n";
335 }
336
337 if (solve) {
338 solve = std::make_shared<DirectSolver>(A->comm(), *A, prm.direct);
339 }
340
341 if (this->A) {
342 this->A = A;
343 }
344
345 if (P && R) {
346 A = C.coarse_operator(*A, *P, *R);
347 }
348
349 if (this->A) {
350 this->A->move_to_backend(bprm);
351 }
352
353 return A;
354 }
355
356 void move_to_backend(const backend_params& bprm, bool keep_src = false)
357 {
358 ARCCORE_ALINA_TIC("move to backend");
359 if (A)
360 A->move_to_backend(bprm);
361 if (P)
362 P->move_to_backend(bprm, keep_src);
363 if (R)
364 R->move_to_backend(bprm, keep_src);
365 ARCCORE_ALINA_TOC("move to backend");
366 }
367
368 ptrdiff_t rows() const
369 {
370 return nrows;
371 }
372
373 ptrdiff_t nonzeros() const
374 {
375 return nnz;
376 }
377 };
378
379 typedef typename std::list<DistributedAMGLevel>::const_iterator level_iterator;
380
382 std::shared_ptr<matrix> A;
383 Repartition repart;
384 std::list<DistributedAMGLevel> levels;
385
386 void init(std::shared_ptr<matrix> A, const backend_params& bprm)
387 {
388 A->comm().check(A->glob_rows() == A->glob_cols(), "Matrix should be square!");
389
390 this->A = A;
391 Coarsening C(prm.coarsening);
392 //std::cout << "DistributedAMGInit: Coarsening=" << C << "\n";
393 bool need_coarse = true;
394
395 while (A->glob_rows() > prm.coarse_enough) {
396 levels.push_back(DistributedAMGLevel(A, prm, bprm));
397
398 if (levels.size() >= prm.max_levels) {
399 levels.back().move_to_backend(bprm, prm.allow_rebuild);
400 break;
401 }
402
403 A = levels.back().step_down(C, repart);
404 levels.back().move_to_backend(bprm, prm.allow_rebuild);
405
406 if (!A) {
407 // Zero-sized coarse level. Probably the system matrix on
408 // this level is diagonal, should be easily solvable with a
409 // couple of smoother iterations.
410 need_coarse = false;
411 break;
412 }
413 }
414
415 if (!A || A->glob_rows() > prm.coarse_enough) {
416 // The coarse matrix is still too big to be solved directly.
417 need_coarse = false;
418 }
419
420 if (A && need_coarse) {
421 levels.push_back(DistributedAMGLevel(A, prm, bprm, prm.direct_coarse));
422 levels.back().move_to_backend(bprm, prm.allow_rebuild);
423 }
424
425 ARCCORE_ALINA_TIC("move to backend");
426 this->A->move_to_backend(bprm, prm.allow_rebuild);
427 ARCCORE_ALINA_TOC("move to backend");
428 }
429
430 template <class Vec1, class Vec2>
431 void cycle(level_iterator lvl, const Vec1& rhs, Vec2& x) const
432 {
433 level_iterator nxt = lvl, end = levels.end();
434 ++nxt;
435
436 if (nxt == end) {
437 if (lvl->solve) {
438 ARCCORE_ALINA_TIC("direct solver");
439 (*lvl->solve)(rhs, x);
440 ARCCORE_ALINA_TOC("direct solver");
441 }
442 else {
443 ARCCORE_ALINA_TIC("relax");
444 for (size_t i = 0; i < prm.npre; ++i)
445 lvl->relax->apply_pre(*lvl->A, rhs, x, *lvl->t);
446 for (size_t i = 0; i < prm.npost; ++i)
447 lvl->relax->apply_post(*lvl->A, rhs, x, *lvl->t);
448 ARCCORE_ALINA_TOC("relax");
449 }
450 }
451 else {
452 for (size_t j = 0; j < prm.ncycle; ++j) {
453 ARCCORE_ALINA_TIC("relax");
454 for (size_t i = 0; i < prm.npre; ++i)
455 lvl->relax->apply_pre(*lvl->A, rhs, x, *lvl->t);
456 ARCCORE_ALINA_TOC("relax");
457
458 backend::residual(rhs, *lvl->A, x, *lvl->t);
459
460 backend::spmv(math::identity<scalar_type>(), *lvl->R, *lvl->t, math::zero<scalar_type>(), *nxt->f);
461
462 backend::clear(*nxt->u);
463 cycle(nxt, *nxt->f, *nxt->u);
464
465 backend::spmv(math::identity<scalar_type>(), *lvl->P, *nxt->u, math::identity<scalar_type>(), x);
466
467 ARCCORE_ALINA_TIC("relax");
468 for (size_t i = 0; i < prm.npost; ++i)
469 lvl->relax->apply_post(*lvl->A, rhs, x, *lvl->t);
470 ARCCORE_ALINA_TOC("relax");
471 }
472 }
473 }
474
475 template <class B, class C, class R, class D, class I>
476 friend std::ostream& operator<<(std::ostream& os, const DistributedAMG<B, C, R, D, I>& a);
477};
478
479template <class B, class C, class R, class D, class I>
480std::ostream& operator<<(std::ostream& os, const DistributedAMG<B, C, R, D, I>& a)
481{
484
485 size_t sum_dof = 0;
486 size_t sum_nnz = 0;
487
488 for (const level& lvl : a.levels) {
489 sum_dof += lvl.rows();
490 sum_nnz += lvl.nonzeros();
491 }
492
493 os << "Preconditioner: DistributedAMG\n";
494 //os << "Coarsening: " << a.prm.coarsening.type << "\n";
495 //os << "Relaxation: " << a.prm.relax.type << "\n";
496
497 os << "Number of levels: " << a.levels.size()
498 << "\nOperator complexity: " << std::fixed << std::setprecision(2)
499 << 1.0 * sum_nnz / a.levels.front().nonzeros()
500 << "\nGrid complexity: " << std::fixed << std::setprecision(2)
501 << 1.0 * sum_dof / a.levels.front().rows()
502 << "\n\nlevel unknowns nonzeros\n"
503 << "---------------------------------\n";
504
505 size_t depth = 0;
506 for (const level& lvl : a.levels) {
507 os << std::setw(5) << depth++
508 << std::setw(13) << lvl.rows()
509 << std::setw(15) << lvl.nonzeros() << " ("
510 << std::setw(5) << std::fixed << std::setprecision(2)
511 << 100.0 * lvl.nonzeros() / sum_nnz
512 << "%) [" << lvl.active_procs << "]" << std::endl;
513 }
514
515 return os;
516}
517
518/*---------------------------------------------------------------------------*/
519/*---------------------------------------------------------------------------*/
520
521} // namespace Arcane::Alina
522
523/*---------------------------------------------------------------------------*/
524/*---------------------------------------------------------------------------*/
525
526#endif
void rebuild(const Matrix &M, const backend_params &bprm=backend_params())
Rebuild the hierarchy using the new system matrix.
std::shared_ptr< matrix > system_matrix_ptr() const
Returns the system matrix from the finest level.
Distributed Matrix using message passing.
Provides distributed direct solver interface for Skyline LU solver.
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.
Convenience wrapper around MPI_Comm.
void check(bool cond, const String &message)
Communicator-wise condition checking.
repart_params repart
Repartition parameters.
Int32 coarse_enough
Specifies when level is coarse enough to be solved directly.
relax_params relax
Relaxation parameters.
bool allow_rebuild
Keep matrices in internal format to allow for quick rebuild of the hierarchy.
Int32 npre
Number of pre-relaxations.
Int32 npost
Number of post-relaxations.
Int32 ncycle
Number of cycles (1 for V-cycle, 2 for W-cycle, etc.).
Int32 max_levels
Maximum number of levels.
coarsening_params coarsening
Coarsening parameters.
direct_params direct
Direct solver parameters.
Int32 pre_cycles
Number of cycles to make as part of preconditioning.
bool direct_coarse
Use direct solver at the coarsest level.
Simple matrix partitioner merging consecutive domains together.