1 // ---------------------------------------------------------------------
2 //
3 // Copyright (C) 2000 - 2020 by the deal.II authors
4 //
5 // This file is part of the deal.II library.
6 //
7 // The deal.II library is free software; you can use it, redistribute
8 // it, and/or modify it under the terms of the GNU Lesser General
9 // Public License as published by the Free Software Foundation; either
10 // version 2.1 of the License, or (at your option) any later version.
11 // The full text of the license can be found in the file LICENSE.md at
12 // the top level directory of deal.II.
13 //
14 // ---------------------------------------------------------------------
15
16 #ifndef dealii_solver_idr_h
17 #define dealii_solver_idr_h
18
19
20 #include <deal.II/base/config.h>
21
22 #include <deal.II/base/logstream.h>
23 #include <deal.II/base/signaling_nan.h>
24 #include <deal.II/base/subscriptor.h>
25 #include <deal.II/base/utilities.h>
26
27 #include <deal.II/lac/full_matrix.h>
28 #include <deal.II/lac/solver.h>
29 #include <deal.II/lac/solver_control.h>
30
31 #include <cmath>
32 #include <random>
33
34 DEAL_II_NAMESPACE_OPEN
35
36 /*!@addtogroup Solvers */
37 /*@{*/
38
39 namespace internal
40 {
41 /**
42 * A namespace for a helper class to the IDR(s) solver.
43 */
44 namespace SolverIDRImplementation
45 {
46 /**
47 * Class to hold temporary vectors whose size depends on
48 * the solver parameter s.
49 */
50 template <typename VectorType>
51 class TmpVectors
52 {
53 public:
54 /**
55 * Constructor. Prepares an array of @p VectorType of length @p s_param.
56 */
57 TmpVectors(const unsigned int s_param, VectorMemory<VectorType> &vmem);
58
59 /**
60 * Destructor. Delete all allocated vectors.
61 */
62 ~TmpVectors() = default;
63
64 /**
65 * Get vector number @p i. If this vector was unused before, an error
66 * occurs.
67 */
68 VectorType &operator[](const unsigned int i) const;
69
70 /**
71 * Get vector number @p i. Allocate it if necessary.
72 *
73 * If a vector must be allocated, @p temp is used to reinit it to the
74 * proper dimensions.
75 */
76 VectorType &
77 operator()(const unsigned int i, const VectorType &temp);
78
79 private:
80 /**
81 * Pool where vectors are obtained from.
82 */
83 VectorMemory<VectorType> &mem;
84
85 /**
86 * Field for storing the vectors.
87 */
88 std::vector<typename VectorMemory<VectorType>::Pointer> data;
89 };
90 } // namespace SolverIDRImplementation
91 } // namespace internal
92
93 /**
94 * This class implements the IDR(s) method used for solving nonsymmetric,
95 * indefinite linear systems, developed in <a
96 * href="https://epubs.siam.org/doi/abs/10.1137/070685804">
97 * IDR(s): A Family of Simple and Fast Algorithms for Solving Large
98 * Nonsymmetric Systems of Linear Equations by Martin B. van Gijzen and Peter
99 * Sonneveld </a>. The implementation here is the preconditioned version from <a
100 * href="https://dl.acm.org/citation.cfm?id=2049667">
101 * Algorithm 913: An Elegant IDR(s) Variant that Efficiently Exploits
102 * Biorthogonality Properties
103 * by Martin B. van Gijzen and Peter Sonneveld</a>. The local structure
104 * @p AdditionalData takes the value for the parameter s which can be any
105 * integer greater than or equal to 1. For <code>s=1</code>, this method has
106 * similar convergence to BiCGStab.
107 *
108 * @note Each iteration of IDR(s) requires <code>s+1</code> preconditioning steps
109 * and matrix-vector products. In this implementation the residual is updated
110 * and convergence is checked after each of these inner steps inside the outer
111 * iteration. If the user enables the history data, the residual at each of
112 * these steps is stored and therefore there will be multiple values per
113 * iteration.
114 */
115 template <class VectorType = Vector<double>>
116 class SolverIDR : public SolverBase<VectorType>
117 {
118 public:
119 /**
120 * Structure for storing additional data needed by the solver.
121 */
122 struct AdditionalData
123 {
124 /**
125 * Constructor. By default, an IDR(2) method is used.
126 */
127 explicit AdditionalData(const unsigned int s = 2)
sAdditionalData128 : s(s)
129 {}
130
131 const unsigned int s;
132 };
133
134 /**
135 * Constructor.
136 */
137 SolverIDR(SolverControl & cn,
138 VectorMemory<VectorType> &mem,
139 const AdditionalData & data = AdditionalData());
140
141 /**
142 * Constructor. Use an object of type GrowingVectorMemory as a default to
143 * allocate memory.
144 */
145 explicit SolverIDR(SolverControl & cn,
146 const AdditionalData &data = AdditionalData());
147
148 /**
149 * Virtual destructor.
150 */
151 virtual ~SolverIDR() override = default;
152
153 /**
154 * Solve the linear system <code>Ax=b</code> for x.
155 */
156 template <typename MatrixType, typename PreconditionerType>
157 void
158 solve(const MatrixType & A,
159 VectorType & x,
160 const VectorType & b,
161 const PreconditionerType &preconditioner);
162
163 protected:
164 /**
165 * Interface for derived class. This function gets the current iteration
166 * vector, the residual and the update vector in each step. It can be used
167 * for graphical output of the convergence history.
168 */
169 virtual void
170 print_vectors(const unsigned int step,
171 const VectorType & x,
172 const VectorType & r,
173 const VectorType & d) const;
174
175 private:
176 /**
177 * Additional solver parameters.
178 */
179 AdditionalData additional_data;
180 };
181
182 /*@}*/
183 /*------------------------- Implementation ----------------------------*/
184
185 #ifndef DOXYGEN
186
187
188 namespace internal
189 {
190 namespace SolverIDRImplementation
191 {
192 template <class VectorType>
TmpVectors(const unsigned int s_param,VectorMemory<VectorType> & vmem)193 inline TmpVectors<VectorType>::TmpVectors(const unsigned int s_param,
194 VectorMemory<VectorType> &vmem)
195 : mem(vmem)
196 , data(s_param)
197 {}
198
199
200
201 template <class VectorType>
202 inline VectorType &TmpVectors<VectorType>::
203 operator[](const unsigned int i) const
204 {
205 AssertIndexRange(i, data.size());
206
207 Assert(data[i] != nullptr, ExcNotInitialized());
208 return *data[i];
209 }
210
211
212
213 template <class VectorType>
214 inline VectorType &
operator()215 TmpVectors<VectorType>::operator()(const unsigned int i,
216 const VectorType & temp)
217 {
218 AssertIndexRange(i, data.size());
219 if (data[i] == nullptr)
220 {
221 data[i] = std::move(typename VectorMemory<VectorType>::Pointer(mem));
222 data[i]->reinit(temp);
223 }
224 return *data[i];
225 }
226 } // namespace SolverIDRImplementation
227 } // namespace internal
228
229
230
231 template <class VectorType>
SolverIDR(SolverControl & cn,VectorMemory<VectorType> & mem,const AdditionalData & data)232 SolverIDR<VectorType>::SolverIDR(SolverControl & cn,
233 VectorMemory<VectorType> &mem,
234 const AdditionalData & data)
235 : SolverBase<VectorType>(cn, mem)
236 , additional_data(data)
237 {}
238
239
240
241 template <class VectorType>
SolverIDR(SolverControl & cn,const AdditionalData & data)242 SolverIDR<VectorType>::SolverIDR(SolverControl &cn, const AdditionalData &data)
243 : SolverBase<VectorType>(cn)
244 , additional_data(data)
245 {}
246
247
248
249 template <class VectorType>
250 void
print_vectors(const unsigned int,const VectorType &,const VectorType &,const VectorType &)251 SolverIDR<VectorType>::print_vectors(const unsigned int,
252 const VectorType &,
253 const VectorType &,
254 const VectorType &) const
255 {}
256
257
258
259 template <class VectorType>
260 template <typename MatrixType, typename PreconditionerType>
261 void
solve(const MatrixType & A,VectorType & x,const VectorType & b,const PreconditionerType & preconditioner)262 SolverIDR<VectorType>::solve(const MatrixType & A,
263 VectorType & x,
264 const VectorType & b,
265 const PreconditionerType &preconditioner)
266 {
267 LogStream::Prefix prefix("IDR(s)");
268
269 SolverControl::State iteration_state = SolverControl::iterate;
270 unsigned int step = 0;
271
272 const unsigned int s = additional_data.s;
273
274 // Define temporary vectors which do not do not depend on s
275 typename VectorMemory<VectorType>::Pointer r_pointer(this->memory);
276 typename VectorMemory<VectorType>::Pointer v_pointer(this->memory);
277 typename VectorMemory<VectorType>::Pointer vhat_pointer(this->memory);
278 typename VectorMemory<VectorType>::Pointer uhat_pointer(this->memory);
279 typename VectorMemory<VectorType>::Pointer ghat_pointer(this->memory);
280
281 VectorType &r = *r_pointer;
282 VectorType &v = *v_pointer;
283 VectorType &vhat = *vhat_pointer;
284 VectorType &uhat = *uhat_pointer;
285 VectorType &ghat = *ghat_pointer;
286
287 r.reinit(x, true);
288 v.reinit(x, true);
289 vhat.reinit(x, true);
290 uhat.reinit(x, true);
291 ghat.reinit(x, true);
292
293 // Initial residual
294 A.vmult(r, x);
295 r.sadd(-1.0, 1.0, b);
296
297 // Check for convergent initial guess
298 double res = r.l2_norm();
299 iteration_state = this->iteration_status(step, res, x);
300 if (iteration_state == SolverControl::success)
301 return;
302
303 // Initialize sets of vectors/matrices whose size dependent on s
304 internal::SolverIDRImplementation::TmpVectors<VectorType> G(s, this->memory);
305 internal::SolverIDRImplementation::TmpVectors<VectorType> U(s, this->memory);
306 internal::SolverIDRImplementation::TmpVectors<VectorType> Q(s, this->memory);
307 FullMatrix<double> M(s, s);
308
309 // Random number generator for vector entries of
310 // Q (normal distribution, mean=0 sigma=1)
311 std::mt19937 rng;
312 std::normal_distribution<> normal_distribution(0.0, 1.0);
313 for (unsigned int i = 0; i < s; ++i)
314 {
315 VectorType &tmp_g = G(i, x);
316 VectorType &tmp_u = U(i, x);
317 tmp_g = 0;
318 tmp_u = 0;
319
320 // Compute random set of s orthonormalized vectors Q
321 // Note: the first vector is chosen to be the initial
322 // residual to match BiCGStab (as is done in comparisons
323 // with BiCGStab in the papers listed in the documentation
324 // of this function)
325 VectorType &tmp_q = Q(i, x);
326 if (i != 0)
327 {
328 for (auto indx : tmp_q.locally_owned_elements())
329 tmp_q(indx) = normal_distribution(rng);
330 tmp_q.compress(VectorOperation::insert);
331 }
332 else
333 tmp_q = r;
334
335 for (unsigned int j = 0; j < i; ++j)
336 {
337 v = Q[j];
338 v *= (v * tmp_q) / (tmp_q * tmp_q);
339 tmp_q.add(-1.0, v);
340 }
341
342 if (i != 0)
343 tmp_q *= 1.0 / tmp_q.l2_norm();
344
345 M(i, i) = 1.;
346 }
347
348 double omega = 1.;
349
350 bool early_exit = false;
351
352 // Outer iteration
353 while (iteration_state == SolverControl::iterate)
354 {
355 ++step;
356
357 // Compute phi
358 Vector<double> phi(s);
359 for (unsigned int i = 0; i < s; ++i)
360 phi(i) = Q[i] * r;
361
362 // Inner iteration over s
363 for (unsigned int k = 0; k < s; ++k)
364 {
365 // Solve M(k:s)*gamma = phi(k:s)
366 Vector<double> gamma(s - k);
367 {
368 Vector<double> phik(s - k);
369 FullMatrix<double> Mk(s - k, s - k);
370 std::vector<unsigned int> indices;
371 unsigned int j = 0;
372 for (unsigned int i = k; i < s; ++i, ++j)
373 {
374 indices.push_back(i);
375 phik(j) = phi(i);
376 }
377 Mk.extract_submatrix_from(M, indices, indices);
378
379 FullMatrix<double> Mk_inv(s - k, s - k);
380 Mk_inv.invert(Mk);
381 Mk_inv.vmult(gamma, phik);
382 }
383
384 {
385 v = r;
386
387 unsigned int j = 0;
388 for (unsigned int i = k; i < s; ++i, ++j)
389 v.add(-1.0 * gamma(j), G[i]);
390 preconditioner.vmult(vhat, v);
391
392 uhat = vhat;
393 uhat *= omega;
394 j = 0;
395 for (unsigned int i = k; i < s; ++i, ++j)
396 uhat.add(gamma(j), U[i]);
397 A.vmult(ghat, uhat);
398 }
399
400 // Update G and U
401 // Orthogonalize ghat to Q0,..,Q_{k-1}
402 // and update uhat
403 for (unsigned int i = 0; i < k; ++i)
404 {
405 double alpha = (Q[i] * ghat) / M(i, i);
406 ghat.add(-alpha, G[i]);
407 uhat.add(-alpha, U[i]);
408 }
409 G[k] = ghat;
410 U[k] = uhat;
411
412 // Update kth column of M
413 for (unsigned int i = k; i < s; ++i)
414 M(i, k) = Q[i] * G[k];
415
416 // Orthogonalize r to Q0,...,Qk,
417 // update x
418 {
419 double beta = phi(k) / M(k, k);
420 r.add(-1.0 * beta, G[k]);
421 x.add(beta, U[k]);
422
423 print_vectors(step, x, r, U[k]);
424
425 // Check for early convergence. If so, store
426 // information in early_exit so that outer iteration
427 // is broken before recomputing the residual
428 res = r.l2_norm();
429 iteration_state = this->iteration_status(step, res, x);
430 if (iteration_state != SolverControl::iterate)
431 {
432 early_exit = true;
433 break;
434 }
435
436 // Update phi
437 if (k + 1 < s)
438 {
439 for (unsigned int i = 0; i < k + 1; ++i)
440 phi(i) = 0.0;
441 for (unsigned int i = k + 1; i < s; ++i)
442 phi(i) -= beta * M(i, k);
443 }
444 }
445 }
446 if (early_exit == true)
447 break;
448
449 // Update r and x
450 preconditioner.vmult(vhat, r);
451 A.vmult(v, vhat);
452
453 omega = (v * r) / (v * v);
454
455 r.add(-1.0 * omega, v);
456 x.add(omega, vhat);
457
458 print_vectors(step, x, r, vhat);
459
460 // Check for convergence
461 res = r.l2_norm();
462 iteration_state = this->iteration_status(step, res, x);
463 if (iteration_state != SolverControl::iterate)
464 break;
465 }
466
467 if (iteration_state != SolverControl::success)
468 AssertThrow(false, SolverControl::NoConvergence(step, res));
469 }
470
471
472 #endif // DOXYGEN
473
474 DEAL_II_NAMESPACE_CLOSE
475
476 #endif
477