Arcane  4.2.3.0
Developer documentation
Loading...
Searching...
No Matches
IDRSSolver.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/* IDDRSolver.h (C) 2000-2026 */
9/* */
10/* IDR(s) (Induced Dimension Reduction) method. */
11/*---------------------------------------------------------------------------*/
12#ifndef ARCCORE_ALINA_IDRSSOLVER_H
13#define ARCCORE_ALINA_IDRSSOLVER_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 * The code is ported from Matlab code published at
27 * http://ta.twi.tudelft.nl/nw/users/gijzen/IDR.html.
28 *
29 * This is a very stable and efficient IDR(s) variant (implemented in the MATLAB
30 * code idrs.m given above) as described in: Martin B. van Gijzen and Peter
31 * Sonneveld, Algorithm 913: An Elegant IDR(s) Variant that Efficiently Exploits
32 * Bi-orthogonality Properties. ACM Transactions on Mathematical Software, Vol.
33 * 38, No. 1, pp. 5:1-5:19, 2011 (copyright ACM).
34 */
35/*---------------------------------------------------------------------------*/
36/*---------------------------------------------------------------------------*/
37
38#include "arccore/alina/SolverUtils.h"
39#include "arccore/alina/AlinaUtils.h"
40
41#include <random>
42
43/*---------------------------------------------------------------------------*/
44/*---------------------------------------------------------------------------*/
45
46namespace Arcane::Alina
47{
48
49/*---------------------------------------------------------------------------*/
50/*---------------------------------------------------------------------------*/
54struct IDRSSolverParams
55{
56 using params = IDRSSolverParams;
57
59 Int32 s = 4;
60
69 double omega = 0.7;
70
72 bool smoothing = false;
73
82 bool replacement = false;
83
86
88 double tol = 1e-8;
89
91 double abstol = std::numeric_limits<double>::min();
92
98 bool ns_search = false;
99
101 bool verbose = false;
102
103 IDRSSolverParams() = default;
104
105 IDRSSolverParams(const PropertyTree& p)
106 : ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, s)
107 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, omega)
108 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, smoothing)
109 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, replacement)
110 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, maxiter)
111 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, tol)
112 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, abstol)
113 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, ns_search)
114 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, verbose)
115 {
116 p.check_params({ "s", "omega", "smoothing", "replacement", "maxiter", "tol", "abstol", "ns_search", "verbose" });
117 }
118
119 void get(PropertyTree& p, const std::string& path) const
120 {
121 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, s);
122 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, omega);
123 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, smoothing);
124 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, replacement);
125 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, maxiter);
126 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, tol);
127 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, abstol);
128 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, ns_search);
129 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, verbose);
130 }
131};
132
133/*---------------------------------------------------------------------------*/
134/*---------------------------------------------------------------------------*/
135
137template <class Backend, class InnerProduct = detail::default_inner_product>
139: public SolverBase
140{
141 public:
142
143 using backend_type = Backend;
144 using BackendType = Backend;
145
146 typedef typename Backend::vector vector;
147 typedef typename Backend::value_type value_type;
148 typedef typename Backend::params backend_params;
149
150 typedef typename math::scalar_of<value_type>::type scalar_type;
151 typedef typename math::rhs_of<value_type>::type rhs_type;
152
153 typedef typename math::inner_product_impl<typename math::rhs_of<value_type>::type>::return_type coef_type;
154
155 using params = IDRSSolverParams;
156
158 IDRSSolver(const AlinaContext& context, size_t n,
159 const params& prm = params(),
160 const backend_params& bprm = backend_params(),
161 const InnerProduct& inner_product = InnerProduct())
162 : SolverBase(context)
163 , prm(prm)
164 , n(n)
165 , inner_product(inner_product)
166 , M(prm.s, prm.s)
167 , f(prm.s)
168 , c(prm.s)
169 , r(Backend::create_vector(n, bprm))
170 , v(Backend::create_vector(n, bprm))
171 , t(Backend::create_vector(n, bprm))
172 {
173 static const scalar_type one = math::identity<scalar_type>();
174 static const scalar_type zero = math::zero<scalar_type>();
175
176 if (prm.smoothing) {
177 x_s = Backend::create_vector(n, bprm);
178 r_s = Backend::create_vector(n, bprm);
179 }
180
181 G.reserve(prm.s);
182 U.reserve(prm.s);
183 for (Int32 i = 0; i < prm.s; ++i) {
184 G.push_back(Backend::create_vector(n, bprm));
185 U.push_back(Backend::create_vector(n, bprm));
186 }
187
188 // Initialize P.
189 P.reserve(prm.s);
190 {
191 std::vector<rhs_type> p(n);
192
193 int pid = inner_product.rank();
194 const int nt = 1;
195
196 const int tid = 0;
197 std::mt19937 rng(pid * nt + tid);
198 std::uniform_real_distribution<scalar_type> rnd(-1, 1);
199
200 for (unsigned j = 0; j < prm.s; ++j) {
201 for (ptrdiff_t i = 0; i < n; ++i) {
202 p[i] = math::constant<rhs_type>(rnd(rng));
203 }
204 P.push_back(Backend::copy_vector(p, bprm));
205 }
206
207 for (unsigned j = 0; j < prm.s; ++j) {
208 for (unsigned k = 0; k < j; ++k) {
209 coef_type alpha = inner_product(*P[k], *P[j]);
210 backend::axpby(-alpha, *P[k], one, *P[j]);
211 }
212 scalar_type norm_pj = norm(*P[j]);
213 backend::axpby(math::inverse(norm_pj), *P[j], zero, *P[j]);
214 }
215 }
216 }
217
233 template <class Matrix, class Precond, class Vec1, class Vec2>
234 SolverResult operator()(Matrix const& A, Precond const& Prec, Vec1 const& rhs, Vec2& x) const
235 {
236 static const scalar_type one = math::identity<scalar_type>();
237 static const scalar_type zero = math::zero<scalar_type>();
238
239 ScopedStreamModifier ss(std::cout);
240
241 scalar_type norm_rhs = norm(rhs);
242 if (norm_rhs < Alina::detail::eps<scalar_type>(1)) {
243 if (prm.ns_search) {
244 norm_rhs = math::identity<scalar_type>();
245 }
246 else {
247 backend::clear(x);
248 return SolverResult(0, norm_rhs);
249 }
250 }
251
252 scalar_type eps = std::max(prm.tol * norm_rhs, prm.abstol);
253
254 // Compute initial residual:
255 backend::residual(rhs, A, x, *r);
256
257 scalar_type res_norm = norm(*r);
258 if (res_norm <= eps) {
259 // Initial guess is a good enough solution.
260 return SolverResult(0, res_norm / norm_rhs);
261 }
262
263 if (prm.smoothing) {
264 backend::copy(x, *x_s);
265 backend::copy(*r, *r_s);
266 }
267
268 // Initialization.
269 coef_type om = math::identity<coef_type>();
270
271 for (unsigned i = 0; i < prm.s; ++i) {
272 backend::clear(*G[i]);
273 backend::clear(*U[i]);
274
275 for (unsigned j = 0; j < prm.s; ++j)
276 M(i, j) = (i == j);
277 }
278
279 // Main iteration loop, build G-spaces:
280 size_t iter = 0;
281 while (iter < prm.maxiter && res_norm > eps) {
282 // New righ-hand size for small system:
283 for (unsigned i = 0; i < prm.s; ++i)
284 f[i] = inner_product(*r, *P[i]);
285
286 for (unsigned k = 0; k < prm.s; ++k) {
287 // Compute new v
288 backend::copy(*r, *v);
289
290 // Solve small system (Note: M is lower triangular)
291 // and make v orthogonal to P:
292 for (unsigned i = k; i < prm.s; ++i) {
293 c[i] = f[i];
294 for (unsigned j = k; j < i; ++j)
295 c[i] -= M(i, j) * c[j];
296 c[i] = math::inverse(M(i, i)) * c[i];
297
298 backend::axpby(-c[i], *G[i], one, *v);
299 }
300
301 Prec.apply(*v, *t);
302
303 // Compute new U[k]
304 backend::axpby(om, *t, c[k], *U[k]);
305 for (unsigned i = k + 1; i < prm.s; ++i)
306 backend::axpby(c[i], *U[i], one, *U[k]);
307
308 // Compute new G[k], G[k] is in space G_j
309 backend::spmv(one, A, *U[k], zero, *G[k]);
310
311 // Bi-Orthogonalise the new basis vectors:
312 for (unsigned i = 0; i < k; ++i) {
313 coef_type alpha = inner_product(*G[k], *P[i]) / M(i, i);
314
315 backend::axpby(-alpha, *G[i], one, *G[k]);
316 backend::axpby(-alpha, *U[i], one, *U[k]);
317 }
318
319 // New column of M = P'*G (first k-1 entries are zero)
320 for (unsigned i = k; i < prm.s; ++i)
321 M(i, k) = inner_product(*G[k], *P[i]);
322
323 precondition(!math::is_zero(M(k, k)), "IDR(s) breakdown: zero M[k,k]");
324
325 // Make r orthogonal to q_i, i = [0..k)
326 coef_type beta = math::inverse(M(k, k)) * f[k];
327 backend::axpby(-beta, *G[k], one, *r);
328 backend::axpby(beta, *U[k], one, x);
329
330 res_norm = norm(*r);
331
332 // Smoothing
333 if (prm.smoothing) {
334 backend::axpbypcz(one, *r_s, -one, *r, zero, *t);
335 coef_type gamma = inner_product(*t, *r_s) / inner_product(*t, *t);
336 backend::axpby(-gamma, *t, one, *r_s);
337 backend::axpbypcz(-gamma, *x_s, gamma, x, one, *x_s);
338 res_norm = norm(*r_s);
339 }
340
341 if (prm.verbose && iter % 5 == 0)
342 std::cout << iter << "\t" << std::scientific << res_norm / norm_rhs << std::endl;
343 if (res_norm <= eps || ++iter >= prm.maxiter)
344 break;
345
346 // New f = P'*r (first k components are zero)
347 for (unsigned i = k + 1; i < prm.s; ++i)
348 f[i] -= beta * M(i, k);
349 }
350
351 if (res_norm <= eps || iter >= prm.maxiter)
352 break;
353
354 // Now we have sufficient vectors in G_j to compute residual in G_j+1
355 // Note: r is already perpendicular to P so v = r
356
357 Prec.apply(*r, *v);
358 backend::spmv(one, A, *v, zero, *t);
359
360 // Computation of a new omega
361 om = omega(*t, *r);
362 precondition(!math::is_zero(om), "IDR(s) breakdown: zero omega");
363
364 backend::axpby(-om, *t, one, *r);
365 backend::axpby(om, *v, one, x);
366
367 if (prm.replacement) {
368 backend::residual(rhs, A, x, *r);
369 }
370 res_norm = norm(*r);
371
372 // Smoothing.
373 if (prm.smoothing) {
374 backend::axpbypcz(one, *r_s, -one, *r, zero, *t);
375 coef_type gamma = inner_product(*t, *r_s) / inner_product(*t, *t);
376 backend::axpby(-gamma, *t, one, *r_s);
377 backend::axpbypcz(-gamma, *x_s, gamma, x, one, *x_s);
378 res_norm = norm(*r_s);
379 }
380
381 ++iter;
382 }
383
384 if (prm.smoothing)
385 backend::copy(*x_s, x);
386
387 return SolverResult(iter, res_norm / norm_rhs);
388 }
389
400 template <class Precond, class Vec1, class Vec2>
401 SolverResult operator()(Precond const& P, Vec1 const& rhs, Vec2& x) const
402 {
403 return (*this)(P.system_matrix(), P, rhs, x);
404 }
405
406 size_t bytes() const
407 {
408 size_t b = 0;
409
410 b += M.size() * sizeof(coef_type);
411
412 b += backend::bytes(f);
413 b += backend::bytes(c);
414
415 b += backend::bytes(*r);
416 b += backend::bytes(*v);
417 b += backend::bytes(*t);
418
419 if (x_s)
420 b += backend::bytes(*x_s);
421 if (r_s)
422 b += backend::bytes(*r_s);
423
424 for (const auto& v : P)
425 b += backend::bytes(*v);
426 for (const auto& v : G)
427 b += backend::bytes(*v);
428 for (const auto& v : U)
429 b += backend::bytes(*v);
430
431 return b;
432 }
433
434 friend std::ostream& operator<<(std::ostream& os, const IDRSSolver& s)
435 {
436 return os << "Type: IDR(" << s.prm.s << ")"
437 << "\nUnknowns: " << s.n
438 << "\nMemory footprint: " << human_readable_memory(s.bytes())
439 << std::endl;
440 }
441
442 public:
443
444 params prm;
445
446 private:
447
448 size_t n;
449
450 InnerProduct inner_product;
451
452 mutable multi_array<coef_type, 2> M;
453 mutable std::vector<coef_type> f, c;
454
455 std::shared_ptr<vector> r, v, t;
456 std::shared_ptr<vector> x_s;
457 std::shared_ptr<vector> r_s;
458
459 std::vector<std::shared_ptr<vector>> P, G, U;
460
461 template <class Vec>
462 scalar_type norm(const Vec& x) const
463 {
464 return std::abs(sqrt(inner_product(x, x)));
465 }
466
467 template <class Vector1, class Vector2>
468 coef_type omega(const Vector1& t, const Vector2& s) const
469 {
470 scalar_type norm_t = norm(t);
471 scalar_type norm_s = norm(s);
472
473 coef_type ts = inner_product(t, s);
474 scalar_type rho = math::norm(ts / (norm_t * norm_s));
475 coef_type om = ts / (norm_t * norm_t);
476
477 if (rho < prm.omega)
478 om *= prm.omega / rho;
479
480 return om;
481 }
482};
483
484/*---------------------------------------------------------------------------*/
485/*---------------------------------------------------------------------------*/
486
487} // namespace Arcane::Alina
488
489/*---------------------------------------------------------------------------*/
490/*---------------------------------------------------------------------------*/
491
492#endif
IDR(s) method (Induced Dimension Reduction).
Definition IDRSSolver.h:140
SolverResult operator()(Matrix const &A, Precond const &Prec, Vec1 const &rhs, Vec2 &x) const
Computes the solution for the given system matrix.
Definition IDRSSolver.h:234
SolverResult operator()(Precond const &P, Vec1 const &rhs, Vec2 &x) const
Computes the solution for the given right-hand side.
Definition IDRSSolver.h:401
size_t bytes() const
Memory used in bytes.
Definition IDRSSolver.h:406
IDRSSolver(const AlinaContext &context, size_t n, const params &prm=params(), const backend_params &bprm=backend_params(), const InnerProduct &inner_product=InnerProduct())
Preallocates necessary data structures for the system of size n.
Definition IDRSSolver.h:158
Class to store parameters as a hierarchical key/value tree.
Definition AlinaUtils.h:113
Save ostream flags in constructor, restore in destructor.
Result of a solution.
Definition AlinaUtils.h:56
Matrix class, to be used by user.
std::int32_t Int32
Signed integer type of 32 bits.
Parameters for IDR(s) solver.
Definition IDRSSolver.h:55
Int32 s
Dimension of the shadow space in IDR(s).
Definition IDRSSolver.h:59
bool ns_search
Ignore the trivial solution x=0 when rhs is zero.
Definition IDRSSolver.h:98
double omega
Computation of omega.
Definition IDRSSolver.h:69
bool verbose
Verbose output (show iterations and error).
Definition IDRSSolver.h:101
double abstol
Target absolute residual error.
Definition IDRSSolver.h:91
Int32 maxiter
Maximum number of iterations.
Definition IDRSSolver.h:85
double tol
Target relative residual error.
Definition IDRSSolver.h:88
bool smoothing
Specifies if residual smoothing must be applied.
Definition IDRSSolver.h:72
bool replacement
Residual replacement.
Definition IDRSSolver.h:82
Default implementation for inner product.