Arcane  v4.1.10.0
Documentation développeur
Chargement...
Recherche...
Aucune correspondance
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
185 const params& prm = params(),
186 const backend_params& bprm = backend_params(),
187 const InnerProduct& inner_product = InnerProduct())
188 : prm(prm)
189 , n(n)
190 , M(prm.M + prm.K)
191 , H(M + 1, M)
192 , H0(M + 1, M)
193 , s(M + 1)
194 , cs(M + 1)
195 , sn(M + 1)
196 , r(Backend::create_vector(n, bprm))
197 , ws(M)
198 , outer_v(prm.K)
199 , inner_product(inner_product)
200 {
201 outer_v_data.reserve(prm.K);
202 for (unsigned i = 0; i < prm.K; ++i)
203 outer_v_data.push_back(Backend::create_vector(n, bprm));
204
205 vs.reserve(M + 1);
206 for (unsigned i = 0; i <= M; ++i)
207 vs.push_back(Backend::create_vector(n, bprm));
208 }
209
225 template <class Matrix, class Precond, class Vec1, class Vec2>
226 SolverResult operator()(Matrix const& A, Precond const& P, Vec1 const& rhs, Vec2& x) const
227 {
228 static const scalar_type zero = math::zero<scalar_type>();
229 static const scalar_type one = math::identity<scalar_type>();
230
231 ScopedStreamModifier ss(std::cout);
232
233 if (prm.always_reset) {
234 outer_v.clear();
235 }
236
237 scalar_type norm_rhs = norm(rhs);
238 if (norm_rhs < Alina::detail::eps<scalar_type>(1)) {
239 if (prm.ns_search) {
240 norm_rhs = math::identity<scalar_type>();
241 }
242 else {
243 backend::clear(x);
244 return SolverResult(0, norm_rhs);
245 }
246 }
247
248 scalar_type norm_r = zero;
249 scalar_type eps = std::max(prm.tol * norm_rhs, prm.abstol);
250
251 Int32 iter = 0;
252 unsigned n_outer = 0;
253 while (true) {
254 if (prm.pside == ePreconditionerSideType::left) {
255 backend::residual(rhs, A, x, *vs[0]);
256 P.apply(*vs[0], *r);
257 }
258 else {
259 backend::residual(rhs, A, x, *r);
260 }
261
262 // -- Check stopping condition
263 norm_r = norm(*r);
264 if (norm_r < eps || iter >= prm.maxiter)
265 break;
266
267 // -- Inner LGMRES iteration
268 backend::axpby(math::inverse(norm_r), *r, zero, *vs[0]);
269
270 std::fill(s.begin(), s.end(), 0);
271 s[0] = norm_r;
272
273 unsigned j = 0;
274 while (true) {
275 // -- Arnoldi process:
276 //
277 // Build an orthonormal basis V and matrices W and H such that
278 // A W = V H
279 // Columns of W, V, and H are stored in `ws`, `vs` and `hs`.
280 //
281 // The first column of V is always the residual vector,
282 // `vs0`; V has *one more column* than the other of the
283 // three matrices.
284 //
285 // The other columns in V are built by feeding in, one by
286 // one, some vectors `z` and orthonormalizing them against
287 // the basis so far. The trick here is to feed in first
288 // some augmentation vectors, before starting to construct
289 // the Krylov basis on `v0`.
290 //
291 // It was shown in [BaJM05] that a good choice (the LGMRES
292 // choice) for these augmentation vectors are the `dx`
293 // vectors obtained from a couple of the previous restart
294 // cycles.
295 //
296 // Note especially that while `vs0` is always the first
297 // column in V, there is no reason why it should also be
298 // the first column in W. (In fact, below `vs0` comes in W
299 // only after the augmentation vectors.)
300 //
301 // The rest of the algorithm then goes as in GMRES, one
302 // solves a minimization problem in the smaller subspace
303 // spanned by W (range) and V (image).
304
305 vector& v_new = *vs[j + 1];
306
307 std::shared_ptr<vector> z;
308 if (j >= M - outer_v.size()) {
309 z = outer_v[j - (M - outer_v.size())];
310 }
311 else {
312 z = vs[j];
313 }
314
315 ws[j] = z;
316
317 preconditioner_spmv(prm.pside, P, A, *z, v_new, *r);
318
319 for (unsigned k = 0; k <= j; ++k) {
320 H0(k, j) = H(k, j) = inner_product(v_new, *vs[k]);
321 backend::axpby(-H(k, j), *vs[k], one, v_new);
322 }
323 H0(j + 1, j) = H(j + 1, j) = norm(v_new);
324
325 backend::axpby(math::inverse(H(j + 1, j)), v_new, zero, v_new);
326
327 for (unsigned k = 0; k < j; ++k)
328 detail::apply_plane_rotation(H(k, j), H(k + 1, j), cs[k], sn[k]);
329
330 detail::generate_plane_rotation(H(j, j), H(j + 1, j), cs[j], sn[j]);
331 detail::apply_plane_rotation(H(j, j), H(j + 1, j), cs[j], sn[j]);
332 detail::apply_plane_rotation(s[j], s[j + 1], cs[j], sn[j]);
333
334 scalar_type inner_res = std::abs(s[j + 1]);
335
336 if (prm.verbose && iter % 5 == 0)
337 std::cout << iter << "\t" << std::scientific << inner_res / norm_rhs << std::endl;
338
339 // Check for termination
340 ++j, ++iter;
341 if (iter >= prm.maxiter || j >= M || inner_res <= eps)
342 break;
343 }
344
345 // -- GMRES terminated: eval solution
346 for (unsigned i = j; i-- > 0;) {
347 s[i] /= H(i, i);
348 for (unsigned k = 0; k < i; ++k)
349 s[k] -= H(k, i) * s[i];
350 }
351
352 vector& dx = *r;
353 backend::lin_comb(j, s, ws, zero, dx);
354
355 // -- Apply step
356 if (prm.pside == ePreconditionerSideType::left) {
357 backend::axpby(one, dx, one, x);
358 }
359 else {
360 vector& tmp = *ws[0];
361 P.apply(dx, tmp);
362 backend::axpby(one, tmp, one, x);
363 }
364
365 // -- Store LGMRES augmented vectors
366 scalar_type norm_dx = norm(dx);
367
368 if (prm.K > 0 && !math::is_zero(norm_dx)) {
369 unsigned outer_slot = n_outer % prm.K;
370 ++n_outer;
371
372 norm_dx = math::inverse(norm_dx);
373 backend::axpby(norm_dx, dx, zero, *outer_v_data[outer_slot]);
374 outer_v.push_back(outer_v_data[outer_slot]);
375 }
376 }
377
378 return SolverResult(iter, norm_r / norm_rhs);
379 }
380
391 template <class Precond, class Vec1, class Vec2>
392 SolverResult operator()(Precond const& P, Vec1 const& rhs, Vec2& x) const
393 {
394 return (*this)(P.system_matrix(), P, rhs, x);
395 }
396
397 size_t bytes() const
398 {
399 size_t b = 0;
400
401 b += H.size() * sizeof(coef_type);
402 b += H0.size() * sizeof(coef_type);
403
404 b += backend::bytes(s);
405 b += backend::bytes(cs);
406 b += backend::bytes(sn);
407
408 b += backend::bytes(*r);
409
410 for (const auto& v : vs)
411 b += backend::bytes(*v);
412
413 for (const auto& v : outer_v_data)
414 b += backend::bytes(*v);
415
416 return b;
417 }
418
419 friend std::ostream& operator<<(std::ostream& os, const LooseGMRESSolver& s)
420 {
421 return os << "Type: LGMRES(" << s.prm.M << "," << s.prm.K << ")"
422 << "\nUnknowns: " << s.n
423 << "\nMemory footprint: " << human_readable_memory(s.bytes())
424 << std::endl;
425 }
426
427 public:
428
429 params prm;
430
431 private:
432
433 size_t n, M;
434
435 mutable multi_array<coef_type, 2> H, H0;
436 mutable std::vector<coef_type> s, cs, sn;
437 std::shared_ptr<vector> r;
438 mutable std::vector<std::shared_ptr<vector>> vs, ws;
439 mutable std::vector<std::shared_ptr<vector>> outer_v_data;
440 mutable circular_buffer<std::shared_ptr<vector>> outer_v;
441
442 InnerProduct inner_product;
443
444 template <class Vec>
445 scalar_type norm(const Vec& x) const
446 {
447 return std::abs(sqrt(inner_product(x, x)));
448 }
449};
450
451/*---------------------------------------------------------------------------*/
452/*---------------------------------------------------------------------------*/
453
454} // namespace Arcane::Alina
455
456/*---------------------------------------------------------------------------*/
457/*---------------------------------------------------------------------------*/
458
459#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.
LooseGMRESSolver(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.
SolverResult operator()(Matrix const &A, Precond const &P, Vec1 const &rhs, Vec2 &x) const
Computes the solution for the given system matrix.
Save ostream flags in constructor, restore in destructor.
Base class for solvers.
Definition SolverBase.h:31
Result of a solving.
Definition AlinaUtils.h:52
Matrix class, to be used by user.
std::int32_t Int32
Type entier signé sur 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).