Arcane  4.2.3.0
Developer documentation
Loading...
Searching...
No Matches
BiCGStabLSolver.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_bicgstabl.h (C) 2000-2026 */
9/* */
10/* BiCGStab(L) iterative method. . */
11/*---------------------------------------------------------------------------*/
12#ifndef ARCCORE_ALINA_BICGSTABL_H
13#define ARCCORE_ALINA_BICGSTABL_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 *
27 * The code is ported from PETSC BCGSL [1] and is based on [2].
28 *
29 * [1] http://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/KSP/KSPBCGSL.html
30 * [2] Fokkema, Diederik R. Enhanced implementation of BiCGstab (l) for solving
31 * linear systems of equations. Universiteit Utrecht. Mathematisch Instituut,
32 * 1996.
33
34 The original code came with the following license:
35
36 Copyright (c) 1991-2014, UChicago Argonne, LLC and the PETSc Development Team
37 All rights reserved.
38
39 Redistribution and use in source and binary forms, with or without modification,
40 are permitted provided that the following conditions are met:
41
42 * Redistributions of source code must retain the above copyright notice, this
43 list of conditions and the following disclaimer.
44 * Redistributions in binary form must reproduce the above copyright notice, this
45 list of conditions and the following disclaimer in the documentation and/or
46 other materials provided with the distribution.
47
48 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
49 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
50 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
51 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
52 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
53 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
54 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
55 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
56 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
57 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
58*/
59/*---------------------------------------------------------------------------*/
60/*---------------------------------------------------------------------------*/
61
62#include "arccore/alina/ValueTypeInterface.h"
63#include "arccore/alina/SolverUtils.h"
64#include "arccore/alina/QRFactorizationImpl.h"
65#include "arccore/alina/SolverBase.h"
66
67/*---------------------------------------------------------------------------*/
68/*---------------------------------------------------------------------------*/
69
70namespace Arcane::Alina
71{
72
73/*---------------------------------------------------------------------------*/
74/*---------------------------------------------------------------------------*/
78struct BiCGStabLSolverParams
79{
80 using params = BiCGStabLSolverParams;
81
82 // Order of the method.
83 int L = 2;
84
85 // Threshold used to decide when to refresh computed residuals.
86 double delta = 0.0;
87
88 // Use a convex function of the MinRes and OR polynomials
89 // after the BiCG step instead of default MinRes
90 bool convex = true;
91
92 // Preconditioning kind (left/right).
93 ePreconditionerSideType pside = ePreconditionerSideType::right;
94
95 // Maximum number of iterations.
96 Int32 maxiter = 100;
97
98 // Target relative residual error.
99 double tol = 1e-8;
100
101 // Target absolute residual error.
102 double abstol = std::numeric_limits<double>::min();
103
109 bool ns_search = false;
110
112 bool verbose = false;
113
114 BiCGStabLSolverParams() = default;
115
116 BiCGStabLSolverParams(const PropertyTree& p)
117 : ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, L)
118 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, delta)
119 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, convex)
120 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, pside)
121 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, maxiter)
122 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, tol)
123 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, abstol)
124 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, ns_search)
125 , ARCCORE_ALINA_PARAMS_IMPORT_VALUE(p, verbose)
126 {
127 p.check_params({ "L", "delta", "convex", "pside", "maxiter", "tol", "abstol", "ns_search", "verbose" });
128 }
129
130 void get(PropertyTree& p, const std::string& path) const
131 {
132 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, L);
133 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, delta);
134 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, convex);
135 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, pside);
136 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, maxiter);
137 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, tol);
138 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, abstol);
139 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, ns_search);
140 ARCCORE_ALINA_PARAMS_EXPORT_VALUE(p, path, verbose);
141 }
142};
143
144/*---------------------------------------------------------------------------*/
145/*---------------------------------------------------------------------------*/
149template <class Backend, class InnerProduct = detail::default_inner_product>
151: public SolverBase
152{
153 public:
154
155 using backend_type = Backend;
156 using BackendType = Backend;
157
158 typedef typename Backend::vector vector;
159 typedef typename Backend::value_type value_type;
160 typedef typename Backend::params backend_params;
161
162 typedef typename math::scalar_of<value_type>::type scalar_type;
163
164 typedef typename math::inner_product_impl<
165 typename math::rhs_of<value_type>::type>::return_type coef_type;
166
167 using params = BiCGStabLSolverParams;
168
170 BiCGStabLSolver(const AlinaContext& context, size_t n,
171 const params& prm = params(),
172 const backend_params& backend_prm = backend_params(),
173 const InnerProduct& inner_product = InnerProduct())
174 : SolverBase(context)
175 , prm(prm)
176 , n(n)
177 , Rt(Backend::create_vector(n, backend_prm))
178 , X(Backend::create_vector(n, backend_prm))
179 , B(Backend::create_vector(n, backend_prm))
180 , T(Backend::create_vector(n, backend_prm))
181 , R(prm.L + 1)
182 , U(prm.L + 1)
183 , MZa(prm.L + 1, prm.L + 1)
184 , MZb(prm.L + 1, prm.L + 1)
185 , Y0(prm.L + 1)
186 , YL(prm.L + 1)
187 , inner_product(inner_product)
188 {
189 precondition(prm.L > 0, "L in BiCGStab(L) should be >=1");
190
191 for (int i = 0; i <= prm.L; ++i) {
192 R[i] = Backend::create_vector(n, backend_prm);
193 U[i] = Backend::create_vector(n, backend_prm);
194 }
195 }
196
197 /*
198 * \brief Computes the solution for the given system matrix.
199 *
200 * Computes the solution for the given system matrix \p A and the
201 * right-hand side \p rhs. Returns the number of iterations made and
202 * the achieved residual as a ``std::tuple``. The solution vector
203 * \p x provides initial approximation in input and holds the computed
204 * solution on output.
205 *
206 * The system matrix may differ from the matrix used during
207 * initialization. This may be used for the solution of non-stationary
208 * problems with slowly changing coefficients. There is a strong chance
209 * that a preconditioner built for a time step will act as a reasonably
210 * good preconditioner for several subsequent time steps [DeSh12]_.
211 */
212 template <class Matrix, class Precond, class Vec1, class Vec2>
213 SolverResult operator()(const Matrix& A, const Precond& P, const Vec1& rhs, Vec2&& x) const
214 {
215 static const coef_type one = math::identity<coef_type>();
216 static const coef_type zero = math::zero<coef_type>();
217
218 const int L = prm.L;
219
220 ScopedStreamModifier ss(std::cout);
221
222 scalar_type norm_rhs = norm(rhs);
223
224 // Check if there is a trivial solution
225 if (norm_rhs < Alina::detail::eps<scalar_type>(1)) {
226 if (prm.ns_search) {
227 norm_rhs = math::identity<scalar_type>();
228 }
229 else {
230 backend::clear(x);
231 return SolverResult(0, norm_rhs);
232 }
233 }
234
235 if (prm.pside == ePreconditionerSideType::left) {
236 backend::residual(rhs, A, x, *T);
237 P.apply(*T, *B);
238 }
239 else {
240 backend::residual(rhs, A, x, *B);
241 }
242
243 scalar_type zeta0 = norm(*B);
244 scalar_type eps = std::max(prm.tol * norm_rhs, prm.abstol);
245
246 coef_type alpha = zero;
247 coef_type rho0 = one;
248 coef_type omega = one;
249
250 // Go
251 backend::copy(*B, *R[0]);
252 backend::copy(*B, *Rt);
253 backend::clear(*X);
254 backend::clear(*U[0]);
255
256 scalar_type zeta = zeta0;
257 scalar_type rnmax_computed = zeta0;
258 scalar_type rnmax_true = zeta0;
259
260 size_t iter = 0;
261 for (; iter < prm.maxiter && zeta >= eps; iter += L) {
262 // BiCG part
263 rho0 = -omega * rho0;
264
265 for (int j = 0; j < L; ++j) {
266 coef_type rho1 = inner_product(*R[j], *Rt);
267 precondition(!math::is_zero(rho1), "BiCGStab(L) breakdown: diverged (zero rho)");
268
269 coef_type beta = alpha * (rho1 / rho0);
270 rho0 = rho1;
271
272 for (int i = 0; i <= j; ++i)
273 backend::axpby(one, *R[i], -beta, *U[i]);
274
275 preconditioner_spmv(prm.pside, P, A, *U[j], *U[j + 1], *T);
276
277 coef_type sigma = inner_product(*U[j + 1], *Rt);
278 precondition(!math::is_zero(sigma), "BiCGStab(L) breakdown: diverged (zero sigma)");
279 alpha = rho1 / sigma;
280
281 backend::axpby(alpha, *U[0], one, *X);
282
283 for (int i = 0; i <= j; ++i)
284 backend::axpby(-alpha, *U[i + 1], one, *R[i]);
285
286 preconditioner_spmv(prm.pside, P, A, *R[j], *R[j + 1], *T);
287
288 zeta = norm(*R[0]);
289
290 rnmax_computed = std::max(zeta, rnmax_computed);
291 rnmax_true = std::max(zeta, rnmax_true);
292
293 // Check for early exit
294 if (zeta < eps) {
295 iter += j + 1;
296 goto done;
297 }
298 }
299
300 // Polynomial part
301 for (int i = 0; i <= L; ++i) {
302 for (int j = 0; j <= i; ++j) {
303 MZa(i, j) = inner_product(*R[i], *R[j]);
304 }
305 }
306
307 // Symmetrize MZa
308 for (int i = 0; i <= L; ++i) {
309 for (int j = i + 1; j <= L; ++j) {
310 MZa(i, j) = MZa(j, i) = math::adjoint(MZa(j, i));
311 }
312 }
313
314 std::copy(MZa.data(), MZa.data() + MZa.size(), MZb.data());
315
316 if (prm.convex || L == 1) {
317 Y0[0] = -one;
318
319 qr.solve(L, L, MZa.stride(0), MZa.stride(1),
320 &MZa(1, 1), &MZb(0, 1), &Y0[1]);
321 }
322 else {
323 Y0[0] = -one;
324 Y0[L] = zero;
325 qr.solve(L - 1, L - 1, MZa.stride(0), MZa.stride(1),
326 &MZa(1, 1), &MZb(0, 1), &Y0[1]);
327
328 YL[0] = zero;
329 YL[L] = -one;
330 qr.solve(L - 1, L - 1, MZa.stride(0), MZa.stride(1),
331 &MZa(1, 1), &MZb(L, 1), &YL[1], /*computed=*/true);
332
333 coef_type dot0 = zero;
334 coef_type dot1 = zero;
335 coef_type dotA = zero;
336 for (int i = 0; i <= L; ++i) {
337 coef_type s0 = zero;
338 coef_type sL = zero;
339
340 for (int j = 0; j <= L; ++j) {
341 coef_type M = MZb(i, j);
342 s0 += M * Y0[j];
343 sL += M * YL[j];
344 }
345
346 dot0 += Y0[i] * s0;
347 dotA += YL[i] * s0;
348 dot1 += YL[i] * sL;
349 }
350
351 scalar_type kappa0 = sqrt(std::abs(std::real(dot0)));
352 scalar_type kappa1 = sqrt(std::abs(std::real(dot1)));
353 scalar_type kappaA = std::real(dotA);
354
355 if (!math::is_zero(kappa0) && !math::is_zero(kappa1)) {
356 scalar_type ghat;
357 if (kappaA < 0.7 * kappa0 * kappa1) {
358 ghat = (kappaA < 0) ? -0.7 * kappa0 / kappa1 : 0.7 * kappa0 / kappa1;
359 }
360 else {
361 ghat = kappaA / (kappa1 * kappa1);
362 }
363
364 for (int i = 0; i <= L; ++i)
365 Y0[i] -= ghat * YL[i];
366 }
367 }
368
369 omega = Y0[L];
370 for (int h = L; h > 0 && math::is_zero(omega); --h)
371 omega = Y0[h];
372 precondition(!math::is_zero(omega), "BiCGStab(L) breakdown: diverged (zero omega)");
373
374 backend::lin_comb(L, &Y0[1], &R[0], one, *X);
375
376 for (int i = 1; i <= L; ++i)
377 Y0[i] = -one * Y0[i];
378
379 backend::lin_comb(L, &Y0[1], &U[1], one, *U[0]);
380 backend::lin_comb(L, &Y0[1], &R[1], one, *R[0]);
381
382 for (int i = 1; i <= L; ++i)
383 Y0[i] = -one * Y0[i];
384
385 zeta = norm(*R[0]);
386
387 // Accurate update
388 if (prm.delta > 0) {
389 rnmax_computed = std::max(zeta, rnmax_computed);
390 rnmax_true = std::max(zeta, rnmax_true);
391
392 bool update_x = zeta < prm.delta * zeta0 && zeta0 <= rnmax_computed;
393
394 if ((zeta < prm.delta * rnmax_true && zeta <= rnmax_true) || update_x) {
395 preconditioner_spmv(prm.pside, P, A, *X, *R[0], *T);
396 backend::axpby(one, *B, -one, *R[0]);
397 rnmax_true = zeta;
398
399 if (update_x) {
400 if (prm.pside == ePreconditionerSideType::left) {
401 backend::axpby(one, *X, one, x);
402 }
403 else {
404 backend::axpby(one, *T, one, x);
405 }
406 backend::clear(*X);
407 backend::copy(*R[0], *B);
408
409 rnmax_computed = zeta;
410 }
411 }
412 }
413 if (prm.verbose && iter % 5 == 0)
414 std::cout << iter << "\t" << std::scientific << zeta / norm_rhs << std::endl;
415 }
416
417 done:
418 if (prm.pside == ePreconditionerSideType::left) {
419 backend::axpby(one, *X, one, x);
420 }
421 else {
422 P.apply(*X, *T);
423 backend::axpby(one, *T, one, x);
424 }
425
426 return SolverResult(iter, zeta / norm_rhs);
427 }
428
439 template <class Precond, class Vec1, class Vec2>
440 SolverResult operator()(const Precond& P, const Vec1& rhs, Vec2&& x) const
441 {
442 return (*this)(P.system_matrix(), P, rhs, x);
443 }
444
445 size_t bytes() const
446 {
447 size_t b = 0;
448
449 b += backend::bytes(*Rt);
450 b += backend::bytes(*X);
451 b += backend::bytes(*B);
452 b += backend::bytes(*T);
453
454 for (const auto& v : R)
455 b += backend::bytes(*v);
456 for (const auto& v : U)
457 b += backend::bytes(*v);
458
459 b += MZa.size() * sizeof(coef_type);
460 b += MZb.size() * sizeof(coef_type);
461
462 b += backend::bytes(Y0);
463 b += backend::bytes(YL);
464
465 b += qr.bytes();
466
467 return b;
468 }
469
470 friend std::ostream& operator<<(std::ostream& os, const BiCGStabLSolver& s)
471 {
472 return os << "Type: BiCGStab(" << s.prm.L << ")"
473 << "\nUnknowns: " << s.n
474 << "\nMemory footprint: " << human_readable_memory(s.bytes())
475 << std::endl;
476 }
477
478 public:
479
480 params prm;
481
482 private:
483
484 size_t n;
485
486 mutable std::shared_ptr<vector> Rt;
487 mutable std::shared_ptr<vector> X;
488 mutable std::shared_ptr<vector> B;
489 mutable std::shared_ptr<vector> T;
490
491 mutable std::vector<std::shared_ptr<vector>> R;
492 mutable std::vector<std::shared_ptr<vector>> U;
493
494 mutable multi_array<coef_type, 2> MZa, MZb;
495 mutable std::vector<coef_type> Y0, YL;
497
498 InnerProduct inner_product;
499
500 template <class Vec>
501 scalar_type norm(const Vec& x) const
502 {
503 return sqrt(math::norm(inner_product(x, x)));
504 }
505};
506
507/*---------------------------------------------------------------------------*/
508/*---------------------------------------------------------------------------*/
509
510} // namespace Arcane::Alina
511
512/*---------------------------------------------------------------------------*/
513/*---------------------------------------------------------------------------*/
514
515#endif
size_t bytes() const
Memory used in bytes.
BiCGStabLSolver(const AlinaContext &context, size_t n, const params &prm=params(), const backend_params &backend_prm=backend_params(), const InnerProduct &inner_product=InnerProduct())
Preallocates necessary data structures for the system of size n.
SolverResult operator()(const Precond &P, const Vec1 &rhs, Vec2 &&x) const
Computes the solution for the given right-hand side.
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.
apfloat sqrt(apfloat v)
Square root of v.
Definition MathApfloat.h:69
std::int32_t Int32
Signed integer type of 32 bits.
Parameters for BiCGStab(L) solver.
bool ns_search
Ignore the trivial solution x=0 when rhs is zero.
bool verbose
Verbose output (show iterations and error).
Default implementation for inner product.