Arcane  4.2.3.0
Developer documentation
Loading...
Searching...
No Matches
LooseGMRESSolver.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/* solver_lgmres.h (C) 2000-2026 */
9/* */
10/* Loose GMRES method solver. */
11/*---------------------------------------------------------------------------*/
12#ifndef ARCCORE_ALINA_LOOSEGMRESSOLVER_H
13#define ARCCORE_ALINA_LOOSEGMRESSOLVER_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 * Ported from scipy lgmres. The original code came with the following license:
27 * \verbatim
28 * Copyright (c) 2001, 2002 Enthought, Inc.
29 * All rights reserved.
30 *
31 * Copyright (c) 2003-2016 SciPy Developers.
32 * All rights reserved.
33 *
34 * Redistribution and use in source and binary forms, with or without
35 * modification, are permitted provided that the following conditions are met:
36 *
37 * a. Redistributions of source code must retain the above copyright notice,
38 * this list of conditions and the following disclaimer.
39 * b. Redistributions in binary form must reproduce the above copyright
40 * notice, this list of conditions and the following disclaimer in the
41 * documentation and/or other materials provided with the distribution.
42 * c. Neither the name of Enthought nor the names of the SciPy Developers
43 * may be used to endorse or promote products derived from this software
44 * without specific prior written permission.
45 *
46 *
47 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
48 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
49 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
50 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
51 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
52 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
53 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
54 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
55 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
56 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
57 * THE POSSIBILITY OF SUCH DAMAGE.
58 * \endverbatim
59 */
60/*---------------------------------------------------------------------------*/
61/*---------------------------------------------------------------------------*/
62
63#include "arccore/alina/SolverUtils.h"
64#include "arccore/alina/SolverBase.h"
65
66/*---------------------------------------------------------------------------*/
67/*---------------------------------------------------------------------------*/
68
69namespace Arcane::Alina
70{
71
72/*---------------------------------------------------------------------------*/
73/*---------------------------------------------------------------------------*/
77struct LooseGMRESSolverParams
78{
79 using params = LooseGMRESSolverParams;
80
82 Int32 M = 30;
83
92 Int32 K = 3;
93
103 bool always_reset = true;
104
106 ePreconditionerSideType pside = ePreconditionerSideType::right;
107
110
112 double tol = 1.0e-8;
113
115 double abstol = std::numeric_limits<double>::min();
116
118 //** Useful for searching for the null-space vectors of the system */
119 bool ns_search = false;
120
122 bool verbose = false;
123
124 LooseGMRESSolverParams() = default;
125
126 LooseGMRESSolverParams(const PropertyTree& p)
127 : ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, M)
128 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, K)
129 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, always_reset)
130 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, pside)
131 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, maxiter)
132 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, tol)
133 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, abstol)
134 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, ns_search)
135 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, verbose)
136 {
137 p.check_params({ "pside", "M", "K", "always_reset", "maxiter", "tol", "abstol", "ns_search", "verbose" });
138 }
139
140 void get(PropertyTree& p, const std::string& path) const
141 {
142 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, M);
143 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, K);
144 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, always_reset);
145 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, pside);
146 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, maxiter);
147 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, tol);
148 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, abstol);
149 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, ns_search);
150 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, verbose);
151 }
152};
153
154/*---------------------------------------------------------------------------*/
155/*---------------------------------------------------------------------------*/
164template <class Backend, class InnerProduct = detail::default_inner_product>
166: public SolverBase
167{
168 public:
169
170 using backend_type = Backend;
171 using BackendType = Backend;
172
173 typedef typename Backend::vector vector;
174 typedef typename Backend::value_type value_type;
175 typedef typename Backend::params backend_params;
176
177 typedef typename math::scalar_of<value_type>::type scalar_type;
178 typedef typename math::rhs_of<value_type>::type rhs_type;
179 typedef typename math::inner_product_impl<rhs_type>::return_type coef_type;
180
181 using params = LooseGMRESSolverParams;
182
184 LooseGMRESSolver(const AlinaContext& context, size_t n,
185 const params& prm = params(),
186 const backend_params& bprm = backend_params(),
187 const InnerProduct& inner_product = InnerProduct())
188 : SolverBase(context)
189 , prm(prm)
190 , n(n)
191 , M(prm.M + prm.K)
192 , H(M + 1, M)
193 , H0(M + 1, M)
194 , s(M + 1)
195 , cs(M + 1)
196 , sn(M + 1)
197 , r(Backend::create_vector(n, bprm))
198 , ws(M)
199 , outer_v(prm.K)
200 , inner_product(inner_product)
201 {
202 outer_v_data.reserve(prm.K);
203 for (unsigned i = 0; i < prm.K; ++i)
204 outer_v_data.push_back(Backend::create_vector(n, bprm));
205
206 vs.reserve(M + 1);
207 for (unsigned i = 0; i <= M; ++i)
208 vs.push_back(Backend::create_vector(n, bprm));
209 }
210
226 template <class Matrix, class Precond, class Vec1, class Vec2>
227 SolverResult operator()(Matrix const& A, Precond const& P, Vec1 const& rhs, Vec2& x) const
228 {
229 static const scalar_type zero = math::zero<scalar_type>();
230 static const scalar_type one = math::identity<scalar_type>();
231
232 ScopedStreamModifier ss(std::cout);
233
234 if (prm.always_reset) {
235 outer_v.clear();
236 }
237
238 scalar_type norm_rhs = norm(rhs);
239 if (norm_rhs < Alina::detail::eps<scalar_type>(1)) {
240 if (prm.ns_search) {
241 norm_rhs = math::identity<scalar_type>();
242 }
243 else {
244 backend::clear(x);
245 return SolverResult(0, norm_rhs);
246 }
247 }
248
249 scalar_type norm_r = zero;
250 scalar_type eps = std::max(prm.tol * norm_rhs, prm.abstol);
251
252 Int32 iter = 0;
253 unsigned n_outer = 0;
254 while (true) {
255 if (prm.pside == ePreconditionerSideType::left) {
256 backend::residual(rhs, A, x, *vs[0]);
257 P.apply(*vs[0], *r);
258 }
259 else {
260 backend::residual(rhs, A, x, *r);
261 }
262
263 // -- Check stopping condition
264 norm_r = norm(*r);
265 if (norm_r < eps || iter >= prm.maxiter)
266 break;
267
268 // -- Inner LGMRES iteration
269 backend::axpby(math::inverse(norm_r), *r, zero, *vs[0]);
270
271 std::fill(s.begin(), s.end(), 0);
272 s[0] = norm_r;
273
274 unsigned j = 0;
275 while (true) {
276 // -- Arnoldi process:
277 //
278 // Build an orthonormal basis V and matrices W and H such that
279 // A W = V H
280 // Columns of W, V, and H are stored in `ws`, `vs` and `hs`.
281 //
282 // The first column of V is always the residual vector,
283 // `vs0`; V has *one more column* than the other of the
284 // three matrices.
285 //
286 // The other columns in V are built by feeding in, one by
287 // one, some vectors `z` and orthonormalizing them against
288 // the basis so far. The trick here is to feed in first
289 // some augmentation vectors, before starting to construct
290 // the Krylov basis on `v0`.
291 //
292 // It was shown in [BaJM05] that a good choice (the LGMRES
293 // choice) for these augmentation vectors are the `dx`
294 // vectors obtained from a couple of the previous restart
295 // cycles.
296 //
297 // Note especially that while `vs0` is always the first
298 // column in V, there is no reason why it should also be
299 // the first column in W. (In fact, below `vs0` comes in W
300 // only after the augmentation vectors.)
301 //
302 // The rest of the algorithm then goes as in GMRES, one
303 // solves a minimization problem in the smaller subspace
304 // spanned by W (range) and V (image).
305
306 vector& v_new = *vs[j + 1];
307
308 std::shared_ptr<vector> z;
309 if (j >= M - outer_v.size()) {
310 z = outer_v[j - (M - outer_v.size())];
311 }
312 else {
313 z = vs[j];
314 }
315
316 ws[j] = z;
317
318 preconditioner_spmv(prm.pside, P, A, *z, v_new, *r);
319
320 for (unsigned k = 0; k <= j; ++k) {
321 H0(k, j) = H(k, j) = inner_product(v_new, *vs[k]);
322 backend::axpby(-H(k, j), *vs[k], one, v_new);
323 }
324 H0(j + 1, j) = H(j + 1, j) = norm(v_new);
325
326 backend::axpby(math::inverse(H(j + 1, j)), v_new, zero, v_new);
327
328 for (unsigned k = 0; k < j; ++k)
329 detail::apply_plane_rotation(H(k, j), H(k + 1, j), cs[k], sn[k]);
330
331 detail::generate_plane_rotation(H(j, j), H(j + 1, j), cs[j], sn[j]);
332 detail::apply_plane_rotation(H(j, j), H(j + 1, j), cs[j], sn[j]);
333 detail::apply_plane_rotation(s[j], s[j + 1], cs[j], sn[j]);
334
335 scalar_type inner_res = std::abs(s[j + 1]);
336
337 if (prm.verbose && iter % 5 == 0)
338 std::cout << iter << "\t" << std::scientific << inner_res / norm_rhs << std::endl;
339
340 // Check for termination
341 ++j, ++iter;
342 if (iter >= prm.maxiter || j >= M || inner_res <= eps)
343 break;
344 }
345
346 // -- GMRES terminated: eval solution
347 for (unsigned i = j; i-- > 0;) {
348 s[i] /= H(i, i);
349 for (unsigned k = 0; k < i; ++k)
350 s[k] -= H(k, i) * s[i];
351 }
352
353 vector& dx = *r;
354 backend::lin_comb(j, s, ws, zero, dx);
355
356 // -- Apply step
357 if (prm.pside == ePreconditionerSideType::left) {
358 backend::axpby(one, dx, one, x);
359 }
360 else {
361 vector& tmp = *ws[0];
362 P.apply(dx, tmp);
363 backend::axpby(one, tmp, one, x);
364 }
365
366 // -- Store LGMRES augmented vectors
367 scalar_type norm_dx = norm(dx);
368
369 if (prm.K > 0 && !math::is_zero(norm_dx)) {
370 unsigned outer_slot = n_outer % prm.K;
371 ++n_outer;
372
373 norm_dx = math::inverse(norm_dx);
374 backend::axpby(norm_dx, dx, zero, *outer_v_data[outer_slot]);
375 outer_v.push_back(outer_v_data[outer_slot]);
376 }
377 }
378
379 return SolverResult(iter, norm_r / norm_rhs);
380 }
381
392 template <class Precond, class Vec1, class Vec2>
393 SolverResult operator()(Precond const& P, Vec1 const& rhs, Vec2& x) const
394 {
395 return (*this)(P.system_matrix(), P, rhs, x);
396 }
397
398 size_t bytes() const
399 {
400 size_t b = 0;
401
402 b += H.size() * sizeof(coef_type);
403 b += H0.size() * sizeof(coef_type);
404
405 b += backend::bytes(s);
406 b += backend::bytes(cs);
407 b += backend::bytes(sn);
408
409 b += backend::bytes(*r);
410
411 for (const auto& v : vs)
412 b += backend::bytes(*v);
413
414 for (const auto& v : outer_v_data)
415 b += backend::bytes(*v);
416
417 return b;
418 }
419
420 friend std::ostream& operator<<(std::ostream& os, const LooseGMRESSolver& s)
421 {
422 return os << "Type: LGMRES(" << s.prm.M << "," << s.prm.K << ")"
423 << "\nUnknowns: " << s.n
424 << "\nMemory footprint: " << human_readable_memory(s.bytes())
425 << std::endl;
426 }
427
428 public:
429
430 params prm;
431
432 private:
433
434 size_t n, M;
435
436 mutable multi_array<coef_type, 2> H, H0;
437 mutable std::vector<coef_type> s, cs, sn;
438 std::shared_ptr<vector> r;
439 mutable std::vector<std::shared_ptr<vector>> vs, ws;
440 mutable std::vector<std::shared_ptr<vector>> outer_v_data;
441 mutable circular_buffer<std::shared_ptr<vector>> outer_v;
442
443 InnerProduct inner_product;
444
445 template <class Vec>
446 scalar_type norm(const Vec& x) const
447 {
448 return std::abs(sqrt(inner_product(x, x)));
449 }
450};
451
452/*---------------------------------------------------------------------------*/
453/*---------------------------------------------------------------------------*/
454
455} // namespace Arcane::Alina
456
457/*---------------------------------------------------------------------------*/
458/*---------------------------------------------------------------------------*/
459
460#endif
Loose GMRES. \rst The LGMRES algorithm [BaJM05]_ is designed to avoid some problems in the convergenc...
size_t bytes() const
Memory used in bytes.
SolverResult operator()(Precond const &P, Vec1 const &rhs, Vec2 &x) const
Computes the solution for the given right-hand side.
SolverResult operator()(Matrix const &A, Precond const &P, Vec1 const &rhs, Vec2 &x) const
Computes the solution for the given system matrix.
LooseGMRESSolver(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.
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 Loose GMRES solver.
bool verbose
Verbose output (show iterations and error).
bool always_reset
Reset augmented vectors between solves.
bool ns_search
Ignore the trivial solution x=0 when rhs is zero.
double abstol
Target absolute residual error.
Int32 K
Number of vectors to carry between inner GMRES iterations.
double tol
Target relative residual error.
Int32 M
Number of inner GMRES iterations per each outer iteration.
Int32 maxiter
Maximum number of iterations.
ePreconditionerSideType pside
Preconditioning kind (left/right).