Arcane  4.2.2.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::mpi_communicator 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::mpi_communicator 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::mpi_communicator 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(Alina::mpi_communicator comm,
175 ptrdiff_t chunk,
176 const std::vector<ptrdiff_t>& ptr,
177 const std::vector<ptrdiff_t>& col,
178 const std::vector<double>& val,
179 const Alina::PropertyTree& prm,
180 const std::vector<double>& f,
181 Alina::eMatrixPartitionerType ptype)
182{
183 auto& prof = Alina::Profiler::globalProfiler();
184 typedef Alina::StaticMatrix<double, B, B> val_type;
185 typedef Alina::StaticMatrix<double, B, 1> rhs_type;
186
187 typedef Alina::BuiltinBackend<val_type> Backend;
188
189 typedef Alina::DistributedMatrix<Backend> DMatrix;
190
194 Solver;
195
196 typename Backend::params bprm;
197
198 Alina::numa_vector<rhs_type> rhs(reinterpret_cast<const rhs_type*>(&f[0]),
199 reinterpret_cast<const rhs_type*>(&f[0]) + chunk / B);
200
201 auto get_distributed_matrix = [&]() {
202 auto t = prof.scoped_tic("distributed matrix");
203
204 std::shared_ptr<DMatrix> A;
205
206 if (ptype != Alina::eMatrixPartitionerType::merge) {
207 A = partition<Backend>(comm,
208 Alina::adapter::block_matrix<val_type>(std::tie(chunk, ptr, col, val)),
209 rhs, bprm, ptype, prm.get("precond.coarsening.aggr.block_size", 1));
210 chunk = A->loc_rows();
211 }
212 else {
213 A = std::make_shared<DMatrix>(
214 comm,
215 Alina::adapter::block_matrix<val_type>(std::tie(chunk, ptr, col, val)));
216 }
217
218 return A;
219 };
220
221 std::shared_ptr<DMatrix> A;
222 std::shared_ptr<Solver> solve;
223
224 {
225 auto t = prof.scoped_tic("setup");
226 A = get_distributed_matrix();
227 solve = std::make_shared<Solver>(comm, A, prm, bprm);
228 }
229
230 if (comm.rank == 0) {
231 std::cout << *solve << std::endl;
232 }
233
234 if (prm.get("precond.allow_rebuild", false)) {
235 if (comm.rank == 0) {
236 std::cout << "Rebuilding the preconditioner..." << std::endl;
237 }
238
239 {
240 auto t = prof.scoped_tic("rebuild");
241 A = get_distributed_matrix();
242 solve->precond().rebuild(A);
243 }
244
245 if (comm.rank == 0) {
246 std::cout << *solve << std::endl;
247 }
248 }
249
251
252 prof.tic("solve");
253 Alina::SolverResult r = (*solve)(rhs, x);
254 prof.toc("solve");
255
256 if (comm.rank == 0) {
257 std::cout << "Iterations: " << r.nbIteration() << std::endl
258 << "Error: " << r.residual() << std::endl
259 << prof << std::endl;
260 }
261}
262#endif
263
264//---------------------------------------------------------------------------
265void solve_scalar(Alina::mpi_communicator comm,
266 ptrdiff_t chunk,
267 const std::vector<ptrdiff_t>& ptr,
268 const std::vector<ptrdiff_t>& col,
269 const std::vector<double>& val,
270 const Alina::PropertyTree& prm,
271 const std::vector<double>& f,
272 Alina::eMatrixPartitionerType ptype)
273{
274 auto& prof = Alina::Profiler::globalProfiler();
275#if defined(SOLVER_BACKEND_BUILTIN)
276 //using Backend = Alina::BuiltinBackend<double>;
278#elif defined(SOLVER_BACKEND_CUDA)
279 using Backend = Alina::backend::cuda<double>;
280#endif
281
282 std::cout << "Using scalar solve ptr_size=" << sizeof(ptrdiff_t)
283 << " ptr_type_size=" << sizeof(Backend::ptr_type)
284 << " col_type_size=" << sizeof(Backend::col_type)
285 << " value_type_size=" << sizeof(Backend::value_type)
286 << "\n";
287
288 typedef Alina::DistributedMatrix<Backend> DMatrix;
289
291
292 typename Backend::params bprm;
293
294#if defined(SOLVER_BACKEND_BUILTIN)
296#elif defined(SOLVER_BACKEND_CUDA)
297 cusparseCreate(&bprm.cusparse_handle);
298 thrust::device_vector<double> rhs(f);
299#endif
300
301 auto get_distributed_matrix = [&]() {
302 auto t = prof.scoped_tic("distributed matrix");
303 std::shared_ptr<DMatrix> A;
304
305 if (ptype != Alina::eMatrixPartitionerType::merge) {
306 A = partition<Backend>(comm,
307 std::tie(chunk, ptr, col, val), rhs, bprm, ptype,
308 prm.get("precond.coarsening.aggr.block_size", 1));
309 chunk = A->loc_rows();
310 }
311 else {
312 A = std::make_shared<DMatrix>(comm, std::tie(chunk, ptr, col, val));
313 }
314
315 return A;
316 };
317
318 std::shared_ptr<DMatrix> A;
319 std::shared_ptr<Solver> solve;
320
321 {
322 auto t = prof.scoped_tic("setup");
323 A = get_distributed_matrix();
324 solve = std::make_shared<Solver>(comm, A, prm, bprm);
325 }
326
327 if (comm.rank == 0) {
328 std::cout << "SolverInfo:\n";
329 std::cout << *solve << std::endl;
330 }
331
332 if (prm.get("precond.allow_rebuild", false)) {
333 if (comm.rank == 0) {
334 std::cout << "Rebuilding the preconditioner..." << std::endl;
335 }
336
337 {
338 auto t = prof.scoped_tic("rebuild");
339 A = get_distributed_matrix();
340 solve->precond().rebuild(A);
341 }
342
343 if (comm.rank == 0) {
344 std::cout << *solve << std::endl;
345 }
346 }
347
348#if defined(SOLVER_BACKEND_BUILTIN)
350#elif defined(SOLVER_BACKEND_CUDA)
351 thrust::device_vector<double> x(chunk, 0.0);
352#endif
353
354 prof.tic("solve");
355 Alina::SolverResult r = (*solve)(rhs, x);
356 prof.toc("solve");
357
358 if (comm.rank == 0) {
359 std::cout << "Iterations: " << r.nbIteration() << std::endl
360 << "Error: " << r.residual() << std::endl
361 << prof << std::endl;
362 }
363}
364
365//---------------------------------------------------------------------------
366int main(int argc, char* argv[])
367{
368 auto& prof = Alina::Profiler::globalProfiler();
369
370 Alina::mpi_init_thread mpi(&argc, &argv);
371 Alina::mpi_communicator comm(MPI_COMM_WORLD);
372
373 if (comm.rank == 0)
374 std::cout << "World size: " << comm.size << std::endl;
375
376 // Read configuration from command line
377 namespace po = Arcane::ProgramOptions;
378 po::options_description desc("Options");
379
380 desc.add_options()("help,h", "show help");
381 desc.add_options()("matrix,A",
382 po::value<std::string>(),
383 "System matrix in the MatrixMarket format. "
384 "When not specified, a Poisson problem in 3D unit cube is assembled. ");
385 desc.add_options()("rhs,f",
386 po::value<std::string>()->default_value(""),
387 "The RHS vector in the MatrixMarket format. "
388 "When omitted, a vector of ones is used by default. "
389 "Should only be provided together with a system matrix. ");
390 desc.add_options()("Ap",
391 po::value<std::vector<std::string>>()->multitoken(),
392 "Pre-partitioned matrix (single file per MPI process)");
393 desc.add_options()("fp",
394 po::value<std::vector<std::string>>()->multitoken(),
395 "Pre-partitioned RHS (single file per MPI process)");
396 desc.add_options()("binary,B",
397 po::bool_switch()->default_value(false),
398 "When specified, treat input files as binary instead of as MatrixMarket. "
399 "It is assumed the files were converted to binary format with mm2bin utility. ");
400 desc.add_options()("block-size,b",
401 po::value<int>()->default_value(1),
402 "The block size of the system matrix. "
403 "When specified, the system matrix is assumed to have block-wise structure. "
404 "This usually is the case for problems in elasticity, structural mechanics, "
405 "for coupled systems of PDE (such as Navier-Stokes equations), etc. ");
406 desc.add_options()("partitioner,r",
407 po::value<Alina::eMatrixPartitionerType>()->default_value(
408#if defined(ARCCORE_ALINA_HAVE_PARMETIS)
409 Alina::eMatrixPartitionerType::parmetis
410#else
411 Alina::eMatrixPartitionerType::merge
412#endif
413 ),
414 "Repartition the system matrix");
415 desc.add_options()("size,n",
416 po::value<ptrdiff_t>()->default_value(128),
417 "domain size");
418 desc.add_options()("prm-file,P",
419 po::value<std::string>(),
420 "Parameter file in json format. ");
421 desc.add_options()("prm,p",
422 po::value<std::vector<std::string>>()->multitoken(),
423 "Parameters specified as name=value pairs. "
424 "May be provided multiple times. Examples:\n"
425 " -p solver.tol=1e-3\n"
426 " -p precond.coarse_enough=300");
427 desc.add_options()("test-rebuild",
428 po::bool_switch()->default_value(false),
429 "When specified, try to rebuild the solver before solving. ");
430
432 p.add("prm", -1);
433
435 po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
436 po::notify(vm);
437
438 if (vm.count("help")) {
439 if (comm.rank == 0)
440 std::cout << desc << std::endl;
441 return 0;
442 }
443
445 if (vm.count("prm-file")) {
446 prm.read_json(vm["prm-file"].as<std::string>());
447 }
448
449 if (vm.count("prm")) {
450 for (const std::string& v : vm["prm"].as<std::vector<std::string>>()) {
451 prm.putKeyValue(v);
452 }
453 }
454
455 ptrdiff_t n;
456 std::vector<ptrdiff_t> ptr;
457 std::vector<ptrdiff_t> col;
458 std::vector<double> val;
459 std::vector<double> rhs;
460
461 int block_size = vm["block-size"].as<int>();
462 int aggr_block = prm.get("precond.coarsening.aggr.block_size", 1);
463
464 bool binary = vm["binary"].as<bool>();
465 Alina::eMatrixPartitionerType ptype = vm["partitioner"].as<Alina::eMatrixPartitionerType>();
466
467 if (vm.count("matrix")) {
468 prof.tic("read");
469 if (binary) {
470 n = read_binary(comm,
471 vm["matrix"].as<std::string>(),
472 vm["rhs"].as<std::string>(),
473 block_size * aggr_block, ptr, col, val, rhs);
474 }
475 else {
476 n = read_matrix_market(comm,
477 vm["matrix"].as<std::string>(),
478 vm["rhs"].as<std::string>(),
479 block_size * aggr_block, ptr, col, val, rhs);
480 }
481 prof.toc("read");
482 }
483 else if (vm.count("Ap")) {
484 prof.tic("read");
485 ptype = Alina::eMatrixPartitionerType::merge;
486
487 std::vector<std::string> Aparts = vm["Ap"].as<std::vector<std::string>>();
488 comm.check(Aparts.size() == static_cast<size_t>(comm.size),
489 "--Ap should have single entry per MPI process");
490
491 if (binary) {
492 Alina::IO::read_crs(Aparts[comm.rank], n, ptr, col, val);
493 }
494 else {
495 ptrdiff_t m;
496 std::tie(n, m) = Alina::IO::mm_reader(Aparts[comm.rank])(ptr, col, val);
497 }
498
499 if (vm.count("fp")) {
500 std::vector<std::string> fparts = vm["fp"].as<std::vector<std::string>>();
501 comm.check(fparts.size() == static_cast<size_t>(comm.size),
502 "--fp should have single entry per MPI process");
503
504 ptrdiff_t rows;
505 ptrdiff_t cols;
506
507 if (binary) {
508 Alina::IO::read_dense(fparts[comm.rank], rows, cols, rhs);
509 }
510 else {
511 std::tie(rows, cols) = Alina::IO::mm_reader(fparts[comm.rank])(rhs);
512 }
513
514 comm.check(rhs.size() == static_cast<size_t>(n), "Wrong RHS size");
515 }
516 else {
517 rhs.resize(n, 1);
518 }
519 prof.toc("read");
520 }
521 else {
522 prof.tic("assemble");
523 n = sample_problem_distributed(comm.rank, comm.size,
524 vm["size"].as<ptrdiff_t>(),
525 block_size * aggr_block, ptr, col, val, rhs);
526 prof.toc("assemble");
527 }
528
529 if (vm["test-rebuild"].as<bool>()) {
530 prm.put("precond.allow_rebuild", true);
531 }
532
533 switch (block_size) {
534
535#if defined(SOLVER_BACKEND_BUILTIN)
536#define ARCCORE_ALINA_CALL_BLOCK_SOLVER(z, data, B) \
537 case B: \
538 solve_block<B>(comm, n, ptr, col, val, prm, rhs, ptype); \
539 break;
540
541 BOOST_PP_SEQ_FOR_EACH(ARCCORE_ALINA_CALL_BLOCK_SOLVER, ~, ARCCORE_ALINA_BLOCK_SIZES)
542
543#undef ARCCORE_ALINA_CALL_BLOCK_SOLVER
544#endif
545
546 case 1:
547 solve_scalar(comm, n, ptr, col, val, prm, rhs, ptype);
548 break;
549 default:
550 if (comm.rank == 0)
551 std::cout << "Unsupported block size!" << std::endl;
552 }
553}
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:112
Result of a solution.
Definition AlinaUtils.h:53
NUMA-aware vector container.
Definition NumaVector.h:42
Matrix class, to be used by user.
Fluent command-line parser builder.
Describes a set of command-line options.
Describes positional (non-option) arguments.
Stores parsed option values.
__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 --
Runtime-configurable wrapper around matrix partitioner.
Convenience wrapper around MPI_Comm.
void check(const Condition &cond, const Message &message)
Communicator-wise condition checking.
Convenience wrapper around MPI_Init_threads/MPI_Finalize.