Arcane  4.2.3.0
Developer documentation
Loading...
Searching...
No Matches
CPRDynamicRowSumPreconditioner.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/* CPRDynamicRowSumPreconditioner.h (C) 2000-2026 */
9/* */
10/* CPR preconditioner with Dynamic Row Sum modification. */
11/*---------------------------------------------------------------------------*/
12#ifndef ARCCORE_ALINA_CPRDYNAMICROWSUMPRECONDITIONER_H
13#define ARCCORE_ALINA_CPRDYNAMICROWSUMPRECONDITIONER_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 <vector>
27#include <algorithm>
28
29#include <memory>
30
31#include "arccore/alina/BuiltinBackend.h"
32#include "arccore/alina/AlinaUtils.h"
33
34/*---------------------------------------------------------------------------*/
35/*---------------------------------------------------------------------------*/
36
37namespace Arcane::Alina::preconditioner
38{
39
40/*---------------------------------------------------------------------------*/
41/*---------------------------------------------------------------------------*/
45template <class PPrecond, class SPrecond>
46class CPRDynamicRowSumPreconditioner
47{
49 "Pressure backend should have scalar value type!");
50
51 static_assert(backend::backends_compatible<
52 typename PPrecond::backend_type,
53 typename SPrecond::backend_type>::value,
54 "Backends for pressure and flow preconditioners should coincide!");
55
56 public:
57
58 typedef typename SPrecond::backend_type backend_type;
59 typedef typename PPrecond::backend_type backend_type_p;
60
61 typedef typename backend_type::value_type value_type;
62 typedef typename math::scalar_of<value_type>::type scalar_type;
63 typedef typename backend_type::matrix matrix;
64 typedef typename backend_type::vector vector;
65 typedef typename backend_type_p::value_type value_type_p;
66 typedef typename backend_type_p::matrix matrix_p;
67 typedef typename backend_type_p::vector vector_p;
68
69 typedef typename backend_type::params backend_params;
70
71 typedef typename BuiltinBackend<value_type>::matrix build_matrix;
72 typedef typename BuiltinBackend<value_type_p>::matrix build_matrix_p;
73
74 struct params
75 {
76 typedef typename PPrecond::params pprecond_params;
77 typedef typename SPrecond::params sprecond_params;
78
79 pprecond_params pprecond;
80 sprecond_params sprecond;
81
83 Int32 active_rows = 0;
84 double eps_dd = 0.2;
85 double eps_ps = 0.02;
86
87 std::vector<double> weights;
88
89 params() = default;
90
91 params(const PropertyTree& p)
92 : ARCCORE_ALINA_PARAMS_IMPORT_CHILD(p, pprecond)
93 , ARCCORE_ALINA_PARAMS_IMPORT_CHILD(p, sprecond)
94 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, block_size)
95 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, active_rows)
96 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, eps_dd)
97 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, eps_ps)
98 {
99 void* ptr = 0;
100 size_t n = 0;
101
102 ptr = p.get("weights", ptr);
103 n = p.get("weights_size", n);
104
105 if (ptr) {
106 precondition(n > 0,
107 "Error in cpr_wdrs parameters: "
108 "weights is set, but weights_size is not");
109
110 weights.assign(
111 static_cast<double*>(ptr),
112 static_cast<double*>(ptr) + n);
113 }
114
115 p.check_params({ "pprecond", "sprecond", "block_size", "active_rows", "eps_dd", "eps_ps", "weights", "weights_size" });
116 }
117
118 void get(PropertyTree& p, const std::string& path = "") const
119 {
120 ARCCORE_ALINA_PARAMS_EXPORT_CHILD(p, path, pprecond);
121 ARCCORE_ALINA_PARAMS_EXPORT_CHILD(p, path, sprecond);
122 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, block_size);
123 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, active_rows);
124 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, eps_dd);
125 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, eps_ps);
126 }
127 };
128
129 template <class Matrix>
130 CPRDynamicRowSumPreconditioner(const AlinaContext& context,
131 const Matrix& K,
132 const params& prm = params(),
133 const backend_params& bprm = backend_params())
134 : prm(prm)
135 , n(backend::nbRow(K))
136 {
137 init(context, std::make_shared<build_matrix>(K), bprm,
138 std::integral_constant<bool, math::static_rows<value_type>::value == 1>());
139 }
140
142 std::shared_ptr<build_matrix> K,
143 const params& prm = params(),
144 const backend_params& bprm = backend_params())
145 : prm(prm)
146 , n(backend::nbRow(*K))
147 {
148 init(context, K, bprm,
149 std::integral_constant<bool, math::static_rows<value_type>::value == 1>());
150 }
151
152 template <class Vec1, class Vec2>
153 void apply(const Vec1& rhs, Vec2&& x) const
154 {
155 const auto one = math::identity<scalar_type>();
156 const auto zero = math::zero<scalar_type>();
157
158 ARCCORE_ALINA_TIC("sprecond");
159 S->apply(rhs, x);
160 ARCCORE_ALINA_TOC("sprecond");
161 backend::residual(rhs, S->system_matrix(), x, *rs);
162
163 backend::spmv(one, *Fpp, *rs, zero, *rp);
164 ARCCORE_ALINA_TIC("pprecond");
165 P->apply(*rp, *xp);
166 ARCCORE_ALINA_TOC("pprecond");
167
168 backend::spmv(one, *Scatter, *xp, one, x);
169 }
170
171 std::shared_ptr<matrix> system_matrix_ptr() const
172 {
173 return S->system_matrix_ptr();
174 }
175
176 const matrix& system_matrix() const
177 {
178 return S->system_matrix();
179 }
180
181 /*
182 * Perform a partial update of the CPR preconditioner. This function
183 * leaves the AMG hierarchy intact, but updates the global preconditioner
184 * SPrecond and optionally also the transfer operator Fpp.
185 */
186 template <class Matrix>
187 void partial_update(const Matrix& K,
188 bool update_transfer_ops = true,
189 const backend_params& bprm = backend_params())
190 {
191 auto K_ptr = std::make_shared<build_matrix>(K);
192 // Update global preconditioner
193 S = std::make_shared<SPrecond>(K_ptr, prm.sprecond, bprm);
194 if (update_transfer_ops) {
195 // Update transfer operator Fpp
196 update_transfer(
197 K_ptr,
198 bprm,
199 std::integral_constant<bool, math::static_rows<value_type>::value == 1>());
200 }
201 }
202
203 params prm;
204
205 private:
206
207 size_t n, np;
208
209 std::shared_ptr<PPrecond> P;
210 std::shared_ptr<SPrecond> S;
211
212 std::shared_ptr<matrix_p> Fpp, Scatter;
213 std::shared_ptr<vector> rs;
214 std::shared_ptr<vector_p> rp, xp;
215
216 // Returns pressure transfer operator fpp and (optionally)
217 // partially constructed pressure system matrix App.
218 std::tuple<std::shared_ptr<build_matrix_p>, std::shared_ptr<build_matrix_p>>
219 first_scalar_pass(std::shared_ptr<build_matrix> K, bool get_app = true)
220 {
221 typedef typename backend::row_iterator<build_matrix>::type row_iterator;
222 const int B = prm.block_size;
223 const ptrdiff_t N = (prm.active_rows ? prm.active_rows : n);
224
225 np = N / B;
226
227 auto fpp = std::make_shared<build_matrix_p>();
228 fpp->set_size(np, n);
229 fpp->set_nonzeros(n);
230 fpp->ptr[0] = 0;
231
232 std::shared_ptr<build_matrix_p> App;
233 if (get_app) {
234 App = std::make_shared<build_matrix_p>();
235 App->set_size(np, np, true);
236 }
237
238 arccoreParallelFor(0, np, ForLoopRunInfo{}, [&](Int32 begin_ip, Int32 ip_size) {
239 std::vector<value_type> a_dia(B), a_off(B), a_top(B);
240 std::vector<row_iterator> k;
241 k.reserve(B);
242
243 for (Int32 ip = begin_ip; ip < (begin_ip + ip_size); ++ip) {
244 Int32 ik = ip * B;
245 bool done = true;
246 ptrdiff_t cur_col = 0;
247
248 std::fill(a_dia.begin(), a_dia.end(), 0);
249 std::fill(a_off.begin(), a_off.end(), 0);
250 std::fill(a_top.begin(), a_top.end(), 0);
251
252 k.clear();
253 for (int i = 0; i < B; ++i) {
254 k.push_back(backend::row_begin(*K, ik + i));
255
256 if (k.back() && k.back().col() < N) {
257 ptrdiff_t col = k.back().col() / B;
258 if (done) {
259 cur_col = col;
260 done = false;
261 }
262 else {
263 cur_col = std::min(cur_col, col);
264 }
265 }
266 }
267
268 while (!done) {
269 if (get_app)
270 ++App->ptr[ip + 1];
271
272 ptrdiff_t end = (cur_col + 1) * B;
273
274 for (int i = 0; i < B; ++i) {
275 for (; k[i] && k[i].col() < end; ++k[i]) {
276 ptrdiff_t c = k[i].col() % B;
277 value_type v = k[i].value();
278
279 if (i == 0) {
280 a_top[c] += std::abs(v);
281 }
282
283 if (c == 0) {
284 if (cur_col == ip) {
285 a_dia[i] = v;
286 }
287 else {
288 a_off[i] += std::abs(v);
289 }
290 }
291 }
292 }
293
294 // Get next column number.
295 done = true;
296 for (int i = 0; i < B; ++i) {
297 if (k[i] && k[i].col() < N) {
298 ptrdiff_t col = k[i].col() / B;
299 if (done) {
300 cur_col = col;
301 done = false;
302 }
303 else {
304 cur_col = std::min(cur_col, col);
305 }
306 }
307 }
308 }
309
310 for (int i = 0; i < B; ++i) {
311 fpp->col[ik + i] = ik + i;
312 double delta = 1;
313
314 if (!prm.weights.empty())
315 delta *= prm.weights[ik + i];
316
317 if (i > 0) {
318 if (a_dia[i] < prm.eps_dd * a_off[i])
319 delta = 0;
320
321 if (a_top[i] < prm.eps_ps * std::abs(a_dia[0]))
322 delta = 0;
323 }
324
325 fpp->val[ik + i] = delta;
326 }
327
328 fpp->ptr[ip + 1] = ik + B;
329 }
330 });
331
332 App->set_nonzeros(App->scan_row_sizes());
333
334 return std::make_tuple(fpp, App);
335 }
336
337 void init(const AlinaContext& context, std::shared_ptr<build_matrix> K, const backend_params bprm, std::true_type)
338 {
339 typedef typename backend::row_iterator<build_matrix>::type row_iterator;
340 const int B = prm.block_size;
341 const ptrdiff_t N = (prm.active_rows ? prm.active_rows : n);
342
343 precondition(prm.weights.empty() || prm.weights.size() == static_cast<size_t>(N),
344 "CPR: weights size is not equal to number of active rows.");
345
346 np = N / B;
347
348 std::shared_ptr<build_matrix_p> fpp, App;
349 std::tie(fpp, App) = first_scalar_pass(K);
350
351 auto scatter = std::make_shared<build_matrix_p>();
352 scatter->set_size(n, np);
353 scatter->set_nonzeros(np);
354 scatter->ptr[0] = 0;
355
356 arccoreParallelFor(0, np, ForLoopRunInfo{}, [&](Int32 begin_ip, Int32 ip_size) {
357 std::vector<row_iterator> k;
358 k.reserve(B);
359
360 for (ptrdiff_t ip = begin_ip; ip < (begin_ip + ip_size); ++ip) {
361 ptrdiff_t ik = ip * B;
362 ptrdiff_t head = App->ptr[ip];
363 bool done = true;
364 ptrdiff_t cur_col = 0;
365
366 value_type_p* d = &fpp->val[ik];
367
368 k.clear();
369 for (int i = 0; i < B; ++i) {
370 k.push_back(backend::row_begin(*K, ik + i));
371
372 if (k.back() && k.back().col() < N) {
373 ptrdiff_t col = k.back().col() / B;
374 if (done) {
375 cur_col = col;
376 done = false;
377 }
378 else {
379 cur_col = std::min(cur_col, col);
380 }
381 }
382 }
383
384 while (!done) {
385 ptrdiff_t end = (cur_col + 1) * B;
386 value_type_p app = 0;
387
388 for (int i = 0; i < B; ++i) {
389 for (; k[i] && k[i].col() < end; ++k[i]) {
390 if (k[i].col() % B == 0) {
391 app += d[i] * k[i].value();
392 }
393 }
394 }
395
396 App->col[head] = cur_col;
397 App->val[head] = app;
398 ++head;
399
400 // Get next column number.
401 done = true;
402 for (int i = 0; i < B; ++i) {
403 if (k[i] && k[i].col() < N) {
404 ptrdiff_t col = k[i].col() / B;
405 if (done) {
406 cur_col = col;
407 done = false;
408 }
409 else {
410 cur_col = std::min(cur_col, col);
411 }
412 }
413 }
414 }
415
416 scatter->col[ip] = ip;
417 scatter->val[ip] = math::identity<value_type>();
418
419 ptrdiff_t nnz = ip;
420 for (int i = 0; i < B; ++i) {
421 if (i == 0)
422 ++nnz;
423 scatter->ptr[ik + i + 1] = nnz;
424 }
425 }
426 });
427
428 for (size_t i = N; i < n; ++i)
429 scatter->ptr[i + 1] = scatter->ptr[i];
430
431 ARCCORE_ALINA_TIC("pprecond");
432 P = std::make_shared<PPrecond>(context, App, prm.pprecond, bprm);
433 ARCCORE_ALINA_TOC("pprecond");
434 ARCCORE_ALINA_TIC("sprecond");
435 S = std::make_shared<SPrecond>(context, K, prm.sprecond, bprm);
436 ARCCORE_ALINA_TOC("sprecond");
437
438 Fpp = backend_type_p::copy_matrix(fpp, bprm);
439 Scatter = backend_type_p::copy_matrix(scatter, bprm);
440
441 rp = backend_type_p::create_vector(np, bprm);
442 xp = backend_type_p::create_vector(np, bprm);
443 rs = backend_type::create_vector(n, bprm);
444 }
445
446 void update_transfer(std::shared_ptr<build_matrix> K, const backend_params bprm, std::true_type)
447 {
448 auto fpp = std::get<0>(first_scalar_pass(K, /*get_app*/ false));
449 Fpp = backend_type_p::copy_matrix(fpp, bprm);
450 }
451
452 void init(const AlinaContext& context, std::shared_ptr<build_matrix> K,
453 const backend_params bprm, std::false_type)
454 {
455 const int B = math::static_rows<value_type>::value;
456 const ptrdiff_t N = (prm.active_rows ? prm.active_rows : n);
457
458 precondition(
459 prm.weights.empty() || prm.weights.size() == static_cast<size_t>(N * B),
460 "CPR: weights size is not equal to number of active rows.");
461
462 np = N;
463
464 auto fpp = std::make_shared<build_matrix_p>();
465 fpp->set_size(np, np * B);
466 fpp->set_nonzeros(np * B);
467 fpp->ptr[0] = 0;
468
469 auto scatter = std::make_shared<build_matrix_p>();
470 scatter->set_size(np * B, np);
471 scatter->set_nonzeros(np);
472 scatter->ptr[0] = 0;
473
474 auto App = std::make_shared<build_matrix_p>();
475 App->set_size(np, np, true);
476 App->set_nonzeros(K->nbNonZero());
477 App->ptr[0] = 0;
478
479 arccoreParallelFor(0, np, ForLoopRunInfo{}, [&](Int32 begin_ip, Int32 ip_size) {
480 for (ptrdiff_t i = begin_ip; i < (begin_ip + ip_size); ++i) {
481 ptrdiff_t ik = i * B;
482 for (int k = 0; k < B; ++k, ++ik) {
483 fpp->col[ik] = ik;
484 scatter->ptr[ik + 1] = i + 1;
485 }
486 fpp->ptr[i + 1] = ik;
487 scatter->col[i] = i;
488 scatter->val[i] = math::identity<value_type_p>();
489
490 ptrdiff_t row_beg = K->ptr[i];
491 ptrdiff_t row_end = K->ptr[i + 1];
492 App->ptr[i + 1] = row_end;
493
494 value_type_p* d = &fpp->val[i * B];
495 const double* w = prm.weights.empty() ? nullptr : &prm.weights[i * B];
496
497 std::array<value_type_p, B> a_dia{};
498 std::array<value_type_p, B> a_off{};
499 std::array<value_type_p, B> a_top{};
500
501 for (ptrdiff_t j = row_beg; j < row_end; ++j) {
502 ptrdiff_t c = K->col[j];
503 value_type v = K->val[j];
504
505 for (int k = 0; k < B; ++k) {
506 a_top[k] += std::abs(v(0, k));
507 if (c == i) {
508 a_dia[k] = v(k, 0);
509 }
510 else {
511 a_off[k] += std::abs(v(k, 0));
512 }
513 }
514 }
515
516 for (int k = 0; k < B; ++k) {
517 if (k > 0 &&
518 (a_dia[k] < prm.eps_dd * a_off[k] ||
519 a_top[k] < prm.eps_ps * std::abs(a_dia[0]))) {
520 d[k] = 0;
521 }
522 else {
523 d[k] = w ? w[k] : 1.0;
524 }
525 }
526
527 for (ptrdiff_t j = row_beg; j < row_end; ++j) {
528 App->col[j] = K->col[j];
529
530 value_type_p app = 0;
531 for (int k = 0; k < B; ++k)
532 app += d[k] * K->val[j](k, 0);
533
534 App->val[j] = app;
535 }
536 }
537 });
538
539 ARCCORE_ALINA_TIC("pprecond");
540 P = std::make_shared<PPrecond>(context, App, prm.pprecond, bprm);
541 ARCCORE_ALINA_TOC("pprecond");
542 ARCCORE_ALINA_TIC("sprecond");
543 S = std::make_shared<SPrecond>(context, K, prm.sprecond, bprm);
544 ARCCORE_ALINA_TOC("sprecond");
545
546 Fpp = backend_type_p::copy_matrix(fpp, bprm);
547 Scatter = backend_type_p::copy_matrix(scatter, bprm);
548
549 rp = backend_type_p::create_vector(np, bprm);
550 xp = backend_type_p::create_vector(np, bprm);
551 rs = backend_type::create_vector(n, bprm);
552 }
553
554 void update_transfer(std::shared_ptr<build_matrix> K, const backend_params bprm, std::false_type)
555 {
556 const int B = math::static_rows<value_type>::value;
557 const ptrdiff_t N = (prm.active_rows ? prm.active_rows : n);
558
559 precondition(prm.weights.empty() || prm.weights.size() == static_cast<size_t>(N * B),
560 "CPR: weights size is not equal to number of active rows.");
561
562 np = N;
563
564 auto fpp = std::make_shared<build_matrix_p>();
565 fpp->set_size(np, np * B);
566 fpp->set_nonzeros(np * B);
567 fpp->ptr[0] = 0;
568
569 arccoreParallelFor(0, np, ForLoopRunInfo{}, [&](Int32 begin_ip, Int32 ip_size) {
570 for (ptrdiff_t i = begin_ip; i < (begin_ip + ip_size); ++i) {
571
572 ptrdiff_t ik = i * B;
573 for (int k = 0; k < B; ++k, ++ik) {
574 fpp->col[ik] = ik;
575 }
576 fpp->ptr[i + 1] = ik;
577
578 ptrdiff_t row_beg = K->ptr[i];
579 ptrdiff_t row_end = K->ptr[i + 1];
580
581 value_type_p* d = &fpp->val[i * B];
582 const double* w = prm.weights.empty() ? nullptr : &prm.weights[i * B];
583
584 std::array<value_type_p, B> a_dia{};
585 std::array<value_type_p, B> a_off{};
586 std::array<value_type_p, B> a_top{};
587
588 for (ptrdiff_t j = row_beg; j < row_end; ++j) {
589 ptrdiff_t c = K->col[j];
590 value_type v = K->val[j];
591
592 for (int k = 0; k < B; ++k) {
593 a_top[k] += std::abs(v(0, k));
594 if (c == i) {
595 a_dia[k] = v(k, 0);
596 }
597 else {
598 a_off[k] += std::abs(v(k, 0));
599 }
600 }
601 }
602
603 for (int k = 0; k < B; ++k) {
604 if (k > 0 &&
605 (a_dia[k] < prm.eps_dd * a_off[k] ||
606 a_top[k] < prm.eps_ps * std::abs(a_dia[0]))) {
607 d[k] = 0;
608 }
609 else {
610 d[k] = w ? w[k] : 1.0;
611 }
612 }
613 }
614 Fpp = backend_type_p::copy_matrix(fpp, bprm);
615 });
616 }
617
618 friend std::ostream& operator<<(std::ostream& os, const CPRDynamicRowSumPreconditioner& p)
619 {
620 os << "CPR_DRS (two-stage preconditioner)\n"
621 "### Pressure preconditioner:\n"
622 << *p.P << "\n"
623 "### Global preconditioner:\n"
624 << *p.S << std::endl;
625 return os;
626 }
627};
628
629/*---------------------------------------------------------------------------*/
630/*---------------------------------------------------------------------------*/
631
632} // namespace Arcane::Alina::preconditioner
633
634/*---------------------------------------------------------------------------*/
635/*---------------------------------------------------------------------------*/
636
637#endif
Class to store parameters as a hierarchical key/value tree.
Definition AlinaUtils.h:113
CPR preconditioner with Dynamic Row Sum modification.
Matrix class, to be used by user.
void arccoreParallelFor(const ComplexForLoopRanges< RankValue, IndexType_ > &loop_ranges, const ForLoopRunInfo &run_info, const LambdaType &lambda_function, const ReducerArgs &... reducer_args)
Applies the lambda function lambda_function concurrently over the iteration interval given by loop_ra...
Definition ParallelFor.h:86
std::int32_t Int32
Signed integer type of 32 bits.
Metafunction that checks if two backends are compatible.
Number of rows for statically sized matrix types.