Arcane  4.2.3.0
Developer documentation
Loading...
Searching...
No Matches
DistributedSolver.cc
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/*---------------------------------------------------------------------------*/
9/*
10 * This file is based on the work on AMGCL library (version march 2026)
11 * which can be found at https://github.com/ddemidov/amgcl.
12 *
13 * Copyright (c) 2012-2022 Denis Demidov <dennis.demidov@gmail.com>
14 * SPDX-License-Identifier: MIT
15 */
16/*---------------------------------------------------------------------------*/
17/*---------------------------------------------------------------------------*/
18
19// For Eigen
20#pragma GCC diagnostic ignored "-Wdeprecated-copy"
21#pragma GCC diagnostic ignored "-Wint-in-bool-context"
22
23#include <iostream>
24#include <vector>
25#include <string>
26
27#include <boost/preprocessor/seq/for_each.hpp>
28
29#include "arccore/alina/BuiltinBackend.h"
30#include "arccore/alina/StaticMatrix.h"
31#include "arccore/alina/Adapters.h"
32
33#if defined(SOLVER_BACKEND_CUDA)
34#include "arccore/alina/CudaBackend.h"
35#include "arccore/alina/relaxation_cusparse_ilu0.h"
36#else
37#ifndef SOLVER_BACKEND_BUILTIN
38#define SOLVER_BACKEND_BUILTIN
39#endif
40#endif
41
42#include "arccore/alina/MessagePassingUtils.h"
43#include "arccore/alina/DistributedPreconditionedSolver.h"
44#include "arccore/alina/DistributedPreconditioner.h"
45#include "arccore/alina/DistributedSolverRuntime.h"
46
47#include "arccore/alina/IO.h"
48#include "arccore/alina/Profiler.h"
49#include "arccore/common/internal/ProgramOptions.h"
50
51#include "SampleProblemCommon.h"
52
53#ifndef ARCCORE_ALINA_BLOCK_SIZES
54#define ARCCORE_ALINA_BLOCK_SIZES (3)(4)
55#endif
56
57using namespace Arcane;
58
59//---------------------------------------------------------------------------
60ptrdiff_t
61read_matrix_market(Alina::AlinaCommunicator comm,
62 const std::string& A_file, const std::string& rhs_file, int block_size,
63 std::vector<ptrdiff_t>& ptr,
64 std::vector<ptrdiff_t>& col,
65 std::vector<double>& val,
66 std::vector<double>& rhs)
67{
68 Alina::IO::mm_reader A_mm(A_file);
69 ptrdiff_t n = A_mm.rows();
70
71 ptrdiff_t chunk = (n + comm.size - 1) / comm.size;
72 if (chunk % block_size != 0) {
73 chunk += block_size - chunk % block_size;
74 }
75
76 ptrdiff_t row_beg = std::min(n, chunk * comm.rank);
77 ptrdiff_t row_end = std::min(n, row_beg + chunk);
78
79 chunk = row_end - row_beg;
80
81 A_mm(ptr, col, val, row_beg, row_end);
82
83 if (rhs_file.empty()) {
84 rhs.resize(chunk);
85 std::fill(rhs.begin(), rhs.end(), 1.0);
86 }
87 else {
88 Alina::IO::mm_reader rhs_mm(rhs_file);
89 rhs_mm(rhs, row_beg, row_end);
90 }
91
92 return chunk;
93}
94
95//---------------------------------------------------------------------------
96ptrdiff_t
97read_binary(Alina::AlinaCommunicator comm,
98 const std::string& A_file, const std::string& rhs_file, int block_size,
99 std::vector<ptrdiff_t>& ptr,
100 std::vector<ptrdiff_t>& col,
101 std::vector<double>& val,
102 std::vector<double>& rhs)
103{
104 ptrdiff_t n = Alina::IO::crs_size<ptrdiff_t>(A_file);
105
106 ptrdiff_t chunk = (n + comm.size - 1) / comm.size;
107 if (chunk % block_size != 0) {
108 chunk += block_size - chunk % block_size;
109 }
110
111 ptrdiff_t row_beg = std::min(n, chunk * comm.rank);
112 ptrdiff_t row_end = std::min(n, row_beg + chunk);
113
114 chunk = row_end - row_beg;
115
116 Alina::IO::read_crs(A_file, n, ptr, col, val, row_beg, row_end);
117
118 if (rhs_file.empty()) {
119 rhs.resize(chunk);
120 std::fill(rhs.begin(), rhs.end(), 1.0);
121 }
122 else {
123 ptrdiff_t rows, cols;
124 Alina::IO::read_dense(rhs_file, rows, cols, rhs, row_beg, row_end);
125 }
126
127 return chunk;
128}
129
130//---------------------------------------------------------------------------
131template <class Backend, class Matrix>
132std::shared_ptr<Alina::DistributedMatrix<Backend>>
133partition(Alina::AlinaCommunicator comm, const Matrix& Astrip,
134 typename Backend::vector& rhs, const typename Backend::params& bprm,
135 Alina::eMatrixPartitionerType ptype, int block_size = 1)
136{
137 auto& prof = Alina::Profiler::globalProfiler();
138 typedef typename Backend::value_type val_type;
139 typedef typename Alina::math::rhs_of<val_type>::type rhs_type;
140 typedef Alina::DistributedMatrix<Backend> DMatrix;
141
142 auto A = std::make_shared<DMatrix>(comm, Astrip);
143
144 if (comm.size == 1 || ptype == Alina::eMatrixPartitionerType::merge)
145 return A;
146
147 prof.tic("partition");
149 prm.put("type", ptype);
151
152 auto I = part(*A, block_size);
153 auto J = transpose(*I);
154 A = product(*J, *product(*A, *I));
155
156#if defined(SOLVER_BACKEND_BUILTIN)
157 Alina::numa_vector<rhs_type> new_rhs(J->loc_rows());
158#elif defined(SOLVER_BACKEND_CUDA)
159 thrust::device_vector<rhs_type> new_rhs(J->loc_rows());
160#endif
161
162 J->move_to_backend(bprm);
163
164 Alina::backend::spmv(1, *J, rhs, 0, new_rhs);
165 rhs.swap(new_rhs);
166 prof.toc("partition");
167
168 return A;
169}
170
171//---------------------------------------------------------------------------
172#if defined(SOLVER_BACKEND_BUILTIN)
173template <int B>
174void solve_block(const Alina::AlinaContext& context,
176 ptrdiff_t chunk,
177 const std::vector<ptrdiff_t>& ptr,
178 const std::vector<ptrdiff_t>& col,
179 const std::vector<double>& val,
180 const Alina::PropertyTree& prm,
181 const std::vector<double>& f,
182 Alina::eMatrixPartitionerType ptype)
183{
184 auto& prof = Alina::Profiler::globalProfiler();
185 typedef Alina::StaticMatrix<double, B, B> val_type;
186 typedef Alina::StaticMatrix<double, B, 1> rhs_type;
187
188 typedef Alina::BuiltinBackend<val_type> Backend;
189
190 typedef Alina::DistributedMatrix<Backend> DMatrix;
191
195 Solver;
196
197 typename Backend::params bprm;
198
199 Alina::numa_vector<rhs_type> rhs(reinterpret_cast<const rhs_type*>(&f[0]),
200 reinterpret_cast<const rhs_type*>(&f[0]) + chunk / B);
201
202 auto get_distributed_matrix = [&]() {
203 auto t = prof.scoped_tic("distributed matrix");
204
205 std::shared_ptr<DMatrix> A;
206
207 if (ptype != Alina::eMatrixPartitionerType::merge) {
208 A = partition<Backend>(comm,
209 Alina::adapter::block_matrix<val_type>(std::tie(chunk, ptr, col, val)),
210 rhs, bprm, ptype, prm.get("precond.coarsening.aggr.block_size", 1));
211 chunk = A->loc_rows();
212 }
213 else {
214 A = std::make_shared<DMatrix>(
215 comm,
216 Alina::adapter::block_matrix<val_type>(std::tie(chunk, ptr, col, val)));
217 }
218
219 return A;
220 };
221
222 std::shared_ptr<DMatrix> A;
223 std::shared_ptr<Solver> solve;
224
225 {
226 auto t = prof.scoped_tic("setup");
227 A = get_distributed_matrix();
228 solve = std::make_shared<Solver>(context, comm, A, prm, bprm);
229 }
230
231 if (comm.rank == 0) {
232 std::cout << *solve << std::endl;
233 }
234
235 if (prm.get("precond.allow_rebuild", false)) {
236 if (comm.rank == 0) {
237 std::cout << "Rebuilding the preconditioner..." << std::endl;
238 }
239
240 {
241 auto t = prof.scoped_tic("rebuild");
242 A = get_distributed_matrix();
243 solve->precond().rebuild(A);
244 }
245
246 if (comm.rank == 0) {
247 std::cout << *solve << std::endl;
248 }
249 }
250
252
253 prof.tic("solve");
254 Alina::SolverResult r = (*solve)(rhs, x);
255 prof.toc("solve");
256
257 if (comm.rank == 0) {
258 std::cout << "Iterations: " << r.nbIteration() << std::endl
259 << "Error: " << r.residual() << std::endl
260 << prof << std::endl;
261 }
262}
263#endif
264
265//---------------------------------------------------------------------------
266void solve_scalar(const Alina::AlinaContext& context,
268 ptrdiff_t chunk,
269 const std::vector<ptrdiff_t>& ptr,
270 const std::vector<ptrdiff_t>& col,
271 const std::vector<double>& val,
272 const Alina::PropertyTree& prm,
273 const std::vector<double>& f,
274 Alina::eMatrixPartitionerType ptype)
275{
276 auto& prof = Alina::Profiler::globalProfiler();
277#if defined(SOLVER_BACKEND_BUILTIN)
278 //using Backend = Alina::BuiltinBackend<double>;
280#elif defined(SOLVER_BACKEND_CUDA)
281 using Backend = Alina::backend::cuda<double>;
282#endif
283
284 std::cout << "Using scalar solve ptr_size=" << sizeof(ptrdiff_t)
285 << " ptr_type_size=" << sizeof(Backend::ptr_type)
286 << " col_type_size=" << sizeof(Backend::col_type)
287 << " value_type_size=" << sizeof(Backend::value_type)
288 << "\n";
289
290 typedef Alina::DistributedMatrix<Backend> DMatrix;
291
293
294 typename Backend::params bprm;
295
296#if defined(SOLVER_BACKEND_BUILTIN)
298#elif defined(SOLVER_BACKEND_CUDA)
299 cusparseCreate(&bprm.cusparse_handle);
300 thrust::device_vector<double> rhs(f);
301#endif
302
303 auto get_distributed_matrix = [&]() {
304 auto t = prof.scoped_tic("distributed matrix");
305 std::shared_ptr<DMatrix> A;
306
307 if (ptype != Alina::eMatrixPartitionerType::merge) {
308 A = partition<Backend>(comm,
309 std::tie(chunk, ptr, col, val), rhs, bprm, ptype,
310 prm.get("precond.coarsening.aggr.block_size", 1));
311 chunk = A->loc_rows();
312 }
313 else {
314 A = std::make_shared<DMatrix>(comm, std::tie(chunk, ptr, col, val));
315 }
316
317 return A;
318 };
319
320 std::shared_ptr<DMatrix> A;
321 std::shared_ptr<Solver> solve;
322
323 {
324 auto t = prof.scoped_tic("setup");
325 A = get_distributed_matrix();
326 solve = std::make_shared<Solver>(context, comm, A, prm, bprm);
327 }
328
329 if (comm.rank == 0) {
330 std::cout << "SolverInfo:\n";
331 std::cout << *solve << std::endl;
332 }
333
334 if (prm.get("precond.allow_rebuild", false)) {
335 if (comm.rank == 0) {
336 std::cout << "Rebuilding the preconditioner..." << std::endl;
337 }
338
339 {
340 auto t = prof.scoped_tic("rebuild");
341 A = get_distributed_matrix();
342 solve->precond().rebuild(A);
343 }
344
345 if (comm.rank == 0) {
346 std::cout << *solve << std::endl;
347 }
348 }
349
350#if defined(SOLVER_BACKEND_BUILTIN)
352#elif defined(SOLVER_BACKEND_CUDA)
353 thrust::device_vector<double> x(chunk, 0.0);
354#endif
355
356 prof.tic("solve");
357 Alina::SolverResult r = (*solve)(rhs, x);
358 prof.toc("solve");
359
360 if (comm.rank == 0) {
361 std::cout << "Iterations: " << r.nbIteration() << std::endl
362 << "Error: " << r.residual() << std::endl
363 << prof << std::endl;
364 }
365}
366
367//---------------------------------------------------------------------------
368int main(int argc, char* argv[])
369{
370 auto& prof = Alina::Profiler::globalProfiler();
371
373 Alina::AlinaCommunicator comm(mpm);
374 if (comm.rank == 0)
375 std::cout << "World size: " << comm.size << std::endl;
376 Alina::AlinaContext context;
377
378 // Read configuration from command line
379 namespace po = Arcane::ProgramOptions;
380 po::options_description desc("Options");
381
382 desc.add_options()("help,h", "show help");
383 desc.add_options()("matrix,A",
384 po::value<std::string>(),
385 "System matrix in the MatrixMarket format. "
386 "When not specified, a Poisson problem in 3D unit cube is assembled. ");
387 desc.add_options()("rhs,f",
388 po::value<std::string>()->default_value(""),
389 "The RHS vector in the MatrixMarket format. "
390 "When omitted, a vector of ones is used by default. "
391 "Should only be provided together with a system matrix. ");
392 desc.add_options()("Ap",
393 po::value<std::vector<std::string>>()->multitoken(),
394 "Pre-partitioned matrix (single file per MPI process)");
395 desc.add_options()("fp",
396 po::value<std::vector<std::string>>()->multitoken(),
397 "Pre-partitioned RHS (single file per MPI process)");
398 desc.add_options()("binary,B",
399 po::bool_switch()->default_value(false),
400 "When specified, treat input files as binary instead of as MatrixMarket. "
401 "It is assumed the files were converted to binary format with mm2bin utility. ");
402 desc.add_options()("block-size,b",
403 po::value<int>()->default_value(1),
404 "The block size of the system matrix. "
405 "When specified, the system matrix is assumed to have block-wise structure. "
406 "This usually is the case for problems in elasticity, structural mechanics, "
407 "for coupled systems of PDE (such as Navier-Stokes equations), etc. ");
408 desc.add_options()("partitioner,r",
409 po::value<Alina::eMatrixPartitionerType>()->default_value(
410#if defined(ARCCORE_ALINA_HAVE_PARMETIS)
411 Alina::eMatrixPartitionerType::parmetis
412#else
413 Alina::eMatrixPartitionerType::merge
414#endif
415 ),
416 "Repartition the system matrix");
417 desc.add_options()("size,n",
418 po::value<ptrdiff_t>()->default_value(128),
419 "domain size");
420 desc.add_options()("prm-file,P",
421 po::value<std::string>(),
422 "Parameter file in json format. ");
423 desc.add_options()("prm,p",
424 po::value<std::vector<std::string>>()->multitoken(),
425 "Parameters specified as name=value pairs. "
426 "May be provided multiple times. Examples:\n"
427 " -p solver.tol=1e-3\n"
428 " -p precond.coarse_enough=300");
429 desc.add_options()("test-rebuild",
430 po::bool_switch()->default_value(false),
431 "When specified, try to rebuild the solver before solving. ");
432
434 p.add("prm", -1);
435
437 po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
438 po::notify(vm);
439
440 if (vm.count("help")) {
441 if (comm.rank == 0)
442 std::cout << desc << std::endl;
443 return 0;
444 }
445
447 if (vm.count("prm-file")) {
448 prm.read_json(vm["prm-file"].as<std::string>());
449 }
450
451 if (vm.count("prm")) {
452 for (const std::string& v : vm["prm"].as<std::vector<std::string>>()) {
453 prm.putKeyValue(v);
454 }
455 }
456
457 ptrdiff_t n;
458 std::vector<ptrdiff_t> ptr;
459 std::vector<ptrdiff_t> col;
460 std::vector<double> val;
461 std::vector<double> rhs;
462
463 int block_size = vm["block-size"].as<int>();
464 int aggr_block = prm.get("precond.coarsening.aggr.block_size", 1);
465
466 bool binary = vm["binary"].as<bool>();
467 Alina::eMatrixPartitionerType ptype = vm["partitioner"].as<Alina::eMatrixPartitionerType>();
468
469 if (vm.count("matrix")) {
470 prof.tic("read");
471 if (binary) {
472 n = read_binary(comm,
473 vm["matrix"].as<std::string>(),
474 vm["rhs"].as<std::string>(),
475 block_size * aggr_block, ptr, col, val, rhs);
476 }
477 else {
478 n = read_matrix_market(comm,
479 vm["matrix"].as<std::string>(),
480 vm["rhs"].as<std::string>(),
481 block_size * aggr_block, ptr, col, val, rhs);
482 }
483 prof.toc("read");
484 }
485 else if (vm.count("Ap")) {
486 prof.tic("read");
487 ptype = Alina::eMatrixPartitionerType::merge;
488
489 std::vector<std::string> Aparts = vm["Ap"].as<std::vector<std::string>>();
490 comm.check(Aparts.size() == static_cast<size_t>(comm.size),
491 "--Ap should have single entry per MPI process");
492
493 if (binary) {
494 Alina::IO::read_crs(Aparts[comm.rank], n, ptr, col, val);
495 }
496 else {
497 ptrdiff_t m;
498 std::tie(n, m) = Alina::IO::mm_reader(Aparts[comm.rank])(ptr, col, val);
499 }
500
501 if (vm.count("fp")) {
502 std::vector<std::string> fparts = vm["fp"].as<std::vector<std::string>>();
503 comm.check(fparts.size() == static_cast<size_t>(comm.size),
504 "--fp should have single entry per MPI process");
505
506 ptrdiff_t rows;
507 ptrdiff_t cols;
508
509 if (binary) {
510 Alina::IO::read_dense(fparts[comm.rank], rows, cols, rhs);
511 }
512 else {
513 std::tie(rows, cols) = Alina::IO::mm_reader(fparts[comm.rank])(rhs);
514 }
515
516 comm.check(rhs.size() == static_cast<size_t>(n), "Wrong RHS size");
517 }
518 else {
519 rhs.resize(n, 1);
520 }
521 prof.toc("read");
522 }
523 else {
524 prof.tic("assemble");
525 n = Alina::PoissonProblemGenerator::createDistributedMatrix(comm.rank, comm.size,
526 vm["size"].as<ptrdiff_t>(),
527 block_size * aggr_block, ptr, col, val, rhs);
528 prof.toc("assemble");
529 }
530
531 if (vm["test-rebuild"].as<bool>()) {
532 prm.put("precond.allow_rebuild", true);
533 }
534
535 switch (block_size) {
536
537#if defined(SOLVER_BACKEND_BUILTIN)
538#define ARCCORE_ALINA_CALL_BLOCK_SOLVER(z, data, B) \
539 case B: \
540 solve_block<B>(context, comm, n, ptr, col, val, prm, rhs, ptype); \
541 break;
542
543 BOOST_PP_SEQ_FOR_EACH(ARCCORE_ALINA_CALL_BLOCK_SOLVER, ~, ARCCORE_ALINA_BLOCK_SIZES)
544
545#undef ARCCORE_ALINA_CALL_BLOCK_SOLVER
546#endif
547
548 case 1:
549 solve_scalar(context, comm, n, ptr, col, val, prm, rhs, ptype);
550 break;
551 default:
552 if (comm.rank == 0)
553 std::cout << "Unsupported block size!" << std::endl;
554 }
555}
Distributed Matrix using message passing.
Iterative solver wrapper for distributed linear systems.
Matrix market reader.
Definition IO.h:54
Class to store parameters as a hierarchical key/value tree.
Definition AlinaUtils.h:113
Result of a solution.
Definition AlinaUtils.h:56
NUMA-aware vector container.
Definition NumaVector.h:42
Matrix class, to be used by user.
static Ref< IMessagePassingMng > createAndInitializeMPI(int *argc, char **argv[], int wanted_thread_level)
Initialize MPI and creates a manager associated with the MPI_WORLD_COMM communicator.
Fluent command-line parser builder.
Describes a set of command-line options.
Describes positional (non-option) arguments.
Stores parsed option values.
Reference to an instance.
__host__ __device__ Real3x3 transpose(const Real3x3 &t)
Transpose the matrix.
Definition MathUtils.h:265
-- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature --
Convenience wrapper around MPI_Comm.
void check(bool cond, const String &message)
Communicator-wise condition checking.
Runtime-configurable wrapper around matrix partitioner.