Discontinuous Galerkin Library
#include "dg/algorithm.h"
lgmres.h
Go to the documentation of this file.
1#ifndef _DG_LGMRES_
2#define _DG_LGMRES_
3
4#include <iostream>
5#include <cstring>
6#include <cmath>
7#include <algorithm>
8
9#include "blas.h"
10#include "functors.h"
11
18namespace dg{
19
50template< class ContainerType>
51class LGMRES
52{
53 public:
54 using container_type = ContainerType;
57 LGMRES() = default;
67 LGMRES( const ContainerType& copyable, unsigned max_inner, unsigned max_outer, unsigned max_restarts):
68 m_tmp(copyable),
69 m_dx(copyable),
70 m_residual( copyable),
71 m_maxRestarts( max_restarts),
72 m_inner_m( max_inner),
73 m_outer_k( max_outer),
74 m_krylovDimension( max_inner+max_outer)
75 {
76 if( m_inner_m < m_outer_k)
77 std::cerr << "WARNING (LGMRES): max_inner is smaller than the restart dimension max_outer. Did you swap the constructor parameters?\n";
78 //Declare Hessenberg matrix
79 m_H.assign( m_krylovDimension+1, std::vector<value_type>( m_krylovDimension, 0));
80 m_HH = m_H; //copy of H to be stored unaltered
81 //Declare givens rotation matrix
82 m_givens.assign( m_krylovDimension+1, {0,0});
83 //Declare s that minimizes the residual:
84 m_s.assign(m_krylovDimension+1,0);
85 // m+k+1 orthogonal basis vectors:
86 // k augmented pairs
87 m_outer_w.assign(m_outer_k,copyable);
88 m_outer_Az.assign(m_outer_k,copyable);
89 m_V.assign(m_krylovDimension+1,copyable);
90 }
97 template<class ...Params>
98 void construct( Params&& ...ps)
99 {
100 //construct and swap
101 *this = LGMRES( std::forward<Params>( ps)...);
102 }
105 void set_max( unsigned new_Restarts) {m_maxRestarts = new_Restarts;}
108 unsigned get_max() const {return m_maxRestarts;}
110 void set_throw_on_fail( bool throw_on_fail){
111 m_throw_on_fail = throw_on_fail;
112 }
115 const ContainerType& copyable()const{ return m_tmp;}
116
139 template< class MatrixType0, class ContainerType0, class ContainerType1, class MatrixType1, class ContainerType2 >
140 unsigned solve( MatrixType0&& A, ContainerType0& x, const ContainerType1& b, MatrixType1&& P, const ContainerType2& W, value_type eps = 1e-12, value_type nrmb_correction = 1);
141
147 bool converged() const{
148 return m_converged;
149 }
150
151 private:
152 template <class Preconditioner, class ContainerType0>
153 void Update(Preconditioner&& P, ContainerType &dx, ContainerType0 &x,
154 unsigned dimension, const std::vector<std::vector<value_type>> &H,
155 std::vector<value_type> &s, const std::vector<const ContainerType*> &W);
156 std::vector<std::array<value_type,2>> m_givens;
157 std::vector<std::vector<value_type>> m_H, m_HH;
158 ContainerType m_tmp, m_dx, m_residual;
159 std::vector<ContainerType> m_V, m_outer_w, m_outer_Az;
160 std::vector<value_type> m_s;
161 unsigned m_maxRestarts, m_inner_m, m_outer_k, m_krylovDimension;
162 bool m_converged = true, m_throw_on_fail = true;
163};
165
166template< class ContainerType>
167template < class Preconditioner, class ContainerType0>
168void LGMRES<ContainerType>::Update(Preconditioner&& P, ContainerType &dx,
169 ContainerType0 &x,
170 unsigned dimension, const std::vector<std::vector<value_type>> &H,
171 std::vector<value_type> &s, const std::vector<const ContainerType*> &W)
172{
173 // Solve for the coefficients, i.e. solve for c in
174 // H*c=s, but we do it in place.
175 for (int lupe = dimension; lupe >= 0; --lupe)
176 {
177 s[lupe] = s[lupe]/H[lupe][lupe];
178 for (int innerLupe = lupe - 1; innerLupe >= 0; --innerLupe)
179 {
180 // Subtract off the parts from the upper diagonal of the matrix.
181 s[innerLupe] = DG_FMA( -s[lupe],H[innerLupe][lupe], s[innerLupe]);
182 }
183 }
184
185 // Finally update the approximation. W_m*s
186 dg::blas2::gemv( dg::asDenseMatrix( W, dimension+1), std::vector<value_type>( s.begin(), s.begin()+dimension+1), dx);
187 // right preconditioner
188 dg::blas2::gemv( std::forward<Preconditioner>(P), dx, m_tmp);
189 dg::blas1::axpby(1.,m_tmp,1.,x);
190}
191
192template< class ContainerType>
193template< class Matrix, class ContainerType0, class ContainerType1, class Preconditioner, class ContainerType2>
194unsigned LGMRES< ContainerType>::solve( Matrix&& A, ContainerType0& x, const ContainerType1& b, Preconditioner&& P, const ContainerType2& S, value_type eps, value_type nrmb_correction)
195{
196 // Improvements over old implementation:
197 // - Use right preconditioned system such that residual norm is available in minimization
198 // - do not compute Az explicitly but save on iterations
199 // - first cycle equivalent to GMRES(m+k)
200 // - use weights for orthogonalization (works because in Saad book 6.29 and 6.30 are also true if V_m is unitary in the S scalar product, the Hessenberg matrix is still formed in the regular 2-norm, just define J(y) with S-norm in 6.26 and form V_m with a Gram-Schmidt process in the W-norm)
201 value_type nrmb = sqrt( blas2::dot( S, b));
202 value_type tol = eps*(nrmb + nrmb_correction);
203 m_converged = true;
204 if( nrmb == 0)
205 {
206 blas1::copy( 0., x);
207 return 0;
208 }
209
210 unsigned restartCycle = 0;
211 unsigned counter = 0;
212 value_type rho = 1.;
213 // DO NOT HOLD THESE AS PRIVATE!! MAKES BUG IN COPY!!
214 std::vector<ContainerType const*> m_W, m_Vptr;
215 m_W.assign(m_krylovDimension,nullptr);
216 m_Vptr.assign(m_krylovDimension+1,nullptr);
217 for( unsigned i=0; i<m_krylovDimension+1; i++)
218 m_Vptr[i] = &m_V[i];
219 do
220 {
221 dg::blas2::gemv(std::forward<Matrix>(A),x,m_residual);
222 dg::blas1::axpby(1.,b,-1.,m_residual);
223 rho = sqrt(dg::blas2::dot(S,m_residual));
224 counter ++;
225 if( rho < tol) //if x happens to be the solution
226 return counter;
227 // The first vector in the Krylov subspace is the normalized residual.
228 dg::blas1::axpby(1.0/rho,m_residual,0.,m_V[0]);
229
230 m_s[0] = rho;
231 for(unsigned lupe=1;lupe<=m_krylovDimension;++lupe)
232 m_s[lupe] = 0.0;
233
234 // Go through and generate the pre-determined number of vectors for the Krylov subspace.
235 for( unsigned iteration=0;iteration<m_krylovDimension;++iteration)
236 {
237 unsigned outer_w_count = std::min(restartCycle,m_outer_k);
238 if(iteration < m_krylovDimension-outer_w_count){
239 m_W[iteration] = &m_V[iteration];
240 dg::blas2::gemv(std::forward<Preconditioner>(P),*m_W[iteration],m_tmp);
241 dg::blas2::gemv(std::forward<Matrix>(A),m_tmp,m_V[iteration+1]);
242 counter++;
243 } else if( iteration < m_krylovDimension){ // size of W
244 unsigned w_idx = iteration - (m_krylovDimension - outer_w_count);
245 m_W[iteration] = &m_outer_w[w_idx];
246 dg::blas1::copy( m_outer_Az[w_idx], m_V[iteration+1]);
247 }
248
249 // Get the next entry in the vectors that form the basis for the Krylov subspace.
250 // Arnoldi modified Gram-Schmidt orthogonalization
251 for(unsigned row=0;row<=iteration;++row)
252 {
253 m_HH[row][iteration] = m_H[row][iteration]
254 = dg::blas2::dot(m_V[iteration+1],S,m_V[row]);
255 dg::blas1::axpby(-m_H[row][iteration],m_V[row],1.,m_V[iteration+1]);
256
257 }
258 m_HH[iteration+1][iteration] = m_H[iteration+1][iteration]
259 = sqrt(dg::blas2::dot(m_V[iteration+1],S,m_V[iteration+1]));
260 dg::blas1::scal(m_V[iteration+1],1.0/m_H[iteration+1][iteration]);
261
262 // Now solve the least squares problem
263 // using Givens Rotations transforming H into
264 // an upper triangular matrix (see Saad Chapter 6.5.3)
265 // corresponding to QR-decomposition of H
266
267 // First apply previous rotations to the current matrix.
268 value_type tmp = 0;
269 for (unsigned row = 0; row < iteration; row++)
270 {
271 tmp = m_givens[row][0]*m_H[row][iteration] + // c_row
272 m_givens[row][1]*m_H[row+1][iteration]; // s_row
273 m_H[row+1][iteration] = -m_givens[row][1]*m_H[row][iteration]
274 + m_givens[row][0]*m_H[row+1][iteration];
275 m_H[row][iteration] = tmp;
276 }
277
278 // Figure out the next Givens rotation.
279 if(m_H[iteration+1][iteration] == 0.0)
280 {
281 // It is already upper triangular. Just leave it be....
282 m_givens[iteration][0] = 1.0; // c_i
283 m_givens[iteration][1] = 0.0; // s_i
284 }
285 else if (fabs(m_H[iteration+1][iteration]) > fabs(m_H[iteration][iteration]))
286 {
287 // The off diagonal entry has a larger
288 // magnitude. Use the ratio of the
289 // diagonal entry over the off diagonal.
290 tmp = m_H[iteration][iteration]/m_H[iteration+1][iteration];
291 m_givens[iteration][1] = 1.0/sqrt(1.0+tmp*tmp);
292 m_givens[iteration][0] = tmp*m_givens[iteration][1];
293 }
294 else
295 {
296 // The off diagonal entry has a smaller
297 // magnitude. Use the ratio of the off
298 // diagonal entry to the diagonal entry.
299 tmp = m_H[iteration+1][iteration]/m_H[iteration][iteration];
300 m_givens[iteration][0] = 1.0/sqrt(1.0+tmp*tmp);
301 m_givens[iteration][1] = tmp*m_givens[iteration][0];
302 }
303 // Apply the new Givens rotation on the new entry in the upper Hessenberg matrix.
304 tmp = m_givens[iteration][0]*m_H[iteration][iteration] +
305 m_givens[iteration][1]*m_H[iteration+1][iteration];
306 m_H[iteration+1][iteration] = -m_givens[iteration][1]*m_H[iteration][iteration] +
307 m_givens[iteration][0]*m_H[iteration+1][iteration]; // zero
308 m_H[iteration][iteration] = tmp;
309 // Finally apply the new Givens rotation on the s vector
310 tmp = m_givens[iteration][0]*m_s[iteration] + m_givens[iteration][1]*m_s[iteration+1];
311 m_s[iteration+1] = -m_givens[iteration][1]*m_s[iteration] + m_givens[iteration][1]*m_s[iteration+1];
312 m_s[iteration] = tmp;
313
314 rho = fabs(m_s[iteration+1]);
315 if( rho < tol)
316 {
317 Update(std::forward<Preconditioner>(P),m_dx,x,iteration,m_H,m_s,m_W);
318 return counter;
319 }
320 }
321 Update(std::forward<Preconditioner>(P),m_dx,x,m_krylovDimension-1,m_H,m_s,m_W);
322 if( m_outer_k > 1)
323 {
324 std::rotate(m_outer_w.rbegin(),m_outer_w.rbegin()+1,m_outer_w.rend());
325 std::rotate(m_outer_Az.rbegin(),m_outer_Az.rbegin()+1,m_outer_Az.rend());
326 }
327 if( m_outer_k > 0)
328 {
329 dg::blas1::copy(m_dx,m_outer_w[0]);
330 // compute A P dx
331 std::vector<value_type> coeffs( m_krylovDimension+1, 0.);
332 for( unsigned i=0; i<m_krylovDimension+1; i++)
333 {
334 coeffs[i] = 0.;
335 for( unsigned k=0; k<m_krylovDimension; k++)
336 coeffs[i] = DG_FMA( m_HH[i][k],m_s[k], coeffs[i]);
337 }
338 dg::blas2::gemv( dg::asDenseMatrix( m_Vptr), coeffs, m_outer_Az[0]);
339 }
340
341 restartCycle ++;
342 // Go through the requisite number of restarts.
343 } while( (restartCycle < m_maxRestarts) && (rho > tol));
344 if( rho > tol)
345 {
346 if( m_throw_on_fail)
347 {
348 throw dg::Fail( eps, Message(_ping_)
349 <<"After "<<counter<<" LGMRES iterations");
350 }
351 m_converged = false;
352 }
353 return counter;
354}
356}//namespace dg
357#endif
Functor class for the right preconditioned LGMRES method to solve .
Definition: lgmres.h:52
bool converged() const
If last call to solve converged or not.
Definition: lgmres.h:147
void set_throw_on_fail(bool throw_on_fail)
Set or unset a throw on failure-to-converge.
Definition: lgmres.h:110
LGMRES(const ContainerType &copyable, unsigned max_inner, unsigned max_outer, unsigned max_restarts)
Allocate memory for the preconditioned LGMRES method.
Definition: lgmres.h:67
const ContainerType & copyable() const
Return an object of same size as the object used for construction.
Definition: lgmres.h:115
LGMRES()=default
Allocate nothing, Call construct method before usage.
void construct(Params &&...ps)
Perfect forward parameters to one of the constructors.
Definition: lgmres.h:98
unsigned get_max() const
Get the current maximum number of restarts.
Definition: lgmres.h:108
void set_max(unsigned new_Restarts)
Set the number of restarts.
Definition: lgmres.h:105
ContainerType container_type
Definition: lgmres.h:54
unsigned solve(MatrixType0 &&A, ContainerType0 &x, const ContainerType1 &b, MatrixType1 &&P, const ContainerType2 &W, value_type eps=1e-12, value_type nrmb_correction=1)
Solve using a right preconditioned LGMRES method.
dg::get_value_type< ContainerType > value_type
Definition: lgmres.h:55
#define _ping_
Definition: exceptions.h:12
void copy(const ContainerTypeIn &source, ContainerTypeOut &target)
Definition: blas1.h:164
void axpby(get_value_type< ContainerType > alpha, const ContainerType1 &x, get_value_type< ContainerType > beta, ContainerType &y)
Definition: blas1.h:231
void scal(ContainerType &x, get_value_type< ContainerType > alpha)
Definition: blas1.h:185
void gemv(get_value_type< ContainerType1 > alpha, MatrixType &&M, const ContainerType1 &x, get_value_type< ContainerType1 > beta, ContainerType2 &y)
; (alias for symv)
Definition: blas2.h:299
get_value_type< MatrixType > dot(const ContainerType1 &x, const MatrixType &m, const ContainerType2 &y)
; Binary reproducible general dot product
Definition: blas2.h:85
EllSparseBlockMat< real_type > dx(const aRealTopology2d< real_type > &g, bc bcx, direction dir=centered)
Create 2d derivative in x-direction.
Definition: derivatives.h:33
auto asDenseMatrix(const std::vector< const ContainerType * > &in)
Lightweight DenseMatrix for dg::blas2::gemv.
Definition: densematrix.h:70
typename TensorTraits< std::decay_t< Vector > >::value_type get_value_type
Definition: tensor_traits.h:38
This is the namespace for all functions and classes defined and used by the discontinuous Galerkin li...
Indicate failure to converge.
Definition: exceptions.h:126