complex_smoother.h
Go to the documentation of this file.
1 //Include guards
2 #ifndef OOMPH_COMPLEX_SMOOTHER_HEADER
3 #define OOMPH_COMPLEX_SMOOTHER_HEADER
4 
5 // Config header generated by autoconfig
6 #ifdef HAVE_CONFIG_H
7 #include <oomph-lib-config.h>
8 #endif
9 
10 // Namespace extension
11 namespace oomph
12 {
13 
14  //====================================================================
15  /// Helmholtz smoother class:
16  /// The smoother class is designed for the Helmholtz equation to be used
17  /// in conjunction with multigrid. The action of the smoother should
18  /// reduce the high frequency errors. These methods are inefficient as
19  /// stand-alone solvers
20  //====================================================================
22  {
23  public:
24 
25  /// Empty constructor
27  {}
28 
29  /// Virtual empty destructor
30  virtual ~HelmholtzSmoother(){};
31 
32  /// \short The smoother_solve function performs fixed number of iterations
33  /// on the system A*result=rhs. The number of (smoothing) iterations is
34  /// the same as the max. number of iterations in the underlying
35  /// IterativeLinearSolver class.
36  virtual void complex_smoother_solve(const Vector<DoubleVector>& rhs,
37  Vector<DoubleVector>& result)=0;
38 
39 
40  /// Setup the smoother for the matrix specified by the pointer
41  virtual void complex_smoother_setup(Vector<CRDoubleMatrix*> matrix_pt)=0;
42 
43  /// \short Helper function to calculate a complex matrix-vector product.
44  /// Assumes the matrix has been provided as a Vector of length two; the
45  /// first entry containing the real part of the system matrix and the
46  /// second entry containing the imaginary part
48  const Vector<DoubleVector>& x,
50  {
51 #ifdef PARANOID
52  // PARANOID check - Make sure the input matrix has the right size
53  if (matrices_pt.size()!=2)
54  {
55  // Create an output stream
56  std::ostringstream error_message_stream;
57 
58  // Create the error message
59  error_message_stream << "Can only deal with two matrices. You have "
60  << matrices_pt.size()
61  << " matrices." << std::endl;
62 
63  // Throw an error
64  throw OomphLibError(error_message_stream.str(),
65  OOMPH_CURRENT_FUNCTION,
66  OOMPH_EXCEPTION_LOCATION);
67  }
68  // PARANOID check - Make sure the vector x has the right size
69  if (x.size()!=2)
70  {
71  // Create an output stream
72  std::ostringstream error_message_stream;
73 
74  // Create the error message
75  error_message_stream << "Can only deal with two input vectors. You have "
76  << x.size()
77  << " vectors." << std::endl;
78 
79  // Throw an error
80  throw OomphLibError(error_message_stream.str(),
81  OOMPH_CURRENT_FUNCTION,
82  OOMPH_EXCEPTION_LOCATION);
83  }
84  // PARANOID check - Make sure the vector soln has the right size
85  if (soln.size()!=2)
86  {
87  // Create an output stream
88  std::ostringstream error_message_stream;
89 
90  // Create the error message
91  error_message_stream << "Can only deal with two output vectors. You have "
92  << soln.size()
93  << " output vectors." << std::endl;
94 
95  // Throw an error
96  throw OomphLibError(error_message_stream.str(),
97  OOMPH_CURRENT_FUNCTION,
98  OOMPH_EXCEPTION_LOCATION);
99  }
100 #endif
101 
102  //-----------------------------------------------------------------------
103  // Suppose we have a complex matrix, A, and two complex vectors, x and
104  // soln. We wish to compute the product A*x=soln (note, * does not mean
105  // we are using complex conjugates here, it is simply used to indicate
106  // a multiplication). To do this we must make use of the fact that we
107  // possess the real and imaginary separately. As a result, it is computed
108  // using:
109  // soln = A*x,
110  // = (A_r + i*A_c)*(x_r + i*x_c),
111  // = [A_r*x_r - A_c*x_c] + i*[A_r*x_c + A_c*x_r],
112  // ==> real(soln) = A_r*x_r - A_c*x_c,
113  // & imag(soln) = A_r*x_c + A_c*x_r,
114  // where the subscripts _r and _c are used to identify the real and
115  // imaginary part, respectively.
116  //-----------------------------------------------------------------------
117 
118  // Store the value of A_r*x_r in the real part of soln
119  matrices_pt[0]->multiply(x[0],soln[0]);
120 
121  // Store the value of A_r*x_c in the imaginary part of soln
122  matrices_pt[0]->multiply(x[1],soln[1]);
123 
124  // Create a temporary vector
125  DoubleVector temp(this->distribution_pt(),0.0);
126 
127  // Calculate the value of A_c*x_c
128  matrices_pt[1]->multiply(x[1],temp);
129 
130  // Subtract the value of temp from soln[0] to get the real part of soln
131  soln[0]-=temp;
132 
133  // Calculate the value of A_c*x_r
134  matrices_pt[1]->multiply(x[0],temp);
135 
136  // Add the value of temp to soln[1] to get the imaginary part of soln
137  soln[1]+=temp;
138  } // End of complex_matrix_multiplication
139 
140  /// \short Self-test to check that all the dimensions of the inputs to
141  /// solve helper are consistent and everything that needs to be built, is.
142  template <typename MATRIX>
143  void check_validity_of_solve_helper_inputs(CRDoubleMatrix* const &real_matrix_pt,
144  CRDoubleMatrix* const &imag_matrix_pt,
145  const Vector<DoubleVector>& rhs,
146  Vector<DoubleVector>& solution,
147  const double& n_dof);
148 
149  protected:
150 
151  /// \short When a derived class object is being used as a smoother in
152  /// the MG algorithm the residual norm
153  /// does not need to be calculated. This boolean is used as a flag to
154  /// indicate this in solve_helper(...)
156  };
157 
158 //==================================================================
159 /// \short Self-test to be called inside solve_helper to ensure
160 /// that all inputs are consistent and everything that needs to
161 /// be built, is.
162 //==================================================================
163  template<typename MATRIX>
166  CRDoubleMatrix* const &imag_matrix_pt,
167  const Vector<DoubleVector>& rhs,
168  Vector<DoubleVector>& solution,
169  const double& n_dof)
170  {
171  // Number of dof types should be 2 (real & imaginary)
172  unsigned n_dof_types=2;
173 
174  // Create a vector to hold the matrices
175  Vector<CRDoubleMatrix*> matrix_storage_pt(2,0);
176 
177  // Assign the first entry in matrix_storage_pt
178  matrix_storage_pt[0]=real_matrix_pt;
179 
180  // Assign the second entry in matrix_storage_pt
181  matrix_storage_pt[1]=imag_matrix_pt;
182 
183  // Loop over the real and imaginary parts
184  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
185  {
186  // Check if the matrix is distributable. If it is then it should
187  // not be distributed
188  if (dynamic_cast<DistributableLinearAlgebraObject*>
189  (matrix_storage_pt[dof_type])!=0)
190  {
191  if (dynamic_cast<DistributableLinearAlgebraObject*>
192  (matrix_storage_pt[dof_type])->distributed())
193  {
194  std::ostringstream error_message_stream;
195  error_message_stream << "The matrix must not be distributed.";
196  throw OomphLibError(error_message_stream.str(),
197  OOMPH_CURRENT_FUNCTION,
198  OOMPH_EXCEPTION_LOCATION);
199  }
200  }
201  // Check that this rhs distribution is setup
202  if (!(rhs[dof_type].built()))
203  {
204  std::ostringstream error_message_stream;
205  error_message_stream << "The rhs vector distribution must be setup.";
206  throw OomphLibError(error_message_stream.str(),
207  OOMPH_CURRENT_FUNCTION,
208  OOMPH_EXCEPTION_LOCATION);
209  }
210  // Check that the rhs has the right number of global rows
211  if (rhs[dof_type].nrow()!=n_dof)
212  {
213  std::ostringstream error_message_stream;
214  error_message_stream << "RHS does not have the same dimension as the "
215  << "linear system";
216  throw OomphLibError(error_message_stream.str(),
217  OOMPH_CURRENT_FUNCTION,
218  OOMPH_EXCEPTION_LOCATION);
219  }
220  // Check that the rhs is not distributed
221  if (rhs[dof_type].distribution_pt()->distributed())
222  {
223  std::ostringstream error_message_stream;
224  error_message_stream << "The rhs vector must not be distributed.";
225  throw OomphLibError(error_message_stream.str(),
226  OOMPH_CURRENT_FUNCTION,
227  OOMPH_EXCEPTION_LOCATION);
228  }
229  // Check that if the result is setup it matches the distribution
230  // of the rhs
231  if (solution[dof_type].built())
232  {
233  if (!(*rhs[dof_type].distribution_pt()==
234  *solution[dof_type].distribution_pt()))
235  {
236  std::ostringstream error_message_stream;
237  error_message_stream << "If the result distribution is setup then it "
238  << "must be the same as the rhs distribution";
239  throw OomphLibError(error_message_stream.str(),
240  OOMPH_CURRENT_FUNCTION,
241  OOMPH_EXCEPTION_LOCATION);
242  }
243  } // if ((solution[0].built())||(solution[1].built()))
244  } // for (unsigned dof_type=0;dof_type<n_dof_types;dof_type)
245  } // End of check_validity_of_solve_helper_inputs
246 
247 
248 ///////////////////////////////////////////////////////////////////////
249 ///////////////////////////////////////////////////////////////////////
250 ///////////////////////////////////////////////////////////////////////
251 
252 
253 //=========================================================================
254 /// Damped Jacobi "solver" templated by matrix type. The "solver"
255 /// exists in many different incarnations: It's an IterativeLinearSolver,
256 /// and a Smoother, all of which use the same basic iteration.
257 //=========================================================================
258  template<typename MATRIX>
260  {
261 
262  public:
263 
264  /// Constructor (empty)
265  ComplexDampedJacobi(const double& omega=0.5) :
266  Matrix_can_be_deleted(true),
267  Matrix_real_pt(0),
268  Matrix_imag_pt(0),
269  Omega(omega)
270  {};
271 
272  /// Empty destructor
274  {
275  // Run clean_up_memory
276  clean_up_memory();
277  } // End of ~ComplexDampedJacobi
278 
279  /// Cleanup data that's stored for resolve (if any has been stored)
281  {
282  // If the real matrix pointer isn't null AND we're allowed to delete
283  // the matrix which is only when we create the matrix ourselves
284  if ((Matrix_real_pt!=0) && (Matrix_can_be_deleted))
285  {
286  // Delete the matrix
287  delete Matrix_real_pt;
288 
289  // Assign the associated pointer the value NULL
290  Matrix_real_pt=0;
291  }
292 
293  // If the real matrix pointer isn't null AND we're allowed to delete
294  // the matrix which is only when we create the matrix ourselves
295  if ((Matrix_imag_pt!=0) && (Matrix_can_be_deleted))
296  {
297  // Delete the matrix
298  delete Matrix_imag_pt;
299 
300  // Assign the associated pointer the value NULL
301  Matrix_imag_pt=0;
302  }
303  } // End of clean_up_memory
304 
305  /// \short Broken copy constructor
307  {
308  BrokenCopy::broken_copy("ComplexDampedJacobi");
309  }
310 
311  /// \short Broken assignment operator
313  {
314  BrokenCopy::broken_assign("ComplexDampedJacobi");
315  }
316 
317  /// \short Function to calculate the value of Omega by passing in the
318  /// value of k and h [see Elman et al. "A multigrid method enhanced by
319  /// Krylov subspace iteration for discrete Helmholtz equations", p.1303]
320  void calculate_omega(const double& k,const double& h)
321  {
322  // Create storage for the parameter kh
323  double kh=k*h;
324 
325  // Store the value of pi
326  double pi=MathematicalConstants::Pi;
327 
328  // Calculate the value of Omega
329  double omega=(12.0-4.0*pow(kh,2.0))/(18.0-3.0*pow(kh,2.0));
330 
331  // Set the tolerance for how close omega can be to 0
332  double tolerance=1.0e-03;
333 
334  // Only store the value of omega if it lies in the range (0,1]. If it
335  // isn't it will produce a divergent scheme. Note, to avoid letting
336  // omega become too small we make sure it is greater than some tolerance
337  if ((omega>tolerance) && !(omega>1))
338  {
339  // Since omega lies in the specified range, store it
340  Omega=omega;
341  }
342  // On the coarsest grids: if the wavenumber is greater than pi and
343  // kh>2cos(pi*h/2) then we can choose omega from the range (0,omega_2)
344  // where omega_2 is defined in [Elman et al."A multigrid method
345  // enhanced by Krylov subspace iteration for discrete Helmholtz
346  // equations", p.1295]
347  else if ((k>pi) && (kh>2*cos(pi*h/2)))
348  {
349  // Calculate the numerator of omega_2
350  double omega_2=(2.0-pow(kh,2.0));
351 
352  // Divide by the denominator of the fraction
353  omega_2/=(2.0*pow(sin(pi*h/2),2.0)-0.5*pow(kh,2.0));
354 
355  // If 2/3 lies in the range (0,omega_2), use it
356  if (omega_2>(2.0/3.0))
357  {
358  // Assign Omega the damping factor used for the Poisson equation
359  Omega=2.0/3.0;
360  }
361  // If omega_2 is less than 2/3 use the midpoint of (tolerance,omega_2)
362  else
363  {
364  // Set the value of Omega
365  Omega=0.5*(tolerance+omega_2);
366  }
367  }
368  // Since the value of kh must be fairly large, make the value of
369  // omega small to compensate
370  else
371  {
372  // Since kh doesn't lie in the chosen range, set it to some small value
373  Omega=0.2;
374  }
375  } // End of calculate_omega
376 
377  /// Get access to the value of Omega (lvalue)
378  double& omega()
379  {
380  // Return the value of Omega
381  return Omega;
382  } // End of omega
383 
384  /// Setup: Pass pointer to the matrix and store in cast form
386  {
387  // Assume the matrices have been passed in from the outside so we must
388  // not delete it. This is needed to avoid pre- and post-smoothers
389  // deleting the same matrix in the MG solver. If this was originally
390  // set to TRUE then this will be sorted out in the other functions
391  // from which this was called
392  Matrix_can_be_deleted=false;
393 
394  // Assign the real matrix pointers
395  Matrix_real_pt=helmholtz_matrix_pt[0];
396 
397  // Assign the imaginary matrix pointers
398  Matrix_imag_pt=helmholtz_matrix_pt[1];
399 
400 #ifdef PARANOID
401  // PARANOID check that if the matrix is distributable. If it is not then
402  // it should not be distributed
403  if (Matrix_real_pt->nrow()!=Matrix_imag_pt->nrow())
404  {
405  std::ostringstream error_message_stream;
406  error_message_stream << "Incompatible real and complex matrix sizes.";
407  throw OomphLibError(error_message_stream.str(),
408  OOMPH_CURRENT_FUNCTION,
409  OOMPH_EXCEPTION_LOCATION);
410  }
411 #endif
412 
413  //--------------------------------------------------------------------
414  // We need the matrix diagonals to calculate inv(D) (where D is the
415  // diagonal of A) as it remains the same for each use of the iterative
416  // scheme. To avoid unnecessary computations we store it now so it can
417  // simply be called in each iteration.
418  //--------------------------------------------------------------------
419 
420  // Grab the diagonal entries of the real part of the system matrix
421  Matrix_diagonal_real=Matrix_real_pt->diagonal_entries();
422 
423  // Grab the diagonal entries of the imaginary part of the system matrix
424  Matrix_diagonal_imag=Matrix_imag_pt->diagonal_entries();
425 
426  // Find the number of entries in the vectors containing the diagonal entries
427  // of both the real and the imaginary matrix
428  unsigned n_dof=Matrix_diagonal_real.size();
429 
430  // Make a dummy vector to store the entries of Matrix_diagonal_inv_sq
431  Matrix_diagonal_inv_sq.resize(n_dof,0.0);
432 
433  // Create a helper variable to store A_r(j,j), for some j
434  double dummy_r;
435 
436  // Create a helper variable to store A_c(j,j), for some j
437  double dummy_c;
438 
439  // Loop over the entries of Matrix_diagonal_inv_sq and set the i-th
440  // entry to 1/(A_r(i,i)^2 + A_c(i,i)^2)
441  for (unsigned i=0;i<n_dof;i++)
442  {
443  // Store the value of A_r(i,i)
444  dummy_r=Matrix_diagonal_real[i];
445 
446  // Store the value of A_c(i,i)
447  dummy_c=Matrix_diagonal_imag[i];
448 
449  // Store the value of 1/(A_r(i,i)^2 + A_c(i,i)^2)
450  Matrix_diagonal_inv_sq[i]=1.0/(dummy_r*dummy_r+dummy_c*dummy_c);
451  }
452  } // End of complex_smoother_setup
453 
454  /// \short The smoother_solve function performs fixed number of iterations
455  /// on the system A*result=rhs. The number of (smoothing) iterations is
456  /// the same as the max. number of iterations in the underlying
457  /// IterativeLinearSolver class.
459  Vector<DoubleVector>& solution)
460  {
461  // If you use a smoother but you don't want to calculate the residual
462  Use_as_smoother=true;
463 
464  // Call the helper function
465  complex_solve_helper(rhs,solution);
466  } // End of complex_smoother_solve
467 
468  /// \short Use damped Jacobi iteration as an IterativeLinearSolver:
469  /// This obtains the Jacobian matrix J and the residual vector r
470  /// (needed for the Newton method) from the problem's get_jacobian
471  /// function and returns the result of Jx=r.
472  void solve(Problem* const& problem_pt, DoubleVector& result)
473  {
474  BrokenCopy::broken_assign("ComplexDampedJacobi");
475  }
476 
477  /// Number of iterations taken
478  unsigned iterations() const
479  {
480  return Iterations;
481  }
482 
483  private:
484 
485  /// \short This is where the actual work is done
486  void complex_solve_helper(const Vector<DoubleVector>& rhs,
487  Vector<DoubleVector>& solution);
488 
489  /// \short Boolean flag to indicate if the matrices pointed to by
490  /// Matrix_real_pt and Matrix_imag_pt can be deleted.
492 
493  /// Pointer to the real part of the system matrix
495 
496  /// Pointer to the real part of the system matrix
498 
499  /// Vector containing the diagonal entries of A_r (real(A))
501 
502  /// Vector containing the diagonal entries of A_c (imag(A))
504 
505  /// Vector whose j'th entry is given by 1/(A_r(j,j)^2 + A_c(j,j)^2)
507 
508  /// Number of iterations taken
509  unsigned Iterations;
510 
511  /// Damping factor
512  double Omega;
513 
514  };
515 
516  //======================================================================
517  /// \short This is where the actual work is done.
518  //======================================================================
519  template<typename MATRIX>
522  Vector<DoubleVector>& solution)
523  {
524  // Get number of dofs
525  unsigned n_dof=Matrix_real_pt->nrow();
526 
527 #ifdef PARANOID
528  // Upcast the matrix to the appropriate type
529  CRDoubleMatrix* tmp_rmatrix_pt=dynamic_cast<CRDoubleMatrix*>(Matrix_real_pt);
530 
531  // Upcast the matrix to the appropriate type
532  CRDoubleMatrix* tmp_imatrix_pt=dynamic_cast<CRDoubleMatrix*>(Matrix_imag_pt);
533 
534  // PARANOID Run the self-tests to check the inputs are correct
535  this->check_validity_of_solve_helper_inputs<MATRIX>(tmp_rmatrix_pt,
536  tmp_imatrix_pt,
537  rhs,solution,n_dof);
538 
539  // We don't need the real matrix pointer any more
540  tmp_rmatrix_pt=0;
541 
542  // We don't need the imaginary matrix pointer any more
543  tmp_imatrix_pt=0;
544 #endif
545 
546  // Setup the solution if it is not
547  if ((!solution[0].distribution_pt()->built())||
548  (!solution[1].distribution_pt()->built()))
549  {
550  solution[0].build(rhs[0].distribution_pt(),0.0);
551  solution[1].build(rhs[1].distribution_pt(),0.0);
552  }
553 
554  // Initialise timer
555  double t_start=TimingHelpers::timer();
556 
557  // Copy the real and imaginary part of the solution vector
558  DoubleVector x_real(solution[0]);
559  DoubleVector x_imag(solution[1]);
560 
561  // Copy the real and imaginary part of the RHS vector
562  DoubleVector rhs_real(rhs[0]);
563  DoubleVector rhs_imag(rhs[1]);
564 
565  // Create storage for the real and imaginary part of the constant term
566  DoubleVector constant_term_real(this->distribution_pt(),0.0);
567  DoubleVector constant_term_imag(this->distribution_pt(),0.0);
568 
569  // Create storage for the real and imaginary part of the residual vector.
570  // These aren't used/built if we're inside the multigrid solver
571  DoubleVector residual_real;
572  DoubleVector residual_imag;
573 
574  // Create storage for the norm of the real and imaginary parts of the
575  // residual vector. These aren't used if we're inside the multigrid
576  // solver
577  double res_real_norm=0.0;
578  double res_imag_norm=0.0;
579 
580  // Variable to hold the current residual norm. This isn't used if
581  // we're inside the multigrid solver
582  double norm_res=0.0;
583 
584  // Variables to hold the initial residual norm. This isn't used if
585  // we're inside the multigrid solver
586  double norm_f=0.0;
587 
588  // Initialise the value of Iterations
589  Iterations=0;
590 
591  //------------------------------------------------------------------------
592  // Initial guess isn't necessarily zero (restricted solution from finer
593  // grids) therefore both x and the residual need to be assigned values
594  // from inputs. The constant term at the end of the stationary iteration
595  // is also calculated here since it does not change at all throughout the
596  // iterative process:
597  //------------------------------------------------------------------------
598 
599  // Loop over all the entries of each vector
600  for(unsigned i=0;i<n_dof;i++)
601  {
602  // Calculate the numerator of the i'th entry in the real component of
603  // the constant term
604  constant_term_real[i]=(Matrix_diagonal_real[i]*rhs_real[i]+
605  Matrix_diagonal_imag[i]*rhs_imag[i]);
606 
607  // Divide by the denominator
608  constant_term_real[i]*=Matrix_diagonal_inv_sq[i];
609 
610  // Scale by the damping factor
611  constant_term_real[i]*=Omega;
612 
613  // Calculate the numerator of the i'th entry in the imaginary component of
614  // the constant term
615  constant_term_imag[i]=(Matrix_diagonal_real[i]*rhs_imag[i]-
616  Matrix_diagonal_imag[i]*rhs_real[i]);
617 
618  // Divide by the denominator
619  constant_term_imag[i]*=Matrix_diagonal_inv_sq[i];
620 
621  // Scale by the damping factor
622  constant_term_imag[i]*=Omega;
623  }
624 
625  // Create 4 temporary vectors to store the various matrix-vector products
626  // required. The appropriate combination has been appended to the name. For
627  // instance, the product A_r*x_c corresponds to temp_vec_rc (real*imag)
628  DoubleVector temp_vec_rr(this->distribution_pt(),0.0);
629  DoubleVector temp_vec_cc(this->distribution_pt(),0.0);
630  DoubleVector temp_vec_rc(this->distribution_pt(),0.0);
631  DoubleVector temp_vec_cr(this->distribution_pt(),0.0);
632 
633  // Calculate the different combinations of A*x (or A*e depending on the
634  // level in the heirarchy) in the complex case (i.e. A_r*x_r, A_c*x_c,
635  // A_r*x_c and A_c*x_r)
636  Matrix_real_pt->multiply(x_real,temp_vec_rr);
637  Matrix_imag_pt->multiply(x_imag,temp_vec_cc);
638  Matrix_real_pt->multiply(x_imag,temp_vec_rc);
639  Matrix_imag_pt->multiply(x_real,temp_vec_cr);
640 
641  //---------------------------------------------------------------------------
642  // Calculating the residual r=b-Ax in the complex case requires more care
643  // than the real case. The correct calculation can be derived rather easily.
644  // Consider the splitting of A, x and b into their complex components:
645  // r = b - A*x
646  // = (b_r + i*b_c) - (A_r + i*A_c)*(x_r + i*x_c)
647  // = [b_r - A_r*x_r + A_c*x_c] + i*[b_c - A_r*x_c - A_c*x_r]
648  // ==> real(r) = b_r - A_r*x_r + A_c*x_c
649  // & imag(r) = b_c - A_r*x_c - A_c*x_r.
650  //---------------------------------------------------------------------------
651 
652  // Calculate the residual only if we're not inside the multigrid solver
653  if (!Use_as_smoother)
654  {
655  // Create storage for the real and imaginary part of the residual vector
656  residual_real.build(this->distribution_pt(),0.0);
657  residual_imag.build(this->distribution_pt(),0.0);
658 
659  // Real part of the residual:
660  residual_real=rhs_real;
661  residual_real-=temp_vec_rr;
662  residual_real+=temp_vec_cc;
663 
664  // Imaginary part of the residual:
665  residual_imag=rhs_imag;
666  residual_imag-=temp_vec_rc;
667  residual_imag-=temp_vec_cr;
668 
669  // Calculate the 2-norm (noting that the 2-norm of a complex vector a of
670  // length n is simply the square root of the sum of the squares of each
671  // real and imaginary component). That is:
672  // \f[
673  // \|a\|_2^2=\sum_{i=0}^{i=n-1}\real(a[i])^2+\imag(a[i])^2.
674  // \f]
675  // can be written as:
676  // \f[
677  // \|a\|_2^2=\|\real(a)\|_2^2+\|\imag(a)\|_2^2.
678  // \f]
679  double res_real_norm=residual_real.norm();
680  double res_imag_norm=residual_imag.norm();
681  double norm_res=sqrt(res_real_norm*res_real_norm+
682  res_imag_norm*res_imag_norm);
683 
684  // If required will document convergence history to screen
685  // or file (if stream is open)
687  {
688  if (!Output_file_stream.is_open())
689  {
690  oomph_info << Iterations << " " << norm_res << std::endl;
691  }
692  else
693  {
694  Output_file_stream << Iterations << " " << norm_res <<std::endl;
695  }
696  } // if (Doc_convergence_history)
697  } // if (!Use_as_smoother)
698 
699  // Two temporary vectors to store the value of A_r*x_r - A_c*x_c and
700  // A_c*x_r + A_r*x_c in each iteration
701  DoubleVector temp_vec_real(this->distribution_pt(),0.0);
702  DoubleVector temp_vec_imag(this->distribution_pt(),0.0);
703 
704  // Calculate A_r*x_r - A_c*x_c
705  temp_vec_real=temp_vec_rr;
706  temp_vec_real-=temp_vec_cc;
707 
708  // Calculate A_c*x_r + A_r*x_c
709  temp_vec_imag=temp_vec_cr;
710  temp_vec_imag+=temp_vec_rc;
711 
712  // Outermost loop: Run up to Max_iter times
713  for (unsigned iter_num=0;iter_num<Max_iter;iter_num++)
714  {
715  // Loop over each degree of freedom and update
716  // the current approximation
717  for (unsigned i=0;i<n_dof;i++)
718  {
719  // Make a couple of dummy variables to help with calculations
720  double dummy_r=0.0;
721  double dummy_c=0.0;
722 
723  // Calculate one part of the correction term (real)
724  dummy_r=(Matrix_diagonal_real[i]*temp_vec_real[i]+
725  Matrix_diagonal_imag[i]*temp_vec_imag[i]);
726 
727  // Calculate one part of the correction term (imaginary)
728  dummy_c=(Matrix_diagonal_real[i]*temp_vec_imag[i]-
729  Matrix_diagonal_imag[i]*temp_vec_real[i]);
730 
731  // Scale by Omega/([A(i,i)_r]^2+[A(i,i)_c]^2)
732  dummy_r*=Omega*Matrix_diagonal_inv_sq[i];
733  dummy_c*=Omega*Matrix_diagonal_inv_sq[i];
734 
735  // Update the value of x_real
736  x_real[i]-=dummy_r;
737  x_imag[i]-=dummy_c;
738  }
739 
740  // Update the value of x (or e depending on the level in the heirarchy)
741  x_real+=constant_term_real;
742  x_imag+=constant_term_imag;
743 
744  // Calculate the different combinations of A*x (or A*e depending on the
745  // level in the heirarchy) in the complex case (i.e. A_r*x_r, A_c*x_c,
746  // A_r*x_c and A_c*x_r)
747  Matrix_real_pt->multiply(x_real,temp_vec_rr);
748  Matrix_imag_pt->multiply(x_imag,temp_vec_cc);
749  Matrix_real_pt->multiply(x_imag,temp_vec_rc);
750  Matrix_imag_pt->multiply(x_real,temp_vec_cr);
751 
752  // Calculate A_r*x_r - A_c*x_c
753  temp_vec_real=temp_vec_rr;
754  temp_vec_real-=temp_vec_cc;
755 
756  // Calculate A_c*x_r + A_r*x_c
757  temp_vec_imag=temp_vec_cr;
758  temp_vec_imag+=temp_vec_rc;
759 
760  // Calculate the residual only if we're not inside the multigrid solver
761  if (!Use_as_smoother)
762  {
763  // Calculate the residual
764  residual_real=rhs_real;
765  residual_real-=temp_vec_rr;
766  residual_real+=temp_vec_cc;
767 
768  // Calculate the imaginary part of the residual vector
769  residual_imag=rhs_imag;
770  residual_imag-=temp_vec_rc;
771  residual_imag-=temp_vec_cr;
772 
773  // Calculate the 2-norm using the method mentioned previously
774  res_real_norm=residual_real.norm();
775  res_imag_norm=residual_imag.norm();
776  norm_res=sqrt(res_real_norm*res_real_norm+
777  res_imag_norm*res_imag_norm)/norm_f;
778 
779  // If required, this will document convergence history to
780  // screen or file (if the stream is open)
782  {
783  if (!Output_file_stream.is_open())
784  {
785  oomph_info << Iterations << " " << norm_res << std::endl;
786  }
787  else
788  {
789  Output_file_stream << Iterations << " " << norm_res << std::endl;
790  }
791  } // if (Doc_convergence_history)
792 
793  // Check the tolerance only if the residual norm is being computed
794  if (norm_res<Tolerance)
795  {
796  // Break out of the for-loop
797  break;
798  }
799  } // if (!Use_as_smoother)
800  } // for (unsigned iter_num=0;iter_num<Max_iter;iter_num++)
801 
802  // Calculate the residual only if we're not inside the multigrid solver
803  if (!Use_as_smoother)
804  {
805  // If time documentation is enabled
806  if(Doc_time)
807  {
808  oomph_info << "\n(Complex) damped Jacobi converged. Residual norm: "
809  << norm_res
810  << "\nNumber of iterations to convergence: "
811  << Iterations << "\n" << std::endl;
812  }
813  } // if (!Use_as_smoother)
814 
815  // Copy the solution into the solution vector
816  solution[0]=x_real;
817  solution[1]=x_imag;
818 
819  // Doc time for solver
820  double t_end=TimingHelpers::timer();
821  Solution_time=t_end-t_start;
822  if(Doc_time)
823  {
824  oomph_info << "Time for solve with (complex) damped Jacobi [sec]: "
825  << Solution_time << std::endl;
826  }
827 
828  // If the solver failed to converge and the user asked for an error if
829  // this happened
830  if((Iterations>Max_iter-1)&&(Throw_error_after_max_iter))
831  {
832  std::string error_message="Solver failed to converge and you requested ";
833  error_message+="an error on convergence failures.";
834  throw OomphLibError(error_message,
835  OOMPH_EXCEPTION_LOCATION,
836  OOMPH_CURRENT_FUNCTION);
837  }
838  } // End of complex_solve_helper function
839 
840 //======================================================================
841 /// \short The GMRES method rewritten for complex matrices
842 //======================================================================
843  template<typename MATRIX>
845  {
846 
847  public:
848 
849  /// Constructor
851  Iterations(0),
852  Matrices_storage_pt(0),
853  Resolving(false),
854  Matrix_can_be_deleted(true)
855  {} // End of ComplexGMRES constructor
856 
857  /// Empty destructor
859  {
860  // Run clean_up_memory
861  clean_up_memory();
862  } // End of ~ComplexGMRES
863 
864  /// Broken copy constructor
866  {
867  BrokenCopy::broken_copy("ComplexGMRES");
868  } // End of ComplexGMRES (copy constructor)
869 
870  /// Broken assignment operator
871  void operator=(const ComplexGMRES&)
872  {
873  BrokenCopy::broken_assign("ComplexGMRES");
874  } // End of operator= (assignment operator)
875 
876  /// Overload disable resolve so that it cleans up memory too
878  {
879  // Disable resolve using the LinearSolver function
881 
882  // Clean up anything kept in memory
883  clean_up_memory();
884  } // End of disable resolve
885 
886  /// \short Solver: Takes pointer to problem and returns the results vector
887  /// which contains the solution of the linear system defined by
888  /// the problem's fully assembled Jacobian and residual vector.
889  void solve(Problem* const &problem_pt, DoubleVector &result)
890  {
891  // Write the error message into a string
892  std::string error_message="Solve function for class\n\n";
893  error_message+="ComplexGMRES\n\n";
894  error_message+="is deliberately broken to avoid the accidental \n";
895  error_message+="use of the inappropriate C++ default. If you \n";
896  error_message+="really need one for this class, write it yourself...\n";
897 
898  // Throw an error
899  throw OomphLibError(error_message,
900  OOMPH_CURRENT_FUNCTION,
901  OOMPH_EXCEPTION_LOCATION);
902  } // End of solve
903 
904  /// \short Linear-algebra-type solver: Takes pointer to a matrix
905  /// and rhs vector and returns the solution of the linear system
906  /// Call the broken base-class version. If you want this, please
907  /// implement it
908  void solve(DoubleMatrixBase* const &matrix_pt,
909  const Vector<double> &rhs,
910  Vector<double> &result)
911  {
912  LinearSolver::solve(matrix_pt,rhs,result);
913  } // End of solve
914 
915  /// Number of iterations taken
916  unsigned iterations() const
917  {
918  // Return the number of iterations used
919  return Iterations;
920  } // End of iterations
921 
922  /// Setup: Pass pointer to the matrix and store in cast form
924  {
925  // Assume the matrices have been passed in from the outside so we must
926  // not delete it. This is needed to avoid pre- and post-smoothers
927  // deleting the same matrix in the MG solver. If this was originally
928  // set to TRUE then this will be sorted out in the other functions
929  // from which this was called
930  Matrix_can_be_deleted=false;
931 
932 #ifdef PARANOID
933  // PARANOID check - Make sure the input has the right number of matrices
934  if (helmholtz_matrix_pt.size()!=2)
935  {
936  std::ostringstream error_message_stream;
937  error_message_stream << "Can only deal with two matrices. You have "
938  << helmholtz_matrix_pt.size()
939  << " matrices." << std::endl;
940 
941  // Throw an error
942  throw OomphLibError(error_message_stream.str(),
943  OOMPH_CURRENT_FUNCTION,
944  OOMPH_EXCEPTION_LOCATION);
945  }
946 #endif
947 
948  // Resize the storage for the system matrices
949  Matrices_storage_pt.resize(2,0);
950 
951  // Assign the real matrix pointer
952  Matrices_storage_pt[0]=helmholtz_matrix_pt[0];
953 
954  // Assign the imaginary matrix pointers
955  Matrices_storage_pt[1]=helmholtz_matrix_pt[1];
956 
957 #ifdef PARANOID
958  // PARANOID check - Make sure that the constituent matrices have the same
959  // number of rows
960  if (Matrices_storage_pt[0]->nrow()!=Matrices_storage_pt[1]->nrow())
961  {
962  std::ostringstream error_message_stream;
963  error_message_stream << "Incompatible real and imag. matrix sizes.";
964  throw OomphLibError(error_message_stream.str(),
965  OOMPH_CURRENT_FUNCTION,
966  OOMPH_EXCEPTION_LOCATION);
967  }
968  // PARANOID check - Make sure that the constituent matrices have the same
969  // number of columns
970  if (Matrices_storage_pt[0]->ncol()!=Matrices_storage_pt[1]->ncol())
971  {
972  std::ostringstream error_message_stream;
973  error_message_stream << "Incompatible real and imag. matrix sizes.";
974  throw OomphLibError(error_message_stream.str(),
975  OOMPH_CURRENT_FUNCTION,
976  OOMPH_EXCEPTION_LOCATION);
977  }
978 #endif
979  } // End of complex_smoother_setup
980 
981  /// \short The smoother_solve function performs fixed number of iterations
982  /// on the system A*result=rhs. The number of (smoothing) iterations is
983  /// the same as the max. number of iterations in the underlying
984  /// IterativeLinearSolver class.
986  Vector<DoubleVector>& solution)
987  {
988  // If you use a smoother but you don't want to calculate the residual
989  Use_as_smoother=true;
990 
991  // Use the helper function where the work is actually done
992  complex_solve_helper(rhs,solution);
993  } // End of complex_smoother_solve
994 
995  private:
996 
997  /// Cleanup data that's stored for resolve (if any has been stored)
999  {
1000  // If the real matrix pointer isn't null AND we're allowed to delete
1001  // the matrix which is only when we create the matrix ourselves
1002  if ((Matrices_storage_pt[0]!=0)&&(Matrix_can_be_deleted))
1003  {
1004  // Delete the matrix
1005  delete Matrices_storage_pt[0];
1006 
1007  // Assign the associated pointer the value NULL
1008  Matrices_storage_pt[0]=0;
1009  }
1010 
1011  // If the real matrix pointer isn't null AND we're allowed to delete
1012  // the matrix which is only when we create the matrix ourselves
1013  if ((Matrices_storage_pt[1]!=0)&&(Matrix_can_be_deleted))
1014  {
1015  // Delete the matrix
1016  delete Matrices_storage_pt[1];
1017 
1018  // Assign the associated pointer the value NULL
1019  Matrices_storage_pt[1]=0;
1020  }
1021  } // End of clean_up_memory
1022 
1023  /// This is where the actual work is done
1024  void complex_solve_helper(const Vector<DoubleVector>& rhs,
1025  Vector<DoubleVector>& solution);
1026 
1027  /// Helper function to update the result vector
1028  void update(const unsigned& k,
1029  const Vector<Vector<std::complex<double> > >& hessenberg,
1030  const Vector<std::complex<double> >& s,
1031  const Vector<Vector<DoubleVector> >& v,
1033  {
1034  // Make a local copy of s
1036 
1037  //-----------------------------------------------------------------
1038  // The Hessenberg matrix should be an upper triangular matrix at
1039  // this point (although from its storage it would appear to be a
1040  // lower triangular matrix since the indexing has been reversed)
1041  // so finding the minimiser of J(y)=min||s-R_m*y|| where R_m is
1042  // the matrix R in the QR factorisation of the Hessenberg matrix.
1043  // Therefore, to obtain y we simply need to use a backwards
1044  // substitution. Note: The implementation here may appear to be
1045  // somewhat confusing as the indexing in the Hessenberg matrix is
1046  // reversed. This implementation of a backwards substitution does
1047  // not run along the columns of the triangular matrix but rather
1048  // up the rows.
1049  //-----------------------------------------------------------------
1050  // The outer loop is a loop over the columns of the Hessenberg matrix
1051  // since the indexing is reversed
1052  for (int i=int(k);i>=0;i--)
1053  {
1054  // Divide the i-th entry of y by the i-th diagonal entry of H
1055  y[i]/=hessenberg[i][i];
1056 
1057  // The inner loop is a loop over the rows of the Hessenberg matrix
1058  for (int j=i-1;j>=0;j--)
1059  {
1060  // Update the j-th entry of y
1061  y[j]-=hessenberg[i][j]*y[i];
1062  }
1063  } // for (int i=int(k);i>=0;i--)
1064 
1065  // Calculate the number of entries in x (simply use the real part as
1066  // both the real and imaginary part should have the same length)
1067  unsigned n_row=x[0].nrow();
1068 
1069  // We assume here that the vector x (which is passed in) is actually x_0
1070  // so we simply need to update its entries to calculate the solution, x
1071  // which is given by x=x_0+Vy.
1072  for (unsigned j=0;j<=k;j++)
1073  {
1074  // For fast access (real part)
1075  const double* vj_r_pt=v[j][0].values_pt();
1076 
1077  // For fast access (imaginary part)
1078  const double* vj_c_pt=v[j][1].values_pt();
1079 
1080  // Loop over the entries in x and update them
1081  for (unsigned i=0;i<n_row;i++)
1082  {
1083  // Update the real part of the i-th entry in x
1084  x[0][i]+=(vj_r_pt[i]*y[j].real())-(vj_c_pt[i]*y[j].imag());
1085 
1086  // Update the imaginary part of the i-th entry in x
1087  x[1][i]+=(vj_c_pt[i]*y[j].real())+(vj_r_pt[i]*y[j].imag());
1088  }
1089  } // for (unsigned j=0;j<=k;j++)
1090  } // End of update
1091 
1092  /// \short Helper function: Generate a plane rotation. This is done by
1093  /// finding the value of \f$ \cos(\theta) \f$ (i.e. cs) and the value of
1094  /// \f$ \sin(\theta) \f$ (i.e. sn) such that:
1095  /// \f[
1096  /// \begin{bmatrix}
1097  /// \overline{\cos\theta} & \overline{\sin\theta} \cr
1098  /// -\sin\theta & \cos\theta
1099  /// \end{bmatrix}
1100  /// \begin{bmatrix}
1101  /// dx
1102  /// \\ dy
1103  /// \end{bmatrix}
1104  /// =
1105  /// \begin{bmatrix}
1106  /// r
1107  /// \\ 0
1108  /// \end{bmatrix},
1109  /// \f]
1110  /// where \f$ r=\sqrt{pow(|dx|,2)+pow(|dy|,2)} \f$. The values of a and b
1111  /// are given by:
1112  /// The values of dx and dy are given by:
1113  /// \f[
1114  /// \cos\theta&=\dfrac{dx}{\sqrt{|dx|^2+|dy|^2}},
1115  /// \f]
1116  /// and
1117  /// \f[
1118  /// \sin\theta&=\dfrac{dy}{\sqrt{|dx|^2+|dy|^2}}.
1119  /// \f]
1120  /// Taken from: Saad Y."Iterative methods for sparse linear systems", p.193.
1121  /// We also check to see that sn is always a real (nonnegative) number. See
1122  /// pp.193-194 for an explanation.
1123  void generate_plane_rotation(std::complex<double>& dx,
1124  std::complex<double>& dy,
1125  std::complex<double>& cs,
1126  std::complex<double>& sn)
1127  {
1128  // If dy=0 then we do not need to apply a rotation
1129  if (dy==0.0)
1130  {
1131  // Using theta=0 gives cos(theta)=1
1132  cs=1.0;
1133 
1134  // Using theta=0 gives sin(theta)=0
1135  sn=0.0;
1136  }
1137  // If dx or dy is large using the original form of calculting cs and sn is
1138  // naive since this may overflow or underflow so instead we calculate
1139  // r=sqrt(pow(|dx|,2)+pow(|dy|,2)) using r=|dy|sqrt(1+pow(|dx|/|dy|,2)) if
1140  // |dy|>|dx| [see <A HREF=https://en.wikipedia.org/wiki/Hypot">Hypot</A>.].
1141  else if(std::abs(dy)>std::abs(dx))
1142  {
1143  // Since |dy|>|dx| calculate the ratio |dx|/|dy|
1144  std::complex<double> temp=dx/dy;
1145 
1146  // Calculate the value of sin(theta) using:
1147  // sin(theta)=dy/sqrt(pow(|dx|,2)+pow(|dy|,2))
1148  // =(dy/|dy|)/sqrt(1+pow(|dx|/|dy|,2)).
1149  sn=(dy/std::abs(dy))/sqrt(1.0+pow(std::abs(temp),2.0));
1150 
1151  // Calculate the value of cos(theta) using:
1152  // cos(theta)=dx/sqrt(pow(|dy|,2)+pow(|dx|,2))
1153  // =(dx/|dy|)/sqrt(1+pow(|dx|/|dy|,2))
1154  // =(dx/dy)*sin(theta).
1155  cs=temp*sn;
1156  }
1157  // Otherwise, we have |dx|>=|dy| so to, again, avoid overflow or underflow
1158  // calculate the values of cs and sn using the method above
1159  else
1160  {
1161  // Since |dx|>=|dy| calculate the ratio dy/dx
1162  std::complex<double> temp=dy/dx;
1163 
1164  // Calculate the value of cos(theta) using:
1165  // cos(theta)=dx/sqrt(pow(|dx|,2)+pow(|dy|,2))
1166  // =(dx/|dx|)/sqrt(1+pow(|dy|/|dx|,2)).
1167  cs=(dx/std::abs(dx))/sqrt(1.0+pow(std::abs(temp),2.0));
1168 
1169  // Calculate the value of sin(theta) using:
1170  // sin(theta)=dy/sqrt(pow(|dx|,2)+pow(|dy|,2))
1171  // =(dy/|dx|)/sqrt(1+pow(|dy|/|dx|,2))
1172  // =(dy/dx)*cos(theta).
1173  sn=temp*cs;
1174  }
1175 
1176  // Set the tolerance for sin(theta)
1177  double tolerance=1.0e-15;
1178 
1179  // Make sure sn is real and nonnegative (it should be!)
1180  if ((std::fabs(sn.imag())>tolerance)||(sn.real()<0))
1181  {
1182  // Create an output stream
1183  std::ostringstream error_message_stream;
1184 
1185  // Create an error message
1186  error_message_stream << "The value of sin(theta) is not real "
1187  << "and/or nonnegative. Value is: "
1188  << sn << std::endl;
1189 
1190  // Throw an error
1191  throw OomphLibError(error_message_stream.str(),
1192  OOMPH_CURRENT_FUNCTION,
1193  OOMPH_EXCEPTION_LOCATION);
1194  }
1195  } // End of generate_plane_rotation
1196 
1197  /// \short Helper function: Apply plane rotation. This is done using the
1198  /// update:
1199  /// \f[
1200  /// \begin{bmatrix}
1201  /// dx
1202  /// \\ dy
1203  /// \end{bmatrix}
1204  /// \leftarrow
1205  /// \begin{bmatrix}
1206  /// \overline{\cos\theta} & \overline{\sin\theta}
1207  /// \\ -\sin\theta & \cos\theta
1208  /// \end{bmatrix}
1209  /// \begin{bmatrix}
1210  /// dx
1211  /// \\ dy
1212  /// \end{bmatrix}.
1213  /// \f]
1214  /// Taken from: Saad Y."Iterative methods for sparse linear systems", p.193.
1215  void apply_plane_rotation(std::complex<double>& dx,
1216  std::complex<double>& dy,
1217  std::complex<double>& cs,
1218  std::complex<double>& sn)
1219  {
1220  // Calculate the value of dx but don't update it yet
1221  std::complex<double> temp=std::conj(cs)*dx+std::conj(sn)*dy;
1222 
1223  // Set the value of dy
1224  dy=-sn*dx+cs*dy;
1225 
1226  // Set the value of dx using the correct values of dx and dy
1227  dx=temp;
1228  } // End of apply_plane_rotation
1229 
1230  /// Number of iterations taken
1231  unsigned Iterations;
1232 
1233  /// Vector of pointers to the real and imaginary part of the system matrix
1235 
1236  /// \short Boolean flag to indicate if the solve is done in re-solve mode,
1237  /// bypassing setup of matrix and preconditioner
1239 
1240  /// \short Boolean flag to indicate if the real and imaginary system
1241  /// matrices can be deleted
1243  };
1244 
1245  //======================================================================
1246  /// \short This is where the actual work is done
1247  //======================================================================
1248  template<typename MATRIX>
1251  Vector<DoubleVector>& solution)
1252  {
1253  // Set the number of dof types (real and imaginary for this solver)
1254  unsigned n_dof_types=2;
1255 
1256  // Get the number of dofs (note, the total number of dofs in the problem
1257  // is 2*n_row but if the constituent vectors and matrices were stored in
1258  // complex objects there would only be (n_row) rows so we use that)
1259  unsigned n_row=Matrices_storage_pt[0]->nrow();
1260 
1261  // Make sure Max_iter isn't greater than n_dof. The user cannot use this
1262  // many iterations when using Krylov subspace methods
1263  if (Max_iter>n_row)
1264  {
1265  // Create an output string stream
1266  std::ostringstream error_message_stream;
1267 
1268  // Create the error message
1269  error_message_stream << "The maximum number of iterations cannot exceed "
1270  << "the number of rows in the problem."
1271  << "\nMaximum number of iterations: " << Max_iter
1272  << "\nNumber of rows: " << n_row
1273  << std::endl;
1274 
1275  // Throw the error message
1276  throw OomphLibError(error_message_stream.str(),
1277  OOMPH_CURRENT_FUNCTION,
1278  OOMPH_EXCEPTION_LOCATION);
1279  }
1280 
1281  // Loop over the real and imaginary parts
1282  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
1283  {
1284 #ifdef PARANOID
1285  // PARANOID check that if the matrix is distributable then it should not be
1286  // then it should not be distributed
1287  if (dynamic_cast<DistributableLinearAlgebraObject*>
1288  (Matrices_storage_pt[dof_type])!=0)
1289  {
1290  if (dynamic_cast<DistributableLinearAlgebraObject*>
1291  (Matrices_storage_pt[dof_type])->distributed())
1292  {
1293  std::ostringstream error_message_stream;
1294  error_message_stream << "The matrix must not be distributed.";
1295  throw OomphLibError(error_message_stream.str(),
1296  OOMPH_CURRENT_FUNCTION,
1297  OOMPH_EXCEPTION_LOCATION);
1298  }
1299  }
1300  // PARANOID check that this rhs distribution is setup
1301  if (!rhs[dof_type].built())
1302  {
1303  std::ostringstream error_message_stream;
1304  error_message_stream << "The rhs vector distribution must be setup.";
1305  throw OomphLibError(error_message_stream.str(),
1306  OOMPH_CURRENT_FUNCTION,
1307  OOMPH_EXCEPTION_LOCATION);
1308  }
1309  // PARANOID check that the rhs has the right number of global rows
1310  if(rhs[dof_type].nrow()!=n_row)
1311  {
1312  std::ostringstream error_message_stream;
1313  error_message_stream << "RHS does not have the same dimension as the"
1314  << " linear system";
1315  throw OomphLibError(error_message_stream.str(),
1316  OOMPH_CURRENT_FUNCTION,
1317  OOMPH_EXCEPTION_LOCATION);
1318  }
1319  // PARANOID check that the rhs is not distributed
1320  if (rhs[dof_type].distribution_pt()->distributed())
1321  {
1322  std::ostringstream error_message_stream;
1323  error_message_stream << "The rhs vector must not be distributed.";
1324  throw OomphLibError(error_message_stream.str(),
1325  OOMPH_CURRENT_FUNCTION,
1326  OOMPH_EXCEPTION_LOCATION);
1327  }
1328  // PARANOID check that if the result is setup it matches the distribution
1329  // of the rhs
1330  if (solution[dof_type].built())
1331  {
1332  if (!(*rhs[dof_type].distribution_pt()==
1333  *solution[dof_type].distribution_pt()))
1334  {
1335  std::ostringstream error_message_stream;
1336  error_message_stream << "If the result distribution is setup then it "
1337  << "must be the same as the rhs distribution";
1338  throw OomphLibError(error_message_stream.str(),
1339  OOMPH_CURRENT_FUNCTION,
1340  OOMPH_EXCEPTION_LOCATION);
1341  }
1342  } // if (solution[dof_type].built())
1343 #endif
1344  // Set up the solution distribution if it's not already distributed
1345  if (!solution[dof_type].built())
1346  {
1347  // Build the distribution
1348  solution[dof_type].build(this->distribution_pt(),0.0);
1349  }
1350  // Otherwise initialise all entries to zero
1351  else
1352  {
1353  // Initialise the entries in the k-th vector in solution to zero
1354  solution[dof_type].initialise(0.0);
1355  }
1356  } // for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
1357 
1358  // Start the solver timer
1359  double t_start=TimingHelpers::timer();
1360 
1361  // Storage for the relative residual
1362  double resid;
1363 
1364  // Initialise vectors (since these are not distributed vectors we template
1365  // one vector by the type std::complex<double> instead of using two vectors,
1366  // each templated by the type double
1367 
1368  // Vector, s, used in the minimisation problem: J(y)=min||s-R_m*y||
1369  // [see Saad Y."Iterative methods for sparse linear systems", p.176.]
1370  Vector<std::complex<double> > s(n_row+1,std::complex<double>(0.0,0.0));
1371 
1372  // Vector to store the value of cos(theta) when using the Givens rotation
1373  Vector<std::complex<double> > cs(n_row+1,std::complex<double>(0.0,0.0));
1374 
1375  // Vector to store the value of sin(theta) when using the Givens rotation
1376  Vector<std::complex<double> > sn(n_row+1,std::complex<double>(0.0,0.0));
1377 
1378  // Create a vector of DoubleVectors (this is a distributed vector so we have
1379  // to create two separate DoubleVector objects to cope with the arithmetic)
1380  Vector<DoubleVector> w(n_dof_types);
1381 
1382  // Build the distribution of both vectors
1383  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
1384  {
1385  // Build the distribution of the k-th constituent vector
1386  w[dof_type].build(this->distribution_pt(),0.0);
1387  }
1388 
1389  // Create a vector of DoubleVectors to store the RHS of b-Jx=Mr. We assume
1390  // both x=0 and that a preconditioner is not applied by which we deduce b=r
1391  Vector<DoubleVector> r(n_dof_types);
1392 
1393  // Build the distribution of both vectors
1394  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
1395  {
1396  // Build the distribution of the k-th constituent vector
1397  r[dof_type].build(this->distribution_pt(),0.0);
1398  }
1399 
1400  // Store the value of b (the RHS vector) in r
1401  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
1402  {
1403  // Build the distribution of the k-th constituent vector
1404  r[dof_type]=rhs[dof_type];
1405  }
1406 
1407  // Calculate the norm of the real part of r
1408  double norm_r=r[0].norm();
1409 
1410  // Calculate the norm of the imaginary part of r
1411  double norm_c=r[1].norm();
1412 
1413  // Compute norm(r)
1414  double normb=sqrt(pow(norm_r,2.0)+pow(norm_c,2.0));
1415 
1416  // Set the value of beta (the initial residual)
1417  double beta=normb;
1418 
1419  // Compute the initial relative residual. If the entries of the RHS vector
1420  // are all zero then set normb equal to one. This is because we divide the
1421  // value of the norm at later stages by normb and dividing by zero is not
1422  // definied
1423  if (normb==0.0)
1424  {
1425  // Set the value of normb
1426  normb=1.0;
1427  }
1428 
1429  // Calculate the ratio between the initial norm and the current norm.
1430  // Since we haven't completed an iteration yet this will simply be one
1431  // unless normb was zero, in which case resid will have value zero
1432  resid=beta/normb;
1433 
1434  // If required, will document convergence history to screen or file (if
1435  // stream open)
1437  {
1438  // If an output file which is open isn't provided then output to screen
1439  if (!Output_file_stream.is_open())
1440  {
1441  // Output the residual value to the screen
1442  oomph_info << 0 << " " << resid << std::endl;
1443  }
1444  // Otherwise, output to file
1445  else
1446  {
1447  // Output the residual value to file
1448  Output_file_stream << 0 << " " << resid <<std::endl;
1449  }
1450  } // if (Doc_convergence_history)
1451 
1452  // If the GMRES algorithm converges immediately
1453  if (resid<=Tolerance)
1454  {
1455  // If time documentation is enabled
1456  if(Doc_time)
1457  {
1458  // Notify the user
1459  oomph_info << "GMRES converged immediately. Normalised residual norm: "
1460  << resid << std::endl;
1461  }
1462 
1463  // Finish running the solver
1464  return;
1465  } // if (resid<=Tolerance)
1466 
1467  // Initialise a vector of orthogonal basis vectors
1469 
1470  // Resize the number of vectors needed
1471  v.resize(n_row+1);
1472 
1473  // Resize each Vector of DoubleVectors to store the real and imaginary
1474  // part of a given vector
1475  for (unsigned dof_type=0;dof_type<n_row+1;dof_type++)
1476  {
1477  // Create two DoubleVector objects in each Vector
1478  v[dof_type].resize(n_dof_types);
1479  }
1480 
1481  // Initialise the upper hessenberg matrix. Since we are not using
1482  // distributed vectors here, the algebra is best done using entries
1483  // of the type std::complex<double>. NOTE: For implementation purposes
1484  // the upper Hessenberg matrix indices are swapped so the matrix is
1485  // effectively transposed
1486  Vector<Vector<std::complex<double> > > hessenberg(n_row+1);
1487 
1488  // Build the zeroth basis vector
1489  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
1490  {
1491  // Build the k-th part of the zeroth vector. Here k=0 and k=1 correspond
1492  // to the real and imaginary part of the zeroth vector, respectively
1493  v[0][dof_type].build(this->distribution_pt(),0.0);
1494  }
1495 
1496  // Loop over the real and imaginary parts of v
1497  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
1498  {
1499  // For fast access
1500  double* v0_pt=v[0][dof_type].values_pt();
1501 
1502  // For fast access
1503  const double* r_pt=r[dof_type].values_pt();
1504 
1505  // Set the zeroth basis vector v[0] to r/beta
1506  for (unsigned i=0;i<n_row;i++)
1507  {
1508  // Assign the i-th entry of the zeroth basis vector
1509  v0_pt[i]=r_pt[i]/beta;
1510  }
1511  } // for (unsigned k=0;k<n_dof_types;k++)
1512 
1513  // Set the first entry in the minimisation problem RHS vector (is meant
1514  // to the vector beta*e_1 initially, where e_1 is the unit vector with
1515  // one in its first entry)
1516  s[0]=beta;
1517 
1518  // Compute the next step of the iterative scheme
1519  for (unsigned j=0;j<Max_iter;j++)
1520  {
1521  // Resize the next column of the upper hessenberg matrix
1522  hessenberg[j].resize(j+2,std::complex<double>(0.0,0.0));
1523 
1524  // Calculate w=J*v_j. Note, we cannot use inbuilt complex matrix algebra
1525  // here as we're using distributed vectors
1526  complex_matrix_multiplication(Matrices_storage_pt,v[j],w);
1527 
1528  // For fast access
1529  double* w_r_pt=w[0].values_pt();
1530 
1531  // For fast access
1532  double* w_c_pt=w[1].values_pt();
1533 
1534  // Loop over all of the entries on and above the principal subdiagonal of
1535  // the Hessenberg matrix in the j-th column (remembering that
1536  // the indices of the upper Hessenberg matrix are swapped for the purpose
1537  // of implementation)
1538  for (unsigned i=0;i<j+1;i++)
1539  {
1540  // For fast access
1541  const double* vi_r_pt=v[i][0].values_pt();
1542 
1543  // For fast access
1544  const double* vi_c_pt=v[i][1].values_pt();
1545 
1546  // Loop over the entries of v and w
1547  for (unsigned k=0;k<n_row;k++)
1548  {
1549  // Store the appropriate entry in v as a complex value
1550  std::complex<double> complex_v(vi_r_pt[k],vi_c_pt[k]);
1551 
1552  // Store the appropriate entry in w as a complex value
1553  std::complex<double> complex_w(w_r_pt[k],w_c_pt[k]);
1554 
1555  // Update the value of H(i,j) noting we're computing a complex
1556  // inner product here
1557  hessenberg[j][i]+=std::conj(complex_v)*complex_w;
1558  }
1559 
1560  // Orthonormalise w against all previous orthogonal vectors, v_i by
1561  // looping over its entries and updating them
1562  for (unsigned k=0;k<n_row;k++)
1563  {
1564  // Update the real part of the k-th entry of w
1565  w_r_pt[k]-=(hessenberg[j][i].real()*vi_r_pt[k]-
1566  hessenberg[j][i].imag()*vi_c_pt[k]);
1567 
1568  // Update the imaginary part of the k-th entry of w
1569  w_c_pt[k]-=(hessenberg[j][i].real()*vi_c_pt[k]+
1570  hessenberg[j][i].imag()*vi_r_pt[k]);
1571  }
1572  } // for (unsigned i=0;i<j+1;i++)
1573 
1574  // Calculate the norm of the real part of w
1575  norm_r=w[0].norm();
1576 
1577  // Calculate the norm of the imaginary part of w
1578  norm_c=w[1].norm();
1579 
1580  // Calculate the norm of the vector w using norm_r and norm_c and assign
1581  // its value to the appropriate entry in the Hessenberg matrix
1582  hessenberg[j][j+1]=sqrt(pow(norm_r,2.0)+pow(norm_c,2.0));
1583 
1584  // Build the real part of the next orthogonal vector
1585  v[j+1][0].build(this->distribution_pt(),0.0);
1586 
1587  // Build the imaginary part of the next orthogonal vector
1588  v[j+1][1].build(this->distribution_pt(),0.0);
1589 
1590  // Check if the value of hessenberg[j][j+1] is zero. If it
1591  // isn't then we update the next entries in v
1592  if (hessenberg[j][j+1]!=0.0)
1593  {
1594  // For fast access
1595  double* v_r_pt=v[j+1][0].values_pt();
1596 
1597  // For fast access
1598  double* v_c_pt=v[j+1][1].values_pt();
1599 
1600  // For fast access
1601  const double* w_r_pt=w[0].values_pt();
1602 
1603  // For fast access
1604  const double* w_c_pt=w[1].values_pt();
1605 
1606  // Notice, the value of H(j,j+1), as calculated above, is clearly a real
1607  // number. As such, calculating the division
1608  // v_{j+1}=w_{j}/h_{j+1,j},
1609  // here is simple, i.e. we don't need to worry about cross terms in the
1610  // algebra. To avoid computing h_{j+1,j} several times we precompute it
1611  double h_subdiag_val=hessenberg[j][j+1].real();
1612 
1613  // Loop over the entries of the new orthogonal vector and set its values
1614  for(unsigned k=0;k<n_row;k++)
1615  {
1616  // The i-th entry of the real component is given by
1617  v_r_pt[k]=w_r_pt[k]/h_subdiag_val;
1618 
1619  // Similarly, the i-th entry of the imaginary component is given by
1620  v_c_pt[k]=w_c_pt[k]/h_subdiag_val;
1621  }
1622  }
1623  // Otherwise, we have to jump to the next part of the algorithm; if
1624  // the value of hessenberg[j][j+1] is zero then the norm of the latest
1625  // orthogonal vector is zero. This is only possible if the entries
1626  // in w are all zero. As a result, the Krylov space of A and r_0 has
1627  // been spanned by the previously calculated orthogonal vectors
1628  else
1629  {
1630  // Book says "Set m=j and jump to step 11" (p.172)...
1631  // Do something here!
1632  oomph_info << "Subdiagonal Hessenberg entry is zero."
1633  << "Do something here..." << std::endl;
1634  } // if (hessenberg[j][j+1]!=0.0)
1635 
1636  // Loop over the entries in the Hessenberg matrix and calculate the
1637  // entries of the Givens rotation matrices
1638  for (unsigned k=0;k<j;k++)
1639  {
1640  // Apply the plane rotation to all of the previous entries in the
1641  // (j)-th column (remembering the indexing is reversed)
1642  apply_plane_rotation(hessenberg[j][k],hessenberg[j][k+1],cs[k],sn[k]);
1643  }
1644 
1645  // Now calculate the entries of the latest Givens rotation matrix
1646  generate_plane_rotation(hessenberg[j][j],hessenberg[j][j+1],cs[j],sn[j]);
1647 
1648  // Apply the plane rotation using the newly calculated entries
1649  apply_plane_rotation(hessenberg[j][j],hessenberg[j][j+1],cs[j],sn[j]);
1650 
1651  // Apply a plane rotation to the corresponding entry in the vector
1652  // s used in the minimisation problem, J(y)=min||s-R_m*y||
1653  apply_plane_rotation(s[j],s[j+1],cs[j],sn[j]);
1654 
1655  // Compute current residual using equation (6.42) in Saad Y, "Iterative
1656  // methods for sparse linear systems", p.177.]. Note, since s has complex
1657  // entries we have to use std::abs instead of std::fabs
1658  beta=std::abs(s[j+1]);
1659 
1660  // Compute the relative residual
1661  resid=beta/normb;
1662 
1663  // If required will document convergence history to screen or file (if
1664  // stream open)
1666  {
1667  // If an output file which is open isn't provided then output to screen
1668  if (!Output_file_stream.is_open())
1669  {
1670  // Output the residual value to the screen
1671  oomph_info << j+1 << " " << resid << std::endl;
1672  }
1673  // Otherwise, output to file
1674  else
1675  {
1676  // Output the residual value to file
1677  Output_file_stream << j+1 << " " << resid <<std::endl;
1678  }
1679  } // if (Doc_convergence_history)
1680 
1681  // If the required tolerance has been met
1682  if (resid<Tolerance)
1683  {
1684  // Store the number of iterations taken
1685  Iterations=j+1;
1686 
1687  // Update the result vector using the result, x=x_0+V_m*y (where V_m
1688  // is given by v here)
1689  update(j,hessenberg,s,v,solution);
1690 
1691  // If time documentation was enabled
1692  if(Doc_time)
1693  {
1694  // Output the current normalised residual norm
1695  oomph_info << "\nGMRES converged (1). Normalised residual norm: "
1696  << resid << std::endl;
1697 
1698  // Output the number of iterations it took for convergence
1699  oomph_info << "Number of iterations to convergence: "
1700  << j+1 << "\n" << std::endl;
1701  }
1702 
1703  // Stop the timer
1704  double t_end=TimingHelpers::timer();
1705 
1706  // Calculate the time taken to calculate the solution
1707  Solution_time=t_end-t_start;
1708 
1709  // If time documentation was enabled
1710  if(Doc_time)
1711  {
1712  // Output the time taken to solve the problem using GMRES
1713  oomph_info << "Time for solve with GMRES [sec]: "
1714  << Solution_time << std::endl;
1715  }
1716 
1717  // As we've met the tolerance for the solver and everything that should
1718  // be documented, has been, finish using the solver
1719  return;
1720  } // if (resid<Tolerance)
1721  } // for (unsigned j=0;j<Max_iter;j++)
1722 
1723  // Store the number of iterations taken
1724  Iterations=Max_iter;
1725 
1726  // Only update if we actually did something
1727  if (Max_iter>0)
1728  {
1729  // Update the result vector using the result, x=x_0+V_m*y (where V_m
1730  // is given by v here)
1731  update(Max_iter-1,hessenberg,s,v,solution);
1732  }
1733 
1734  // Stop the timer
1735  double t_end=TimingHelpers::timer();
1736 
1737  // Calculate the time taken to calculate the solution
1738  Solution_time=t_end-t_start;
1739 
1740  // If time documentation was enabled
1741  if(Doc_time)
1742  {
1743  // Output the time taken to solve the problem using GMRES
1744  oomph_info << "Time for solve with GMRES [sec]: "
1745  << Solution_time << std::endl;
1746  }
1747 
1748  // Finish using the solver
1749  return;
1750  } // End of complex_solve_helper
1751 
1752 
1753 ///////////////////////////////////////////////////////////////////////
1754 ///////////////////////////////////////////////////////////////////////
1755 ///////////////////////////////////////////////////////////////////////
1756 
1757 
1758 //======================================================================
1759 /// \short The GMRES method for the Helmholtz solver.
1760 //======================================================================
1761  template<typename MATRIX>
1763  public BlockPreconditioner<MATRIX>
1764  {
1765  public:
1766 
1767  /// Constructor
1770  Iterations(0),
1771  Resolving(false),
1772  Matrix_can_be_deleted(true)
1773  {
1774  Preconditioner_LHS=true;
1775  }
1776 
1777  /// Destructor (cleanup storage)
1779  {
1780  clean_up_memory();
1781  }
1782 
1783  /// Broken copy constructor
1785  {
1786  BrokenCopy::broken_copy("HelmholtzGMRESMG");
1787  }
1788 
1789  /// Broken assignment operator
1791  {
1792  BrokenCopy::broken_assign("HelmholtzGMRESMG");
1793  }
1794 
1795  /// Overload disable resolve so that it cleans up memory too
1797  {
1799  clean_up_memory();
1800  }
1801 
1802  /// \short Implementation of the pure virtual base class function. The
1803  /// function has been broken because this is meant to be used as a linear
1804  /// solver
1806  {
1807  // Open an output stream
1808  std::ostringstream error_message_stream;
1809 
1810  // Create an error message
1811  error_message_stream << "Preconditioner_solve(...) is broken. "
1812  << "HelmholtzGMRESMG is only meant to be used as "
1813  << "a linear solver.\n";
1814 
1815  // Throw the error message
1816  throw OomphLibError(error_message_stream.str(),
1817  OOMPH_CURRENT_FUNCTION,
1818  OOMPH_EXCEPTION_LOCATION);
1819  }
1820 
1821  /// \short Implementation of the pure virtual base class function. This
1822  /// accompanies the preconditioner_solve function and so is also broken
1823  void setup()
1824  {
1825  // Open an output stream
1826  std::ostringstream error_message_stream;
1827 
1828  // Create an error message
1829  error_message_stream << "This function is broken. HelmholtzGMRESMG is "
1830  << "only meant to be used as a linear solver.\n";
1831 
1832  // Throw the error message
1833  throw OomphLibError(error_message_stream.str(),
1834  OOMPH_CURRENT_FUNCTION,
1835  OOMPH_EXCEPTION_LOCATION);
1836  }
1837 
1838  /// \short Solver: Takes pointer to problem and returns the results vector
1839  /// which contains the solution of the linear system defined by
1840  /// the problem's fully assembled Jacobian and residual vector.
1841  void solve(Problem* const &problem_pt,DoubleVector &result)
1842  {
1843 #ifdef OOMPH_HAS_MPI
1844  // Make sure that this is running in serial. Can't guarantee it'll
1845  // work when the problem is distributed over several processors
1846  if (MPI_Helpers::communicator_pt()->nproc()>1)
1847  {
1848  // Throw a warning
1849  OomphLibWarning("Can't guarantee the MG solver will work in parallel!",
1850  OOMPH_CURRENT_FUNCTION,
1851  OOMPH_EXCEPTION_LOCATION);
1852  }
1853 #endif
1854 
1855  // Find # of degrees of freedom (variables)
1856  unsigned n_dof=problem_pt->ndof();
1857 
1858  // Initialise timer
1859  double t_start=TimingHelpers::timer();
1860 
1861  // We're not re-solving
1862  Resolving=false;
1863 
1864  // Get rid of any previously stored data
1865  clean_up_memory();
1866 
1867  // Grab the communicator from the MGProblem object and assign it
1868  this->set_comm_pt(problem_pt->communicator_pt());
1869 
1870  // Setup the distribution
1871  LinearAlgebraDistribution dist(problem_pt->communicator_pt(),n_dof,false);
1872 
1873  // Build the internal distribution in this way because both the
1874  // IterativeLinearSolver and BlockPreconditioner class have base-
1875  // class subobjects of type oomph::DistributableLinearAlgebraObject
1877 
1878  // Get Jacobian matrix in format specified by template parameter
1879  // and nonlinear residual vector
1880  MATRIX* matrix_pt=new MATRIX;
1881  DoubleVector f;
1882  if (dynamic_cast<DistributableLinearAlgebraObject*>(matrix_pt) != 0)
1883  {
1884  if (dynamic_cast<CRDoubleMatrix*>(matrix_pt) != 0)
1885  {
1886  dynamic_cast<CRDoubleMatrix* >(matrix_pt)->
1889  }
1890  }
1891 
1892  // Get the Jacobian and residuals vector
1893  problem_pt->get_jacobian(f,*matrix_pt);
1894 
1895  // We've made the matrix, we can delete it...
1896  Matrix_can_be_deleted=true;
1897 
1898  // Replace the current matrix used in Preconditioner by the new matrix
1899  this->set_matrix_pt(matrix_pt);
1900 
1901  // The preconditioner works with one mesh; set it! Since we only use
1902  // the block preconditioner on the finest level, we use the mesh from
1903  // that level
1904  this->set_nmesh(1);
1905 
1906  // Elements in actual pml layer are trivially wrapped versions of
1907  // their bulk counterparts. Technically they are different elements
1908  // so we have to allow different element types
1909  bool allow_different_element_types_in_mesh=true;
1910  this->set_mesh(0,problem_pt->mesh_pt(),
1911  allow_different_element_types_in_mesh);
1912 
1913  // Set up the generic block look up scheme
1914  this->block_setup();
1915 
1916  // Extract the number of blocks.
1917  unsigned nblock_types=this->nblock_types();
1918 
1919 #ifdef PARANOID
1920  // PARANOID check - there must only be two block types
1921  if (nblock_types!=2)
1922  {
1923  // Create the error message
1924  std::stringstream tmp;
1925  tmp << "There are supposed to be two block types.\nYours has "
1926  << nblock_types << std::endl;
1927 
1928  // Throw an error
1929  throw OomphLibError(tmp.str(),
1930  OOMPH_CURRENT_FUNCTION,
1931  OOMPH_EXCEPTION_LOCATION);
1932  }
1933 #endif
1934 
1935  // Resize the storage for the system matrices
1936  Matrices_storage_pt.resize(2,0);
1937 
1938  // Loop over the rows of the block matrix
1939  for (unsigned i=0;i<nblock_types;i++)
1940  {
1941  // Fix the column index
1942  unsigned j=0;
1943 
1944  // Create new CRDoubleMatrix objects
1945  Matrices_storage_pt[i]=new CRDoubleMatrix;
1946 
1947  // Extract the required blocks, i.e. the first column
1948  this->get_block(i,j,*Matrices_storage_pt[i]);
1949  }
1950 
1951  // Doc time for setup
1952  double t_end=TimingHelpers::timer();
1953  Jacobian_setup_time=t_end-t_start;
1954 
1955  if(Doc_time)
1956  {
1957  oomph_info << "\nTime for setup of block Jacobian [sec]: "
1958  << Jacobian_setup_time << std::endl;
1959  }
1960 
1961  // Call linear algebra-style solver
1962  // If the result distribution is wrong, then redistribute
1963  // before the solve and return to original distribution
1964  // afterwards
1966  &&(result.built()))
1967  {
1968  // Make a distribution object
1969  LinearAlgebraDistribution temp_global_dist(result.distribution_pt());
1970 
1971  // Build the result vector distribution
1973 
1974  // Solve the problem
1975  solve_helper(matrix_pt,f,result);
1976 
1977  // Redistribute the vector
1978  result.redistribute(&temp_global_dist);
1979  }
1980  // Otherwise just solve
1981  else
1982  {
1983  // Solve
1984  solve_helper(matrix_pt,f,result);
1985  }
1986 
1987  // Kill matrix unless it's still required for resolve
1988  if (!Enable_resolve)
1989  {
1990  // Clean up anything left in memory
1991  clean_up_memory();
1992  }
1993  } // End of solve
1994 
1995  /// \short Linear-algebra-type solver: Takes pointer to a matrix and rhs
1996  /// vector and returns the solution of the linear system.
1997  void solve(DoubleMatrixBase* const &matrix_pt,
1998  const DoubleVector &rhs,
1999  DoubleVector &solution)
2000  {
2001  // Open an output stream
2002  std::ostringstream error_message_stream;
2003 
2004  // Create an error message
2005  error_message_stream << "This function is broken. The block preconditioner "
2006  << "needs access to the underlying mesh.\n";
2007 
2008  // Throw the error message
2009  throw OomphLibError(error_message_stream.str(),
2010  OOMPH_CURRENT_FUNCTION,
2011  OOMPH_EXCEPTION_LOCATION);
2012  }
2013 
2014 
2015  /// \short Linear-algebra-type solver: Takes pointer to a matrix
2016  /// and rhs vector and returns the solution of the linear system
2017  /// Call the broken base-class version. If you want this, please
2018  /// implement it
2019  void solve(DoubleMatrixBase* const &matrix_pt,
2020  const Vector<double> &rhs,
2021  Vector<double> &result)
2022  {LinearSolver::solve(matrix_pt,rhs,result);}
2023 
2024  /// \short Re-solve the system defined by the last assembled Jacobian
2025  /// and the rhs vector specified here. Solution is returned in the
2026  /// vector result.
2027  void resolve(const DoubleVector &rhs,
2028  DoubleVector &result)
2029  {
2030  // We are re-solving
2031  Resolving=true;
2032 
2033 #ifdef PARANOID
2034  if ((Matrices_storage_pt[0]==0)||(Matrices_storage_pt[1]==0))
2035  {
2036  throw OomphLibError("No matrix was stored -- cannot re-solve",
2037  OOMPH_CURRENT_FUNCTION,
2038  OOMPH_EXCEPTION_LOCATION);
2039  }
2040 #endif
2041 
2042  // Set up a dummy matrix. As we're resolving this won't be used in
2043  // solve_helper but we need to pass a matrix in to fill the input.
2044  // The matrices used in the calculations have already been stored
2045  CRDoubleMatrix* matrix_pt=new CRDoubleMatrix;
2046 
2047  // Call the helper function
2048  solve_helper(matrix_pt,rhs,result);
2049 
2050  // Delete the matrix
2051  delete matrix_pt;
2052 
2053  // Make it a null pointer
2054  matrix_pt=0;
2055 
2056  // Reset re-solving flag
2057  Resolving=false;
2058  }
2059 
2060  /// Number of iterations taken
2061  unsigned iterations() const
2062  {
2063  return Iterations;
2064  }
2065 
2066  /// \short Set left preconditioning (the default)
2067  void set_preconditioner_LHS() {Preconditioner_LHS=true;}
2068 
2069  /// \short Enable right preconditioning
2070  void set_preconditioner_RHS() {Preconditioner_LHS=false;}
2071 
2072  protected:
2073 
2074  /// General interface to solve function
2075  void solve_helper(DoubleMatrixBase* const &matrix_pt,
2076  const DoubleVector &rhs,
2077  DoubleVector &solution);
2078 
2079  /// Cleanup data that's stored for resolve (if any has been stored)
2081  {
2082  // If the matrix storage has been resized
2083  if (Matrices_storage_pt.size()>0)
2084  {
2085  // If the real matrix pointer isn't null AND we're allowed to delete
2086  // the matrix which is only when we create the matrix ourselves
2087  if ((Matrices_storage_pt[0]!=0)&&(Matrix_can_be_deleted))
2088  {
2089  // Delete the matrix
2090  delete Matrices_storage_pt[0];
2091 
2092  // Assign the associated pointer the value NULL
2093  Matrices_storage_pt[0]=0;
2094  }
2095 
2096  // If the real matrix pointer isn't null AND we're allowed to delete
2097  // the matrix which is only when we create the matrix ourselves
2098  if ((Matrices_storage_pt[1]!=0)&&(Matrix_can_be_deleted))
2099  {
2100  // Delete the matrix
2101  delete Matrices_storage_pt[1];
2102 
2103  // Assign the associated pointer the value NULL
2104  Matrices_storage_pt[1]=0;
2105  }
2106  }
2107  } // End of clean_up_memory
2108 
2109  /// \short Helper function to calculate a complex matrix-vector product.
2110  /// Assumes the matrix has been provided as a Vector of length two; the
2111  /// first entry containing the real part of the system matrix and the
2112  /// second entry containing the imaginary part
2114  const Vector<DoubleVector>& x,
2115  Vector<DoubleVector>& soln)
2116  {
2117 #ifdef PARANOID
2118  // PARANOID check - Make sure the input matrix has the right size
2119  if (matrices_pt.size()!=2)
2120  {
2121  // Create an output stream
2122  std::ostringstream error_message_stream;
2123 
2124  // Create the error message
2125  error_message_stream << "Can only deal with two matrices. You have "
2126  << matrices_pt.size()
2127  << " matrices." << std::endl;
2128 
2129  // Throw an error
2130  throw OomphLibError(error_message_stream.str(),
2131  OOMPH_CURRENT_FUNCTION,
2132  OOMPH_EXCEPTION_LOCATION);
2133  }
2134  // PARANOID check - Make sure the vector x has the right size
2135  if (x.size()!=2)
2136  {
2137  // Create an output stream
2138  std::ostringstream error_message_stream;
2139 
2140  // Create the error message
2141  error_message_stream << "Can only deal with two input vectors. You have "
2142  << x.size()
2143  << " vectors." << std::endl;
2144 
2145  // Throw an error
2146  throw OomphLibError(error_message_stream.str(),
2147  OOMPH_CURRENT_FUNCTION,
2148  OOMPH_EXCEPTION_LOCATION);
2149  }
2150  // PARANOID check - Make sure the vector soln has the right size
2151  if (soln.size()!=2)
2152  {
2153  // Create an output stream
2154  std::ostringstream error_message_stream;
2155 
2156  // Create the error message
2157  error_message_stream << "Can only deal with two output vectors. You have "
2158  << soln.size()
2159  << " output vectors." << std::endl;
2160 
2161  // Throw an error
2162  throw OomphLibError(error_message_stream.str(),
2163  OOMPH_CURRENT_FUNCTION,
2164  OOMPH_EXCEPTION_LOCATION);
2165  }
2166 #endif
2167 
2168  // NOTE: We assume all vectors have been distributed at this point but
2169  // code can be written at a later time to build the vectors if they're
2170  // not already built.
2171 
2172  //-----------------------------------------------------------------------
2173  // Suppose we have a complex matrix, A, and two complex vectors, x and
2174  // soln. We wish to compute the product A*x=soln (note, * does not mean
2175  // we are using complex conjugates here, it is simply used to indicate
2176  // a multiplication). To do this we must make use of the fact that we
2177  // possess the real and imaginary separately. As a result, it is computed
2178  // using:
2179  // soln = A*x,
2180  // = (A_r + i*A_c)*(x_r + i*x_c),
2181  // = [A_r*x_r - A_c*x_c] + i*[A_r*x_c + A_c*x_r],
2182  // ==> real(soln) = A_r*x_r - A_c*x_c,
2183  // & imag(soln) = A_r*x_c + A_c*x_r,
2184  // where the subscripts _r and _c are used to identify the real and
2185  // imaginary part, respectively.
2186  //-----------------------------------------------------------------------
2187 
2188  // Store the value of A_r*x_r in the real part of soln
2189  matrices_pt[0]->multiply(x[0],soln[0]);
2190 
2191  // Store the value of A_r*x_c in the imaginary part of soln
2192  matrices_pt[0]->multiply(x[1],soln[1]);
2193 
2194  // Create a temporary vector
2195  DoubleVector temp(Matrices_storage_pt[0]->distribution_pt(),0.0);
2196 
2197  // Calculate the value of A_c*x_c
2198  matrices_pt[1]->multiply(x[1],temp);
2199 
2200  // Subtract the value of temp from soln[0] to get the real part of soln
2201  soln[0]-=temp;
2202 
2203  // Calculate the value of A_c*x_r
2204  matrices_pt[1]->multiply(x[0],temp);
2205 
2206  // Add the value of temp to soln[1] to get the imaginary part of soln
2207  soln[1]+=temp;
2208  } // End of complex_matrix_multiplication
2209 
2210  /// Helper function to update the result vector
2211  void update(const unsigned& k,
2212  const Vector<Vector<std::complex<double> > >& hessenberg,
2213  const Vector<std::complex<double> >& s,
2214  const Vector<Vector<DoubleVector> >& v,
2216  {
2217  // Make a local copy of s
2219 
2220  //-----------------------------------------------------------------
2221  // The Hessenberg matrix should be an upper triangular matrix at
2222  // this point (although from its storage it would appear to be a
2223  // lower triangular matrix since the indexing has been reversed)
2224  // so finding the minimiser of J(y)=min||s-R_m*y|| where R_m is
2225  // the matrix R in the QR factorisation of the Hessenberg matrix.
2226  // Therefore, to obtain y we simply need to use a backwards
2227  // substitution. Note: The implementation here may appear to be
2228  // somewhat confusing as the indexing in the Hessenberg matrix is
2229  // reversed. This implementation of a backwards substitution does
2230  // not run along the columns of the triangular matrix but rather
2231  // up the rows.
2232  //-----------------------------------------------------------------
2233 
2234  // The outer loop is a loop over the columns of the Hessenberg matrix
2235  // since the indexing is reversed
2236  for (int i=int(k);i>=0;i--)
2237  {
2238  // Divide the i-th entry of y by the i-th diagonal entry of H
2239  y[i]/=hessenberg[i][i];
2240 
2241  // The inner loop is a loop over the rows of the Hessenberg matrix
2242  for (int j=i-1;j>=0;j--)
2243  {
2244  // Update the j-th entry of y
2245  y[j]-=hessenberg[i][j]*y[i];
2246  }
2247  } // for (int i=int(k);i>=0;i--)
2248 
2249  // Calculate the number of entries in x (simply use the real part as
2250  // both the real and imaginary part should have the same length)
2251  unsigned n_row=x[0].nrow();
2252 
2253  // Build a temporary vector with entries initialised to 0.0
2254  Vector<DoubleVector> block_z(2);
2255 
2256  // Build a temporary vector with entries initialised to 0.0
2257  Vector<DoubleVector> block_temp(2);
2258 
2259  // Build the distributions
2260  for (unsigned dof_type=0;dof_type<2;dof_type++)
2261  {
2262  // Build the (dof_type)-th vector
2263  block_z[dof_type].build(x[0].distribution_pt(),0.0);
2264 
2265  // Build the (dof_type)-th vector
2266  block_temp[dof_type].build(x[0].distribution_pt(),0.0);
2267  }
2268 
2269  // Get access to the underlying values
2270  double* block_temp_r_pt=block_temp[0].values_pt();
2271 
2272  // Get access to the underlying values
2273  double* block_temp_c_pt=block_temp[1].values_pt();
2274 
2275  // Calculate x=Vy
2276  for (unsigned j=0;j<=k;j++)
2277  {
2278  // Get access to j-th column of Z_m
2279  const double* vj_r_pt=v[j][0].values_pt();
2280 
2281  // Get access to j-th column of Z_m
2282  const double* vj_c_pt=v[j][1].values_pt();
2283 
2284  // Loop over the entries in x and update them
2285  for (unsigned i=0;i<n_row;i++)
2286  {
2287  // Update the real part of the i-th entry in x
2288  block_temp_r_pt[i]+=(vj_r_pt[i]*y[j].real())-(vj_c_pt[i]*y[j].imag());
2289 
2290  // Update the imaginary part of the i-th entry in x
2291  block_temp_c_pt[i]+=(vj_c_pt[i]*y[j].real())+(vj_r_pt[i]*y[j].imag());
2292  }
2293  } // for (unsigned j=0;j<=k;j++)
2294 
2295  // If we're using LHS preconditioning
2296  if(Preconditioner_LHS)
2297  {
2298  // Since we're using LHS preconditioning the preconditioner is applied
2299  // to the matrix and RHS vector so we simply update the value of x
2300  for (unsigned dof_type=0;dof_type<2;dof_type++)
2301  {
2302  // Update
2303  x[dof_type]+=block_temp[dof_type];
2304  }
2305  }
2306  // If we're using RHS preconditioning
2307  else
2308  {
2309  // Create a temporary vector
2311 
2312  // Copy block vectors block_temp back to temp
2313  this->return_block_vectors(block_temp,temp);
2314 
2315  // Create a temporary vector
2317 
2318  // Copy block vectors block_z back to z
2319  this->return_block_vectors(block_z,z);
2320 
2321  // Since we're using RHS preconditioning the preconditioner is applied
2322  // to the solution vector
2324 
2325  // Split up the solution vector into DoubleVectors, whose entries are
2326  // arranged to match the matrix blocks and assign it
2327  this->get_block_vectors(z,block_z);
2328 
2329  // Use the update: x_m=x_0+inv(M)Vy [see Saad Y,"Iterative methods for
2330  // sparse linear systems", p.284]
2331  for (unsigned dof_type=0;dof_type<2;dof_type++)
2332  {
2333  // Update
2334  x[dof_type]+=block_z[dof_type];
2335  }
2336  } // if(Preconditioner_LHS) else
2337  } // End of update
2338 
2339  /// \short Helper function: Generate a plane rotation. This is done by
2340  /// finding the value of \f$ \cos(\theta) \f$ (i.e. cs) and the value of
2341  /// \f$ \sin(\theta) \f$ (i.e. sn) such that:
2342  /// \f[
2343  /// \begin{bmatrix}
2344  /// \overline{\cos\theta} & \overline{\sin\theta}
2345  /// \\ -\sin\theta & \cos\theta
2346  /// \end{bmatrix}
2347  /// \begin{bmatrix}
2348  /// dx
2349  /// \\ dy
2350  /// \end{bmatrix}
2351  /// =
2352  /// \begin{bmatrix}
2353  /// r
2354  /// \\ 0
2355  /// \end{bmatrix},
2356  /// \f]
2357  /// where \f$ r=\sqrt{pow(|dx|,2)+pow(|dy|,2)} \f$. The values of a and b
2358  /// are given by:
2359  /// The values of dx and dy are given by:
2360  /// \f[
2361  /// \cos\theta&=\dfrac{dx}{\sqrt{|dx|^2+|dy|^2}},
2362  /// \f]
2363  /// and
2364  /// \f[
2365  /// \sin\theta&=\dfrac{dy}{\sqrt{|dx|^2+|dy|^2}}.
2366  /// \f]
2367  /// Taken from: Saad Y."Iterative methods for sparse linear systems", p.193.
2368  /// We also check to see that sn is always a real (nonnegative) number. See
2369  /// pp.193-194 for an explanation.
2370  void generate_plane_rotation(std::complex<double>& dx,
2371  std::complex<double>& dy,
2372  std::complex<double>& cs,
2373  std::complex<double>& sn)
2374  {
2375  // If dy=0 then we do not need to apply a rotation
2376  if (dy==0.0)
2377  {
2378  // Using theta=0 gives cos(theta)=1
2379  cs=1.0;
2380 
2381  // Using theta=0 gives sin(theta)=0
2382  sn=0.0;
2383  }
2384  // If dx or dy is large using the original form of calculting cs and sn is
2385  // naive since this may overflow or underflow so instead we calculate
2386  // r=sqrt(pow(|dx|,2)+pow(|dy|,2)) using r=|dy|sqrt(1+pow(|dx|/|dy|,2)) if
2387  // |dy|>|dx| [see <A HREF=https://en.wikipedia.org/wiki/Hypot">Hypot</A>.].
2388  else if(std::abs(dy)>std::abs(dx))
2389  {
2390  // Since |dy|>|dx| calculate the ratio |dx|/|dy|
2391  std::complex<double> temp=dx/dy;
2392 
2393  // Calculate the value of sin(theta) using:
2394  // sin(theta)=dy/sqrt(pow(|dx|,2)+pow(|dy|,2))
2395  // =(dy/|dy|)/sqrt(1+pow(|dx|/|dy|,2)).
2396  sn=(dy/std::abs(dy))/sqrt(1.0+pow(std::abs(temp),2.0));
2397 
2398  // Calculate the value of cos(theta) using:
2399  // cos(theta)=dx/sqrt(pow(|dy|,2)+pow(|dx|,2))
2400  // =(dx/|dy|)/sqrt(1+pow(|dx|/|dy|,2))
2401  // =(dx/dy)*sin(theta).
2402  cs=temp*sn;
2403  }
2404  // Otherwise, we have |dx|>=|dy| so to, again, avoid overflow or underflow
2405  // calculate the values of cs and sn using the method above
2406  else
2407  {
2408  // Since |dx|>=|dy| calculate the ratio dy/dx
2409  std::complex<double> temp=dy/dx;
2410 
2411  // Calculate the value of cos(theta) using:
2412  // cos(theta)=dx/sqrt(pow(|dx|,2)+pow(|dy|,2))
2413  // =(dx/|dx|)/sqrt(1+pow(|dy|/|dx|,2)).
2414  cs=(dx/std::abs(dx))/sqrt(1.0+pow(std::abs(temp),2.0));
2415 
2416  // Calculate the value of sin(theta) using:
2417  // sin(theta)=dy/sqrt(pow(|dx|,2)+pow(|dy|,2))
2418  // =(dy/|dx|)/sqrt(1+pow(|dy|/|dx|,2))
2419  // =(dy/dx)*cos(theta).
2420  sn=temp*cs;
2421  }
2422 
2423  // Set the tolerance for sin(theta)
2424  double tolerance=1.0e-15;
2425 
2426  // Make sure sn is real and nonnegative (it should be!)
2427  if ((std::fabs(sn.imag())>tolerance)||(sn.real()<0))
2428  {
2429  // Create an output stream
2430  std::ostringstream error_message_stream;
2431 
2432  // Create an error message
2433  error_message_stream << "The value of sin(theta) is not real "
2434  << "and/or nonnegative. Value is: "
2435  << sn << std::endl;
2436 
2437  // Throw an error
2438  throw OomphLibError(error_message_stream.str(),
2439  OOMPH_CURRENT_FUNCTION,
2440  OOMPH_EXCEPTION_LOCATION);
2441  }
2442  } // End of generate_plane_rotation
2443 
2444  /// \short Helper function: Apply plane rotation. This is done using the
2445  /// update:
2446  /// \f[
2447  /// \begin{bmatrix}
2448  /// dx
2449  /// \\ dy
2450  /// \end{bmatrix}
2451  /// \leftarrow
2452  /// \begin{bmatrix}
2453  /// \overline{\cos\theta} & \overline{\sin\theta}
2454  /// \\ -\sin\theta & \cos\theta
2455  /// \end{bmatrix}
2456  /// \begin{bmatrix}
2457  /// dx
2458  /// \\ dy
2459  /// \end{bmatrix}.
2460  /// \f]
2461  /// Taken from: Saad Y."Iterative methods for sparse linear systems", p.193.
2462  void apply_plane_rotation(std::complex<double>& dx,
2463  std::complex<double>& dy,
2464  std::complex<double>& cs,
2465  std::complex<double>& sn)
2466  {
2467  // Calculate the value of dx but don't update it yet
2468  std::complex<double> temp=std::conj(cs)*dx+std::conj(sn)*dy;
2469 
2470  // Set the value of dy
2471  dy=-sn*dx+cs*dy;
2472 
2473  // Set the value of dx using the correct values of dx and dy
2474  dx=temp;
2475  } // End of apply_plane_rotation
2476 
2477  /// Number of iterations taken
2478  unsigned Iterations;
2479 
2480  /// Vector of pointers to the real and imaginary part of the system matrix
2482 
2483  /// \short Boolean flag to indicate if the solve is done in re-solve mode,
2484  /// bypassing setup of matrix and preconditioner
2486 
2487  /// \short Boolean flag to indicate if the matrix pointed to be Matrix_pt
2488  /// can be deleted.
2490 
2491  /// \short boolean indicating use of left hand preconditioning (if true)
2492  /// or right hand preconditioning (if false)
2494  };
2495 
2496 ///////////////////////////////////////////////////////////////////////
2497 ///////////////////////////////////////////////////////////////////////
2498 ///////////////////////////////////////////////////////////////////////
2499 
2500 //=============================================================================
2501 /// Linear-algebra-type solver: Takes pointer to a matrix and rhs vector
2502 /// and returns the solution of the linear system.
2503 /// based on the algorithm presented in Templates for the
2504 /// Solution of Linear Systems: Building Blocks for Iterative Methods, Barrett,
2505 /// Berry et al, SIAM, 2006 and the implementation in the IML++ library :
2506 /// http://math.nist.gov/iml++/
2507 //=============================================================================
2508  template <typename MATRIX>
2511  const DoubleVector& rhs,
2512  DoubleVector& solution)
2513  {
2514  // Set the number of dof types (real and imaginary for this solver)
2515  unsigned n_dof_types=this->ndof_types();
2516 
2517 #ifdef PARANOID
2518  // This only works for 2 dof types
2519  if (n_dof_types!=2)
2520  {
2521  // Create an output stream
2522  std::stringstream error_message_stream;
2523 
2524  // Create the error message
2525  error_message_stream << "This preconditioner only works for problems "
2526  << "with 2 dof types\nYours has " << n_dof_types;
2527 
2528  // Throw the error message
2529  throw OomphLibError(error_message_stream.str(),
2530  OOMPH_CURRENT_FUNCTION,
2531  OOMPH_EXCEPTION_LOCATION);
2532  }
2533 #endif
2534 
2535  // Get the number of dofs (note, the total number of dofs in the problem
2536  // is 2*n_row but if the constituent vectors and matrices were stored in
2537  // complex objects there would only be (n_row) rows so we use that)
2538  unsigned n_row=Matrices_storage_pt[0]->nrow();
2539 
2540  // Make sure Max_iter isn't greater than n_dof. The user cannot use this
2541  // many iterations when using Krylov subspace methods
2542  if (Max_iter>n_row)
2543  {
2544  // Create an output string stream
2545  std::ostringstream error_message_stream;
2546 
2547  // Create the error message
2548  error_message_stream << "The maximum number of iterations cannot exceed "
2549  << "the number of rows in the problem."
2550  << "\nMaximum number of iterations: " << Max_iter
2551  << "\nNumber of rows: " << n_row
2552  << std::endl;
2553 
2554  // Throw the error message
2555  throw OomphLibError(error_message_stream.str(),
2556  OOMPH_CURRENT_FUNCTION,
2557  OOMPH_EXCEPTION_LOCATION);
2558  }
2559 
2560 #ifdef PARANOID
2561  // Loop over the real and imaginary parts
2562  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
2563  {
2564  // PARANOID check that if the matrix is distributable then it should not be
2565  // then it should not be distributed
2566  if (dynamic_cast<DistributableLinearAlgebraObject*>
2567  (Matrices_storage_pt[dof_type])!=0)
2568  {
2569  if (dynamic_cast<DistributableLinearAlgebraObject*>
2570  (Matrices_storage_pt[dof_type])->distributed())
2571  {
2572  std::ostringstream error_message_stream;
2573  error_message_stream << "The matrix must not be distributed.";
2574  throw OomphLibError(error_message_stream.str(),
2575  OOMPH_CURRENT_FUNCTION,
2576  OOMPH_EXCEPTION_LOCATION);
2577  }
2578  }
2579  }
2580  // PARANOID check that this rhs distribution is setup
2581  if (!rhs.built())
2582  {
2583  std::ostringstream error_message_stream;
2584  error_message_stream << "The rhs vector distribution must be setup.";
2585  throw OomphLibError(error_message_stream.str(),
2586  OOMPH_CURRENT_FUNCTION,
2587  OOMPH_EXCEPTION_LOCATION);
2588  }
2589  // PARANOID check that the rhs has the right number of global rows
2590  if(rhs.nrow()!=2*n_row)
2591  {
2592  std::ostringstream error_message_stream;
2593  error_message_stream << "RHS does not have the same dimension as the"
2594  << " linear system";
2595  throw OomphLibError(error_message_stream.str(),
2596  OOMPH_CURRENT_FUNCTION,
2597  OOMPH_EXCEPTION_LOCATION);
2598  }
2599  // PARANOID check that the rhs is not distributed
2600  if (rhs.distribution_pt()->distributed())
2601  {
2602  std::ostringstream error_message_stream;
2603  error_message_stream << "The rhs vector must not be distributed.";
2604  throw OomphLibError(error_message_stream.str(),
2605  OOMPH_CURRENT_FUNCTION,
2606  OOMPH_EXCEPTION_LOCATION);
2607  }
2608  // PARANOID check that if the result is setup it matches the distribution
2609  // of the rhs
2610  if (solution.built())
2611  {
2612  if (!(*rhs.distribution_pt()==*solution.distribution_pt()))
2613  {
2614  std::ostringstream error_message_stream;
2615  error_message_stream << "If the result distribution is setup then it "
2616  << "must be the same as the rhs distribution";
2617  throw OomphLibError(error_message_stream.str(),
2618  OOMPH_CURRENT_FUNCTION,
2619  OOMPH_EXCEPTION_LOCATION);
2620  }
2621  } // if (solution[dof_type].built())
2622 #endif
2623 
2624  // Set up the solution distribution if it's not already distributed
2625  if (!solution.built())
2626  {
2627  // Build the distribution
2629  }
2630  // Otherwise initialise all entries to zero
2631  else
2632  {
2633  // Initialise the entries in the k-th vector in solution to zero
2634  solution.initialise(0.0);
2635  }
2636 
2637  // Create a vector of DoubleVectors (this is a distributed vector so we have
2638  // to create two separate DoubleVector objects to cope with the arithmetic)
2639  Vector<DoubleVector> block_solution(n_dof_types);
2640 
2641  // Create a vector of DoubleVectors (this is a distributed vector so we have
2642  // to create two separate DoubleVector objects to cope with the arithmetic)
2643  Vector<DoubleVector> block_rhs(n_dof_types);
2644 
2645  // Build the distribution of both vectors
2646  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
2647  {
2648  // Build the distribution of the k-th constituent vector
2649  block_solution[dof_type].build(this->block_distribution_pt(dof_type),0.0);
2650 
2651  // Build the distribution of the k-th constituent vector
2652  block_rhs[dof_type].build(this->block_distribution_pt(dof_type),0.0);
2653  }
2654 
2655  // Grab the solution vector in block form
2656  this->get_block_vectors(solution,block_solution);
2657 
2658  // Grab the RHS vector in block form
2659  this->get_block_vectors(rhs,block_rhs);
2660 
2661  // Start the solver timer
2662  double t_start=TimingHelpers::timer();
2663 
2664  // Storage for the relative residual
2665  double resid;
2666 
2667  // Initialise vectors (since these are not distributed vectors we template
2668  // one vector by the type std::complex<double> instead of using two vectors,
2669  // each templated by the type double
2670 
2671  // Vector, s, used in the minimisation problem: J(y)=min||s-R_m*y||
2672  // [see Saad Y."Iterative methods for sparse linear systems", p.176.]
2673  Vector<std::complex<double> > s(n_row+1,std::complex<double>(0.0,0.0));
2674 
2675  // Vector to store the value of cos(theta) when using the Givens rotation
2676  Vector<std::complex<double> > cs(n_row+1,std::complex<double>(0.0,0.0));
2677 
2678  // Vector to store the value of sin(theta) when using the Givens rotation
2679  Vector<std::complex<double> > sn(n_row+1,std::complex<double>(0.0,0.0));
2680 
2681  // Create a vector of DoubleVectors (this is a distributed vector so we have
2682  // to create two separate DoubleVector objects to cope with the arithmetic)
2683  Vector<DoubleVector> block_w(n_dof_types);
2684 
2685  // Build the distribution of both vectors
2686  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
2687  {
2688  // Build the distribution of the k-th constituent vector
2689  block_w[dof_type].build(this->block_distribution_pt(dof_type),0.0);
2690  }
2691 
2692  // Set up the preconditioner only if we're not re-solving
2693  if (!Resolving)
2694  {
2695  // Only set up the preconditioner before solve if required
2697  {
2698  // Set up the preconditioner from the Jacobian matrix
2699  double t_start_prec=TimingHelpers::timer();
2700 
2701  // Use the setup function in the Preconditioner class
2702  preconditioner_pt()->setup(dynamic_cast<MATRIX*>(matrix_pt));
2703 
2704  // Doc time for setup of preconditioner
2705  double t_end_prec=TimingHelpers::timer();
2706  Preconditioner_setup_time=t_end_prec-t_start_prec;
2707 
2708  // If time documentation is enabled
2709  if(Doc_time)
2710  {
2711  // Output the time taken
2712  oomph_info << "Time for setup of preconditioner [sec]: "
2713  << Preconditioner_setup_time << std::endl;
2714  }
2715  }
2716  }
2717  else
2718  {
2719  // If time documentation is enabled
2720  if(Doc_time)
2721  {
2722  // Notify the user
2723  oomph_info << "Setup of preconditioner is bypassed in resolve mode"
2724  << std::endl;
2725  }
2726  } // if (!Resolving) else
2727 
2728  // Create a vector of DoubleVectors to store the RHS of b-Jx=Mr. We assume
2729  // both x=0 and that a preconditioner is not applied by which we deduce b=r
2730  Vector<DoubleVector> block_r(n_dof_types);
2731 
2732  // Build the distribution of both vectors
2733  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
2734  {
2735  // Build the distribution of the k-th constituent vector
2736  block_r[dof_type].build(this->block_distribution_pt(dof_type),0.0);
2737  }
2738 
2739  // If we're using LHS preconditioning solve b-Jx=Mr for r (assumes x=0)
2740  // so calculate r=M^{-1}b otherwise set r=b (RHS prec.)
2741  if(Preconditioner_LHS)
2742  {
2743  // Create a vector of the same size as rhs
2745 
2746  // Copy the vectors in r to full_r
2747  this->return_block_vectors(block_r,r);
2748 
2749  // Use the preconditioner
2751 
2752  // Copy the vector full_r into the vectors in r
2753  this->get_block_vectors(r,block_r);
2754  }
2755  else
2756  {
2757  // Store the value of b (the RHS vector) in r
2758  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
2759  {
2760  // Copy the entries of rhs into r
2761  block_r[dof_type]=block_rhs[dof_type];
2762  }
2763  } // if(Preconditioner_LHS)
2764 
2765  // Calculate the norm of the real part of r
2766  double norm_r=block_r[0].norm();
2767 
2768  // Calculate the norm of the imaginary part of r
2769  double norm_c=block_r[1].norm();
2770 
2771  // Compute norm(r)
2772  double normb=sqrt(pow(norm_r,2.0)+pow(norm_c,2.0));
2773 
2774  // Set the value of beta (the initial residual)
2775  double beta=normb;
2776 
2777  // Compute the initial relative residual. If the entries of the RHS vector
2778  // are all zero then set normb equal to one. This is because we divide the
2779  // value of the norm at later stages by normb and dividing by zero is not
2780  // definied
2781  if (normb==0.0)
2782  {
2783  // Set the value of normb
2784  normb=1.0;
2785  }
2786 
2787  // Calculate the ratio between the initial norm and the current norm.
2788  // Since we haven't completed an iteration yet this will simply be one
2789  // unless normb was zero, in which case resid will have value zero
2790  resid=beta/normb;
2791 
2792  // If required, will document convergence history to screen or file (if
2793  // stream open)
2795  {
2796  // If an output file which is open isn't provided then output to screen
2797  if (!Output_file_stream.is_open())
2798  {
2799  // Output the residual value to the screen
2800  oomph_info << 0 << " " << resid << std::endl;
2801  }
2802  // Otherwise, output to file
2803  else
2804  {
2805  // Output the residual value to file
2806  Output_file_stream << 0 << " " << resid <<std::endl;
2807  }
2808  } // if (Doc_convergence_history)
2809 
2810  // If the GMRES algorithm converges immediately
2811  if (resid<=Tolerance)
2812  {
2813  // If time documentation is enabled
2814  if(Doc_time)
2815  {
2816  // Notify the user
2817  oomph_info << "GMRES converged immediately. Normalised residual norm: "
2818  << resid << std::endl;
2819  }
2820 
2821  // Finish running the solver
2822  return;
2823  } // if (resid<=Tolerance)
2824 
2825  // Initialise a vector of orthogonal basis vectors
2826  Vector<Vector<DoubleVector> > block_v;
2827 
2828  // Resize the number of vectors needed
2829  block_v.resize(n_row+1);
2830 
2831  // Resize each Vector of DoubleVectors to store the real and imaginary
2832  // part of a given vector
2833  for (unsigned dof_type=0;dof_type<n_row+1;dof_type++)
2834  {
2835  // Create two DoubleVector objects in each Vector
2836  block_v[dof_type].resize(n_dof_types);
2837  }
2838 
2839  // Initialise the upper hessenberg matrix. Since we are not using
2840  // distributed vectors here, the algebra is best done using entries
2841  // of the type std::complex<double>. NOTE: For implementation purposes
2842  // the upper Hessenberg matrix indices are swapped so the matrix is
2843  // effectively transposed
2844  Vector<Vector<std::complex<double> > > hessenberg(n_row+1);
2845 
2846  // Build the zeroth basis vector
2847  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
2848  {
2849  // Build the k-th part of the zeroth vector. Here k=0 and k=1 correspond
2850  // to the real and imaginary part of the zeroth vector, respectively
2851  block_v[0][dof_type].build(this->block_distribution_pt(dof_type),0.0);
2852  }
2853 
2854  // Loop over the real and imaginary parts of v
2855  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
2856  {
2857  // For fast access
2858  double* v0_pt=block_v[0][dof_type].values_pt();
2859 
2860  // For fast access
2861  const double* block_r_pt=block_r[dof_type].values_pt();
2862 
2863  // Set the zeroth basis vector v[0] to r/beta
2864  for (unsigned i=0;i<n_row;i++)
2865  {
2866  // Assign the i-th entry of the zeroth basis vector
2867  v0_pt[i]=block_r_pt[i]/beta;
2868  }
2869  } // for (unsigned k=0;k<n_dof_types;k++)
2870 
2871  // Set the first entry in the minimisation problem RHS vector (is meant
2872  // to the vector beta*e_1 initially, where e_1 is the unit vector with
2873  // one in its first entry)
2874  s[0]=beta;
2875 
2876  // Compute the next step of the iterative scheme
2877  for (unsigned j=0;j<Max_iter;j++)
2878  {
2879  // Resize the next column of the upper hessenberg matrix
2880  hessenberg[j].resize(j+2,std::complex<double>(0.0,0.0));
2881 
2882  // Calculate w=M^{-1}(Jv[j]) (LHS prec.) or w=JM^{-1}v (RHS prec.)
2883  {
2884  // Create a temporary vector
2886 
2887  // Create a temporary vector
2889 
2890  // Create a temporary vector
2892 
2893  // Create a temporary vector of DoubleVectors
2894  Vector<DoubleVector> block_temp(2);
2895 
2896  // Create two DoubleVectors
2897  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
2898  {
2899  block_temp[dof_type].build(this->block_distribution_pt(dof_type),0.0);
2900  }
2901 
2902  // If we're using LHS preconditioning
2903  if(Preconditioner_LHS)
2904  {
2905  // Solve Jv[j]=Mw for w. Note, we cannot use inbuilt complex matrix
2906  // algebra here as we're using distributed vectors
2907  complex_matrix_multiplication(Matrices_storage_pt,block_v[j],block_temp);
2908 
2909  // Copy block_temp into temp
2910  this->return_block_vectors(block_temp,temp);
2911 
2912  // Copy block_w into w
2913  this->return_block_vectors(block_w,w);
2914 
2915  // Apply the preconditioner
2916  this->preconditioner_pt()->preconditioner_solve(temp,w);
2917 
2918  // Copy w into block_w
2919  this->get_block_vectors(w,block_w);
2920  }
2921  // If we're using RHS preconditioning
2922  else
2923  {
2924  // Copy the real and imaginary part of v[j] into one vector, vj
2925  this->return_block_vectors(block_v[j],vj);
2926 
2927  // Use w=JM^{-1}v by saad p270
2928  this->preconditioner_pt()->preconditioner_solve(vj,temp);
2929 
2930  // Copy w into block_w
2931  this->get_block_vectors(temp,block_temp);
2932 
2933  // Solve Jv[j] = Mw for w. Note, we cannot use inbuilt complex matrix
2934  // algebra here as we're using distributed vectors
2935  complex_matrix_multiplication(Matrices_storage_pt,block_temp,block_w);
2936  }
2937  } // Calculate w=M^{-1}(Jv[j]) (LHS prec.) or w=JM^{-1}v (RHS prec.)
2938 
2939  // For fast access
2940  double* block_w_r_pt=block_w[0].values_pt();
2941 
2942  // For fast access
2943  double* block_w_c_pt=block_w[1].values_pt();
2944 
2945  // Loop over all of the entries on and above the principal subdiagonal of
2946  // the Hessenberg matrix in the j-th column (remembering that
2947  // the indices of the upper Hessenberg matrix are swapped for the purpose
2948  // of implementation)
2949  for (unsigned i=0;i<j+1;i++)
2950  {
2951  // For fast access
2952  const double* vi_r_pt=block_v[i][0].values_pt();
2953 
2954  // For fast access
2955  const double* vi_c_pt=block_v[i][1].values_pt();
2956 
2957  // Loop over the entries of v and w
2958  for (unsigned k=0;k<n_row;k++)
2959  {
2960  // Store the appropriate entry in v as a complex value
2961  std::complex<double> complex_v(vi_r_pt[k],vi_c_pt[k]);
2962 
2963  // Store the appropriate entry in w as a complex value
2964  std::complex<double> complex_w(block_w_r_pt[k],block_w_c_pt[k]);
2965 
2966  // Update the value of H(i,j) noting we're computing a complex
2967  // inner product here (the ordering is very important here!)
2968  hessenberg[j][i]+=std::conj(complex_v)*complex_w;
2969  }
2970 
2971  // Orthonormalise w against all previous orthogonal vectors, v_i by
2972  // looping over its entries and updating them
2973  for (unsigned k=0;k<n_row;k++)
2974  {
2975  // Update the real part of the k-th entry of w
2976  block_w_r_pt[k]-=(hessenberg[j][i].real()*vi_r_pt[k]-
2977  hessenberg[j][i].imag()*vi_c_pt[k]);
2978 
2979  // Update the imaginary part of the k-th entry of w
2980  block_w_c_pt[k]-=(hessenberg[j][i].real()*vi_c_pt[k]+
2981  hessenberg[j][i].imag()*vi_r_pt[k]);
2982  }
2983  } // for (unsigned i=0;i<j+1;i++)
2984 
2985  // Calculate the norm of the real part of w
2986  norm_r=block_w[0].norm();
2987 
2988  // Calculate the norm of the imaginary part of w
2989  norm_c=block_w[1].norm();
2990 
2991  // Calculate the norm of the vector w using norm_r and norm_c and assign
2992  // its value to the appropriate entry in the Hessenberg matrix
2993  hessenberg[j][j+1]=sqrt(pow(norm_r,2.0)+pow(norm_c,2.0));
2994 
2995  // Build the (j+1)-th basis vector
2996  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
2997  {
2998  // Build the k-th part of the zeroth vector. Here k=0 and k=1 correspond
2999  // to the real and imaginary part of the zeroth vector, respectively
3000  block_v[j+1][dof_type].build(this->block_distribution_pt(dof_type),0.0);
3001  }
3002 
3003  // Check if the value of hessenberg[j][j+1] is zero. If it
3004  // isn't then we update the next entries in v
3005  if (hessenberg[j][j+1]!=0.0)
3006  {
3007  // For fast access
3008  double* v_r_pt=block_v[j+1][0].values_pt();
3009 
3010  // For fast access
3011  double* v_c_pt=block_v[j+1][1].values_pt();
3012 
3013  // For fast access
3014  const double* block_w_r_pt=block_w[0].values_pt();
3015 
3016  // For fast access
3017  const double* block_w_c_pt=block_w[1].values_pt();
3018 
3019  // Notice, the value of H(j,j+1), as calculated above, is clearly a real
3020  // number. As such, calculating the division
3021  // v_{j+1}=w_{j}/h_{j+1,j},
3022  // here is simple, i.e. we don't need to worry about cross terms in the
3023  // algebra. To avoid computing h_{j+1,j} several times we precompute it
3024  double h_subdiag_val=hessenberg[j][j+1].real();
3025 
3026  // Loop over the entries of the new orthogonal vector and set its values
3027  for(unsigned k=0;k<n_row;k++)
3028  {
3029  // The i-th entry of the real component is given by
3030  v_r_pt[k]=block_w_r_pt[k]/h_subdiag_val;
3031 
3032  // Similarly, the i-th entry of the imaginary component is given by
3033  v_c_pt[k]=block_w_c_pt[k]/h_subdiag_val;
3034  }
3035  }
3036  // Otherwise, we have to jump to the next part of the algorithm; if
3037  // the value of hessenberg[j][j+1] is zero then the norm of the latest
3038  // orthogonal vector is zero. This is only possible if the entries
3039  // in w are all zero. As a result, the Krylov space of A and r_0 has
3040  // been spanned by the previously calculated orthogonal vectors
3041  else
3042  {
3043  // Book says "Set m=j and jump to step 11" (p.172)...
3044  // Do something here!
3045  oomph_info << "Subdiagonal Hessenberg entry is zero. "
3046  << "Do something here..." << std::endl;
3047  } // if (hessenberg[j][j+1]!=0.0)
3048 
3049  // Loop over the entries in the Hessenberg matrix and calculate the
3050  // entries of the Givens rotation matrices
3051  for (unsigned k=0;k<j;k++)
3052  {
3053  // Apply the plane rotation to all of the previous entries in the
3054  // (j)-th column (remembering the indexing is reversed)
3055  apply_plane_rotation(hessenberg[j][k],hessenberg[j][k+1],cs[k],sn[k]);
3056  }
3057 
3058  // Now calculate the entries of the latest Givens rotation matrix
3059  generate_plane_rotation(hessenberg[j][j],hessenberg[j][j+1],cs[j],sn[j]);
3060 
3061  // Apply the plane rotation using the newly calculated entries
3062  apply_plane_rotation(hessenberg[j][j],hessenberg[j][j+1],cs[j],sn[j]);
3063 
3064  // Apply a plane rotation to the corresponding entry in the vector
3065  // s used in the minimisation problem, J(y)=min||s-R_m*y||
3066  apply_plane_rotation(s[j],s[j+1],cs[j],sn[j]);
3067 
3068  // Compute current residual using equation (6.42) in Saad Y, "Iterative
3069  // methods for sparse linear systems", p.177.]. Note, since s has complex
3070  // entries we have to use std::abs instead of std::fabs
3071  beta=std::abs(s[j+1]);
3072 
3073  // Compute the relative residual
3074  resid=beta/normb;
3075 
3076  // If required will document convergence history to screen or file (if
3077  // stream open)
3079  {
3080  // If an output file which is open isn't provided then output to screen
3081  if (!Output_file_stream.is_open())
3082  {
3083  // Output the residual value to the screen
3084  oomph_info << j+1 << " " << resid << std::endl;
3085  }
3086  // Otherwise, output to file
3087  else
3088  {
3089  // Output the residual value to file
3090  Output_file_stream << j+1 << " " << resid <<std::endl;
3091  }
3092  } // if (Doc_convergence_history)
3093 
3094  // If the required tolerance has been met
3095  if (resid<Tolerance)
3096  {
3097  // Store the number of iterations taken
3098  Iterations=j+1;
3099 
3100  // Update the result vector using the result, x=x_0+V_m*y (where V_m
3101  // is given by v here)
3102  update(j,hessenberg,s,block_v,block_solution);
3103 
3104  // Copy the vectors in block_solution to solution
3105  this->return_block_vectors(block_solution,solution);
3106 
3107  // If time documentation was enabled
3108  if(Doc_time)
3109  {
3110  // Output the current normalised residual norm
3111  oomph_info << "\nGMRES converged (1). Normalised residual norm: "
3112  << resid << std::endl;
3113 
3114  // Output the number of iterations it took for convergence
3115  oomph_info << "Number of iterations to convergence: "
3116  << j+1 << "\n" << std::endl;
3117  }
3118 
3119  // Stop the timer
3120  double t_end=TimingHelpers::timer();
3121 
3122  // Calculate the time taken to calculate the solution
3123  Solution_time=t_end-t_start;
3124 
3125  // If time documentation was enabled
3126  if(Doc_time)
3127  {
3128  // Output the time taken to solve the problem using GMRES
3129  oomph_info << "Time for solve with GMRES [sec]: "
3130  << Solution_time << std::endl;
3131  }
3132 
3133  // As we've met the tolerance for the solver and everything that should
3134  // be documented, has been, finish using the solver
3135  return;
3136  } // if (resid<Tolerance)
3137  } // for (unsigned j=0;j<Max_iter;j++)
3138 
3139  // Store the number of iterations taken
3140  Iterations=Max_iter;
3141 
3142  // Only update if we actually did something
3143  if (Max_iter>0)
3144  {
3145  // Update the result vector using the result, x=x_0+V_m*y (where V_m
3146  // is given by v here)
3147  update(Max_iter-1,hessenberg,s,block_v,block_solution);
3148 
3149  // Copy the vectors in block_solution to solution
3150  this->return_block_vectors(block_solution,solution);
3151  }
3152 
3153  // Solve Mr=(b-Jx) for r
3154  {
3155  // Create a temporary vector of DoubleVectors
3156  Vector<DoubleVector> block_temp(2);
3157 
3158  // Create two DoubleVectors
3159  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
3160  {
3161  // Build the distribution of the (dof_type)-th vector
3162  block_temp[dof_type].build(this->block_distribution_pt(dof_type),0.0);
3163  }
3164 
3165  // Calculate the value of Jx
3166  complex_matrix_multiplication(Matrices_storage_pt,block_solution,block_temp);
3167 
3168  // Get the values pointer of the vector (real)
3169  double* block_temp_r_pt=block_temp[0].values_pt();
3170 
3171  // Get the values pointer of the vector (imaginary)
3172  double* block_temp_c_pt=block_temp[1].values_pt();
3173 
3174  // Get the values pointer of the RHS vector (real)
3175  const double* block_rhs_r_pt=block_rhs[0].values_pt();
3176 
3177  // Get the values pointer of the RHS vector (imaginary)
3178  const double* block_rhs_c_pt=block_rhs[1].values_pt();
3179 
3180  // Loop over the dofs
3181  for (unsigned i=0;i<n_row;i++)
3182  {
3183  // Calculate b-Jx (real)
3184  block_temp_r_pt[i]=block_rhs_r_pt[i]-block_temp_r_pt[i];
3185 
3186  // Calculate b-Jx (imaginary)
3187  block_temp_c_pt[i]=block_rhs_c_pt[i]-block_temp_c_pt[i];
3188  }
3189 
3190  // If we're using LHS preconditioning
3191  if(Preconditioner_LHS)
3192  {
3193  // Create a temporary DoubleVectors
3195 
3196  // Create a vector of the same size as rhs
3198 
3199  // Copy the vectors in r to full_r
3200  this->return_block_vectors(block_temp,temp);
3201 
3202  // Copy the vectors in r to full_r
3203  this->return_block_vectors(block_r,r);
3204 
3205  // Apply the preconditioner
3207  }
3208  }
3209 
3210  // Compute the current residual
3211  beta=0.0;
3212 
3213  // Get access to the values pointer (real)
3214  norm_r=block_r[0].norm();
3215 
3216  // Get access to the values pointer (imaginary)
3217  norm_c=block_r[1].norm();
3218 
3219  // Calculate the full norm
3220  beta=sqrt(pow(norm_r,2.0)+pow(norm_c,2.0));
3221 
3222  // Calculate the relative residual
3223  resid=beta/normb;
3224 
3225  // If the relative residual lies within tolerance
3226  if (resid<Tolerance)
3227  {
3228  // If time documentation is enabled
3229  if(Doc_time)
3230  {
3231  // Notify the user
3232  oomph_info << "\nGMRES converged (2). Normalised residual norm: "
3233  << resid
3234  << "\nNumber of iterations to convergence: "
3235  << Iterations << "\n" << std::endl;
3236  }
3237 
3238  // End the timer
3239  double t_end=TimingHelpers::timer();
3240 
3241  // Calculate the time taken for the solver
3242  Solution_time=t_end-t_start;
3243 
3244  // If time documentation is enabled
3245  if(Doc_time)
3246  {
3247  oomph_info << "Time for solve with GMRES [sec]: "
3248  << Solution_time << std::endl;
3249  }
3250  return;
3251  }
3252 
3253  // Otherwise GMRES failed convergence
3254  oomph_info << "\nGMRES did not converge to required tolerance! "
3255  << "\nReturning with normalised residual norm: " << resid
3256  << "\nafter " << Max_iter << " iterations.\n" << std::endl;
3257 
3258  // Throw an error if requested
3260  {
3261  std::string err="Solver failed to converge and you requested an error";
3262  err+=" on convergence failures.";
3263  throw OomphLibError(err,OOMPH_EXCEPTION_LOCATION,
3264  OOMPH_CURRENT_FUNCTION);
3265  }
3266 
3267  // Finish using the solver
3268  return;
3269  } // End of solve_helper
3270 
3271 
3272 ///////////////////////////////////////////////////////////////////////
3273 ///////////////////////////////////////////////////////////////////////
3274 ///////////////////////////////////////////////////////////////////////
3275 
3276 
3277 //======================================================================
3278 /// \short The FGMRES method, i.e. the flexible variant of the GMRES
3279 /// method which allows for nonconstant preconditioners [see Saad Y,
3280 /// "Iterative methods for sparse linear systems", p.287]. Note, FGMRES
3281 /// can only cater to right preconditioning; if the user tries to switch
3282 /// to left preconditioning they will be notified of this
3283 //======================================================================
3284 template<typename MATRIX>
3285 class HelmholtzFGMRESMG : public virtual HelmholtzGMRESMG<MATRIX>
3286 {
3287 
3288  public:
3289 
3290  /// Constructor (empty)
3292  {
3293  // Can only use RHS preconditioning
3294  this->Preconditioner_LHS=false;
3295  };
3296 
3297  /// Destructor (cleanup storage)
3299  {
3300  // Call the clean up function in the base class GMRES
3301  this->clean_up_memory();
3302  }
3303 
3304  /// Broken copy constructor
3306  {
3307  BrokenCopy::broken_copy("HelmholtzFGMRESMG");
3308  }
3309 
3310  /// Broken assignment operator
3312  {
3313  BrokenCopy::broken_assign("HelmholtzFGMRESMG");
3314  }
3315 
3316  /// \short Overloaded function to let the user know that left preconditioning
3317  /// is not possible with FGMRES, only right preconditioning
3319  {
3320  // Create an output stream
3321  std::ostringstream error_message_stream;
3322 
3323  // Create an error message
3324  error_message_stream << "FGMRES cannot use left preconditioning. It is "
3325  << "only capable of using right preconditioning."
3326  << std::endl;
3327 
3328  // Throw the error message
3329  throw OomphLibError(error_message_stream.str(),
3330  OOMPH_CURRENT_FUNCTION,
3331  OOMPH_EXCEPTION_LOCATION);
3332  } // End of set_preconditioner_LHS
3333 
3334  /// \short Solver: Takes pointer to problem and returns the results vector
3335  /// which contains the solution of the linear system defined by
3336  /// the problem's fully assembled Jacobian and residual vector.
3337  void solve(Problem* const &problem_pt,DoubleVector &result)
3338  {
3339 #ifdef OOMPH_HAS_MPI
3340  // Make sure that this is running in serial. Can't guarantee it'll
3341  // work when the problem is distributed over several processors
3342  if (MPI_Helpers::communicator_pt()->nproc()>1)
3343  {
3344  // Throw a warning
3345  OomphLibWarning("Can't guarantee the MG solver will work in parallel!",
3346  OOMPH_CURRENT_FUNCTION,
3347  OOMPH_EXCEPTION_LOCATION);
3348  }
3349 #endif
3350 
3351  // Find # of degrees of freedom (variables)
3352  unsigned n_dof=problem_pt->ndof();
3353 
3354  // Initialise timer
3355  double t_start=TimingHelpers::timer();
3356 
3357  // We're not re-solving
3358  this->Resolving=false;
3359 
3360  // Get rid of any previously stored data
3361  this->clean_up_memory();
3362 
3363  // Grab the communicator from the MGProblem object and assign it
3364  this->set_comm_pt(problem_pt->communicator_pt());
3365 
3366  // Setup the distribution
3367  LinearAlgebraDistribution dist(problem_pt->communicator_pt(),n_dof,false);
3368 
3369  // Build the internal distribution in this way because both the
3370  // IterativeLinearSolver and BlockPreconditioner class have base-
3371  // class subobjects of type oomph::DistributableLinearAlgebraObject
3373 
3374  // Get Jacobian matrix in format specified by template parameter
3375  // and nonlinear residual vector
3376  MATRIX* matrix_pt=new MATRIX;
3377  DoubleVector f;
3378  if (dynamic_cast<DistributableLinearAlgebraObject*>(matrix_pt) != 0)
3379  {
3380  if (dynamic_cast<CRDoubleMatrix*>(matrix_pt) != 0)
3381  {
3382  dynamic_cast<CRDoubleMatrix* >(matrix_pt)->
3385  }
3386  }
3387 
3388  // Get the Jacobian and residuals vector
3389  problem_pt->get_jacobian(f,*matrix_pt);
3390 
3391  // We've made the matrix, we can delete it...
3392  this->Matrix_can_be_deleted=true;
3393 
3394  // Replace the current matrix used in Preconditioner by the new matrix
3395  this->set_matrix_pt(matrix_pt);
3396 
3397  // The preconditioner works with one mesh; set it! Since we only use
3398  // the block preconditioner on the finest level, we use the mesh from
3399  // that level
3400  this->set_nmesh(1);
3401 
3402  // Elements in actual pml layer are trivially wrapped versions of
3403  // their bulk counterparts. Technically they are different elements
3404  // so we have to allow different element types
3405  bool allow_different_element_types_in_mesh=true;
3406  this->set_mesh(0,problem_pt->mesh_pt(),
3407  allow_different_element_types_in_mesh);
3408 
3409  // Set up the generic block look up scheme
3410  this->block_setup();
3411 
3412  // Extract the number of blocks.
3413  unsigned nblock_types=this->nblock_types();
3414 
3415 #ifdef PARANOID
3416  // PARANOID check - there must only be two block types
3417  if (nblock_types!=2)
3418  {
3419  // Create the error message
3420  std::stringstream tmp;
3421  tmp << "There are supposed to be two block types.\nYours has "
3422  << nblock_types << std::endl;
3423 
3424  // Throw an error
3425  throw OomphLibError(tmp.str(),
3426  OOMPH_CURRENT_FUNCTION,
3427  OOMPH_EXCEPTION_LOCATION);
3428  }
3429 #endif
3430 
3431  // Resize the storage for the system matrices
3432  this->Matrices_storage_pt.resize(2,0);
3433 
3434  // Loop over the rows of the block matrix
3435  for (unsigned i=0;i<nblock_types;i++)
3436  {
3437  // Fix the column index
3438  unsigned j=0;
3439 
3440  // Create new CRDoubleMatrix objects
3441  this->Matrices_storage_pt[i]=new CRDoubleMatrix;
3442 
3443  // Extract the required blocks, i.e. the first column
3444  this->get_block(i,j,*(this->Matrices_storage_pt[i]));
3445  }
3446 
3447  // Doc time for setup
3448  double t_end=TimingHelpers::timer();
3449  this->Jacobian_setup_time=t_end-t_start;
3450 
3451  if(this->Doc_time)
3452  {
3453  oomph_info << "\nTime for setup of block Jacobian [sec]: "
3454  << this->Jacobian_setup_time << std::endl;
3455  }
3456 
3457  // Call linear algebra-style solver
3458  // If the result distribution is wrong, then redistribute
3459  // before the solve and return to original distribution
3460  // afterwards
3462  &&(result.built()))
3463  {
3464  // Make a distribution object
3465  LinearAlgebraDistribution temp_global_dist(result.distribution_pt());
3466 
3467  // Build the result vector distribution
3469 
3470  // Solve the problem
3471  solve_helper(matrix_pt,f,result);
3472 
3473  // Redistribute the vector
3474  result.redistribute(&temp_global_dist);
3475  }
3476  // Otherwise just solve
3477  else
3478  {
3479  // Solve
3480  solve_helper(matrix_pt,f,result);
3481  }
3482 
3483  // Kill matrix unless it's still required for resolve
3484  if (!(this->Enable_resolve))
3485  {
3486  // Clean up anything left in memory
3487  this->clean_up_memory();
3488  }
3489  } // End of solve
3490 
3491  private:
3492 
3493  /// General interface to solve function
3494  void solve_helper(DoubleMatrixBase* const &matrix_pt,
3495  const DoubleVector &rhs,
3496  DoubleVector &solution);
3497 
3498  /// Helper function to update the result vector
3499  void update(const unsigned& k,
3500  const Vector<Vector<std::complex<double> > >& hessenberg,
3501  const Vector<std::complex<double> >& s,
3502  const Vector<Vector<DoubleVector> >& z_m,
3504  {
3505  // Make a local copy of s
3507 
3508  //-----------------------------------------------------------------
3509  // The Hessenberg matrix should be an upper triangular matrix at
3510  // this point (although from its storage it would appear to be a
3511  // lower triangular matrix since the indexing has been reversed)
3512  // so finding the minimiser of J(y)=min||s-R_m*y|| where R_m is
3513  // the matrix R in the QR factorisation of the Hessenberg matrix.
3514  // Therefore, to obtain y we simply need to use a backwards
3515  // substitution. Note: The implementation here may appear to be
3516  // somewhat confusing as the indexing in the Hessenberg matrix is
3517  // reversed. This implementation of a backwards substitution does
3518  // not run along the columns of the triangular matrix but rather
3519  // up the rows.
3520  //-----------------------------------------------------------------
3521 
3522  // The outer loop is a loop over the columns of the Hessenberg matrix
3523  // since the indexing is reversed
3524  for (int i=int(k);i>=0;i--)
3525  {
3526  // Divide the i-th entry of y by the i-th diagonal entry of H
3527  y[i]/=hessenberg[i][i];
3528 
3529  // The inner loop is a loop over the rows of the Hessenberg matrix
3530  for (int j=i-1;j>=0;j--)
3531  {
3532  // Update the j-th entry of y
3533  y[j]-=hessenberg[i][j]*y[i];
3534  }
3535  } // for (int i=int(k);i>=0;i--)
3536 
3537  // Calculate the number of entries in x (simply use the real part as
3538  // both the real and imaginary part should have the same length)
3539  unsigned n_row=x[0].nrow();
3540 
3541  // Build a temporary vector with entries initialised to 0.0
3542  Vector<DoubleVector> block_update(2);
3543 
3544  // Build the distributions
3545  for (unsigned dof_type=0;dof_type<2;dof_type++)
3546  {
3547  // Build the (dof_type)-th vector of block_update
3548  block_update[dof_type].build(x[0].distribution_pt(),0.0);
3549  }
3550 
3551  // Get access to the underlying values
3552  double* block_update_r_pt=block_update[0].values_pt();
3553 
3554  // Get access to the underlying values
3555  double* block_update_c_pt=block_update[1].values_pt();
3556 
3557  // Calculate x=Vy
3558  for (unsigned j=0;j<=k;j++)
3559  {
3560  // Get access to j-th column of Z_m
3561  const double* z_mj_r_pt=z_m[j][0].values_pt();
3562 
3563  // Get access to j-th column of Z_m
3564  const double* z_mj_c_pt=z_m[j][1].values_pt();
3565 
3566  // Loop over the entries in x and update them
3567  for (unsigned i=0;i<n_row;i++)
3568  {
3569  // Update the real part of the i-th entry in x
3570  block_update_r_pt[i]+=(z_mj_r_pt[i]*y[j].real())-(z_mj_c_pt[i]*y[j].imag());
3571 
3572  // Update the imaginary part of the i-th entry in x
3573  block_update_c_pt[i]+=(z_mj_c_pt[i]*y[j].real())+(z_mj_r_pt[i]*y[j].imag());
3574  }
3575  } // for (unsigned j=0;j<=k;j++)
3576 
3577  // Use the update: x_m=x_0+inv(M)Vy [see Saad Y,"Iterative methods for
3578  // sparse linear systems", p.284]
3579  for (unsigned dof_type=0;dof_type<2;dof_type++)
3580  {
3581  // Update the solution
3582  x[dof_type]+=block_update[dof_type];
3583  }
3584  } // End of update
3585 };
3586 
3587 
3588 //////////////////////////////////////////////////////////////////////
3589 //////////////////////////////////////////////////////////////////////
3590 //////////////////////////////////////////////////////////////////////
3591 
3592 
3593 //=============================================================================
3594 /// Linear-algebra-type solver: Takes pointer to a matrix and rhs vector
3595 /// and returns the solution of the linear system.
3596 /// based on the algorithm presented in Templates for the
3597 /// Solution of Linear Systems: Building Blocks for Iterative Methods, Barrett,
3598 /// Berry et al, SIAM, 2006 and the implementation in the IML++ library :
3599 /// http://math.nist.gov/iml++/
3600 //=============================================================================
3601  template <typename MATRIX>
3604  const DoubleVector& rhs,
3605  DoubleVector& solution)
3606  {
3607  // Set the number of dof types (real and imaginary for this solver)
3608  unsigned n_dof_types=this->ndof_types();
3609 
3610 #ifdef PARANOID
3611  // This only works for 2 dof types
3612  if (n_dof_types!=2)
3613  {
3614  // Create an output stream
3615  std::stringstream error_message_stream;
3616 
3617  // Create the error message
3618  error_message_stream << "This preconditioner only works for problems "
3619  << "with 2 dof types\nYours has " << n_dof_types;
3620 
3621  // Throw the error message
3622  throw OomphLibError(error_message_stream.str(),
3623  OOMPH_CURRENT_FUNCTION,
3624  OOMPH_EXCEPTION_LOCATION);
3625  }
3626 #endif
3627 
3628  // Get the number of dofs (note, the total number of dofs in the problem
3629  // is 2*n_row but if the constituent vectors and matrices were stored in
3630  // complex objects there would only be (n_row) rows so we use that)
3631  unsigned n_row=this->Matrices_storage_pt[0]->nrow();
3632 
3633  // Make sure Max_iter isn't greater than n_dof. The user cannot use this
3634  // many iterations when using Krylov subspace methods
3635  if (this->Max_iter>n_row)
3636  {
3637  // Create an output string stream
3638  std::ostringstream error_message_stream;
3639 
3640  // Create the error message
3641  error_message_stream << "The maximum number of iterations cannot exceed "
3642  << "the number of rows in the problem."
3643  << "\nMaximum number of iterations: " << this->Max_iter
3644  << "\nNumber of rows: " << n_row
3645  << std::endl;
3646 
3647  // Throw the error message
3648  throw OomphLibError(error_message_stream.str(),
3649  OOMPH_CURRENT_FUNCTION,
3650  OOMPH_EXCEPTION_LOCATION);
3651  }
3652 
3653 #ifdef PARANOID
3654  // Loop over the real and imaginary parts
3655  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
3656  {
3657  // PARANOID check that if the matrix is distributable then it should not be
3658  // then it should not be distributed
3659  if (dynamic_cast<DistributableLinearAlgebraObject*>
3660  (this->Matrices_storage_pt[dof_type])!=0)
3661  {
3662  if (dynamic_cast<DistributableLinearAlgebraObject*>
3663  (this->Matrices_storage_pt[dof_type])->distributed())
3664  {
3665  std::ostringstream error_message_stream;
3666  error_message_stream << "The matrix must not be distributed.";
3667  throw OomphLibError(error_message_stream.str(),
3668  OOMPH_CURRENT_FUNCTION,
3669  OOMPH_EXCEPTION_LOCATION);
3670  }
3671  }
3672  }
3673  // PARANOID check that this rhs distribution is setup
3674  if (!rhs.built())
3675  {
3676  std::ostringstream error_message_stream;
3677  error_message_stream << "The rhs vector distribution must be setup.";
3678  throw OomphLibError(error_message_stream.str(),
3679  OOMPH_CURRENT_FUNCTION,
3680  OOMPH_EXCEPTION_LOCATION);
3681  }
3682  // PARANOID check that the rhs has the right number of global rows
3683  if(rhs.nrow()!=2*n_row)
3684  {
3685  std::ostringstream error_message_stream;
3686  error_message_stream << "RHS does not have the same dimension as the"
3687  << " linear system";
3688  throw OomphLibError(error_message_stream.str(),
3689  OOMPH_CURRENT_FUNCTION,
3690  OOMPH_EXCEPTION_LOCATION);
3691  }
3692  // PARANOID check that the rhs is not distributed
3693  if (rhs.distribution_pt()->distributed())
3694  {
3695  std::ostringstream error_message_stream;
3696  error_message_stream << "The rhs vector must not be distributed.";
3697  throw OomphLibError(error_message_stream.str(),
3698  OOMPH_CURRENT_FUNCTION,
3699  OOMPH_EXCEPTION_LOCATION);
3700  }
3701  // PARANOID check that if the result is setup it matches the distribution
3702  // of the rhs
3703  if (solution.built())
3704  {
3705  if (!(*rhs.distribution_pt()==*solution.distribution_pt()))
3706  {
3707  std::ostringstream error_message_stream;
3708  error_message_stream << "If the result distribution is setup then it "
3709  << "must be the same as the rhs distribution";
3710  throw OomphLibError(error_message_stream.str(),
3711  OOMPH_CURRENT_FUNCTION,
3712  OOMPH_EXCEPTION_LOCATION);
3713  }
3714  } // if (solution[dof_type].built())
3715 #endif
3716 
3717  // Set up the solution distribution if it's not already distributed
3718  if (!solution.built())
3719  {
3720  // Build the distribution
3722  }
3723  // Otherwise initialise all entries to zero
3724  else
3725  {
3726  // Initialise the entries in the k-th vector in solution to zero
3727  solution.initialise(0.0);
3728  }
3729 
3730  // Create a vector of DoubleVectors (this is a distributed vector so we have
3731  // to create two separate DoubleVector objects to cope with the arithmetic)
3732  Vector<DoubleVector> block_solution(n_dof_types);
3733 
3734  // Create a vector of DoubleVectors (this is a distributed vector so we have
3735  // to create two separate DoubleVector objects to cope with the arithmetic)
3736  Vector<DoubleVector> block_rhs(n_dof_types);
3737 
3738  // Build the distribution of both vectors
3739  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
3740  {
3741  // Build the distribution of the k-th constituent vector
3742  block_solution[dof_type].build(this->block_distribution_pt(dof_type),0.0);
3743 
3744  // Build the distribution of the k-th constituent vector
3745  block_rhs[dof_type].build(this->block_distribution_pt(dof_type),0.0);
3746  }
3747 
3748  // Grab the solution vector in block form
3749  this->get_block_vectors(solution,block_solution);
3750 
3751  // Grab the RHS vector in block form
3752  this->get_block_vectors(rhs,block_rhs);
3753 
3754  // Start the solver timer
3755  double t_start=TimingHelpers::timer();
3756 
3757  // Storage for the relative residual
3758  double resid;
3759 
3760  // Initialise vectors (since these are not distributed vectors we template
3761  // one vector by the type std::complex<double> instead of using two vectors,
3762  // each templated by the type double
3763 
3764  // Vector, s, used in the minimisation problem: J(y)=min||s-R_m*y||
3765  // [see Saad Y."Iterative methods for sparse linear systems", p.176.]
3766  Vector<std::complex<double> > s(n_row+1,std::complex<double>(0.0,0.0));
3767 
3768  // Vector to store the value of cos(theta) when using the Givens rotation
3769  Vector<std::complex<double> > cs(n_row+1,std::complex<double>(0.0,0.0));
3770 
3771  // Vector to store the value of sin(theta) when using the Givens rotation
3772  Vector<std::complex<double> > sn(n_row+1,std::complex<double>(0.0,0.0));
3773 
3774  // Create a vector of DoubleVectors (this is a distributed vector so we have
3775  // to create two separate DoubleVector objects to cope with the arithmetic)
3776  Vector<DoubleVector> block_w(n_dof_types);
3777 
3778  // Build the distribution of both vectors
3779  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
3780  {
3781  // Build the distribution of the k-th constituent vector
3782  block_w[dof_type].build(this->block_distribution_pt(dof_type),0.0);
3783  }
3784 
3785  // Set up the preconditioner only if we're not re-solving
3786  if (!(this->Resolving))
3787  {
3788  // Only set up the preconditioner before solve if required
3790  {
3791  // Set up the preconditioner from the Jacobian matrix
3792  double t_start_prec=TimingHelpers::timer();
3793 
3794  // Use the setup function in the Preconditioner class
3795  this->preconditioner_pt()->setup(dynamic_cast<MATRIX*>(matrix_pt));
3796 
3797  // Doc time for setup of preconditioner
3798  double t_end_prec=TimingHelpers::timer();
3799  this->Preconditioner_setup_time=t_end_prec-t_start_prec;
3800 
3801  // If time documentation is enabled
3802  if(this->Doc_time)
3803  {
3804  // Output the time taken
3805  oomph_info << "Time for setup of preconditioner [sec]: "
3806  << this->Preconditioner_setup_time << std::endl;
3807  }
3808  }
3809  }
3810  else
3811  {
3812  // If time documentation is enabled
3813  if(this->Doc_time)
3814  {
3815  // Notify the user
3816  oomph_info << "Setup of preconditioner is bypassed in resolve mode"
3817  << std::endl;
3818  }
3819  } // if (!Resolving) else
3820 
3821  // Create a vector of DoubleVectors to store the RHS of b-Jx=Mr. We assume
3822  // both x=0 and that a preconditioner is not applied by which we deduce b=r
3823  Vector<DoubleVector> block_r(n_dof_types);
3824 
3825  // Build the distribution of both vectors
3826  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
3827  {
3828  // Build the distribution of the k-th constituent vector
3829  block_r[dof_type].build(this->block_distribution_pt(dof_type),0.0);
3830  }
3831 
3832  // Store the value of b (the RHS vector) in r
3833  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
3834  {
3835  // Copy the entries of rhs into r
3836  block_r[dof_type]=block_rhs[dof_type];
3837  }
3838 
3839  // Calculate the norm of the real part of r
3840  double norm_r=block_r[0].norm();
3841 
3842  // Calculate the norm of the imaginary part of r
3843  double norm_c=block_r[1].norm();
3844 
3845  // Compute norm(r)
3846  double normb=sqrt(pow(norm_r,2.0)+pow(norm_c,2.0));
3847 
3848  // Set the value of beta (the initial residual)
3849  double beta=normb;
3850 
3851  // Compute the initial relative residual. If the entries of the RHS vector
3852  // are all zero then set normb equal to one. This is because we divide the
3853  // value of the norm at later stages by normb and dividing by zero is not
3854  // definied
3855  if (normb==0.0)
3856  {
3857  // Set the value of normb
3858  normb=1.0;
3859  }
3860 
3861  // Calculate the ratio between the initial norm and the current norm.
3862  // Since we haven't completed an iteration yet this will simply be one
3863  // unless normb was zero, in which case resid will have value zero
3864  resid=beta/normb;
3865 
3866  // If required, will document convergence history to screen or file (if
3867  // stream open)
3868  if (this->Doc_convergence_history)
3869  {
3870  // If an output file which is open isn't provided then output to screen
3871  if (!(this->Output_file_stream.is_open()))
3872  {
3873  // Output the residual value to the screen
3874  oomph_info << 0 << " " << resid << std::endl;
3875  }
3876  // Otherwise, output to file
3877  else
3878  {
3879  // Output the residual value to file
3880  this->Output_file_stream << 0 << " " << resid <<std::endl;
3881  }
3882  } // if (Doc_convergence_history)
3883 
3884  // If the GMRES algorithm converges immediately
3885  if (resid<=this->Tolerance)
3886  {
3887  // If time documentation is enabled
3888  if(this->Doc_time)
3889  {
3890  // Notify the user
3891  oomph_info << "FGMRES converged immediately. Normalised residual norm: "
3892  << resid << std::endl;
3893  }
3894 
3895  // Finish running the solver
3896  return;
3897  } // if (resid<=Tolerance)
3898 
3899  // Initialise a vector of orthogonal basis vectors
3900  Vector<Vector<DoubleVector> > block_v;
3901 
3902  // Resize the number of vectors needed
3903  block_v.resize(n_row+1);
3904 
3905  // Create a vector of DoubleVectors (stores the preconditioned vectors)
3906  Vector<Vector<DoubleVector> > block_z;
3907 
3908  // Resize the number of vectors needed
3909  block_z.resize(n_row+1);
3910 
3911  // Resize each Vector of DoubleVectors to store the real and imaginary
3912  // part of a given vector
3913  for (unsigned i=0;i<n_row+1;i++)
3914  {
3915  // Create space for two DoubleVector objects in each Vector
3916  block_v[i].resize(n_dof_types);
3917 
3918  // Create space for two DoubleVector objects in each Vector
3919  block_z[i].resize(n_dof_types);
3920  }
3921 
3922  // Initialise the upper hessenberg matrix. Since we are not using
3923  // distributed vectors here, the algebra is best done using entries
3924  // of the type std::complex<double>. NOTE: For implementation purposes
3925  // the upper Hessenberg matrix indices are swapped so the matrix is
3926  // effectively transposed
3927  Vector<Vector<std::complex<double> > > hessenberg(n_row+1);
3928 
3929  // Build the zeroth basis vector
3930  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
3931  {
3932  // Build the k-th part of the zeroth vector. Here k=0 and k=1 correspond
3933  // to the real and imaginary part of the zeroth vector, respectively
3934  block_v[0][dof_type].build(this->block_distribution_pt(dof_type),0.0);
3935  }
3936 
3937  // Loop over the real and imaginary parts of v
3938  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
3939  {
3940  // For fast access
3941  double* block_v0_pt=block_v[0][dof_type].values_pt();
3942 
3943  // For fast access
3944  const double* block_r_pt=block_r[dof_type].values_pt();
3945 
3946  // Set the zeroth basis vector v[0] to r/beta
3947  for (unsigned i=0;i<n_row;i++)
3948  {
3949  // Assign the i-th entry of the zeroth basis vector
3950  block_v0_pt[i]=block_r_pt[i]/beta;
3951  }
3952  } // for (unsigned k=0;k<n_dof_types;k++)
3953 
3954  // Set the first entry in the minimisation problem RHS vector (is meant
3955  // to the vector beta*e_1 initially, where e_1 is the unit vector with
3956  // one in its first entry)
3957  s[0]=beta;
3958 
3959  // Compute the next step of the iterative scheme
3960  for (unsigned j=0;j<this->Max_iter;j++)
3961  {
3962  // Resize the next column of the upper hessenberg matrix
3963  hessenberg[j].resize(j+2,std::complex<double>(0.0,0.0));
3964 
3965  // Calculate w_j=Jz_j where z_j=M^{-1}v_j (RHS prec.)
3966  {
3967  // Create a temporary vector
3969 
3970  // Create a temporary vector
3972 
3973  // Create a temporary vector
3975 
3976  // Create two DoubleVectors
3977  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
3978  {
3979  // Build the k-th part of the j-th preconditioning result vector
3980  block_z[j][dof_type].build(this->block_distribution_pt(dof_type),0.0);
3981  }
3982 
3983  // Copy the real and imaginary part of v[j] into one vector, vj
3984  this->return_block_vectors(block_v[j],vj);
3985 
3986  // Calculate z_j=M^{-1}v_j by saad p270
3987  this->preconditioner_pt()->preconditioner_solve(vj,zj);
3988 
3989  // Copy zj into z[j][0] and z[j][1]
3990  this->get_block_vectors(zj,block_z[j]);
3991 
3992  // Calculate w_j=J*(M^{-1}v_j). Note, we cannot use inbuilt complex matrix
3993  // algebra here as we're using distributed vectors
3994  this->complex_matrix_multiplication(this->Matrices_storage_pt,block_z[j],block_w);
3995  } // Calculate w=JM^{-1}v (RHS prec.)
3996 
3997  // For fast access
3998  double* block_w_r_pt=block_w[0].values_pt();
3999 
4000  // For fast access
4001  double* block_w_c_pt=block_w[1].values_pt();
4002 
4003  // Loop over all of the entries on and above the principal subdiagonal of
4004  // the Hessenberg matrix in the j-th column (remembering that
4005  // the indices of the upper Hessenberg matrix are swapped for the purpose
4006  // of implementation)
4007  for (unsigned i=0;i<j+1;i++)
4008  {
4009  // For fast access
4010  const double* block_vi_r_pt=block_v[i][0].values_pt();
4011 
4012  // For fast access
4013  const double* block_vi_c_pt=block_v[i][1].values_pt();
4014 
4015  // Loop over the entries of v and w
4016  for (unsigned k=0;k<n_row;k++)
4017  {
4018  // Store the appropriate entry in v as a complex value
4019  std::complex<double> complex_v(block_vi_r_pt[k],block_vi_c_pt[k]);
4020 
4021  // Store the appropriate entry in w as a complex value
4022  std::complex<double> complex_w(block_w_r_pt[k],block_w_c_pt[k]);
4023 
4024  // Update the value of H(i,j) noting we're computing a complex
4025  // inner product here (the ordering is very important here!)
4026  hessenberg[j][i]+=std::conj(complex_v)*complex_w;
4027  }
4028 
4029  // Orthonormalise w against all previous orthogonal vectors, v_i by
4030  // looping over its entries and updating them
4031  for (unsigned k=0;k<n_row;k++)
4032  {
4033  // Update the real part of the k-th entry of w
4034  block_w_r_pt[k]-=(hessenberg[j][i].real()*block_vi_r_pt[k]-
4035  hessenberg[j][i].imag()*block_vi_c_pt[k]);
4036 
4037  // Update the imaginary part of the k-th entry of w
4038  block_w_c_pt[k]-=(hessenberg[j][i].real()*block_vi_c_pt[k]+
4039  hessenberg[j][i].imag()*block_vi_r_pt[k]);
4040  }
4041  } // for (unsigned i=0;i<j+1;i++)
4042 
4043  // Calculate the norm of the real part of w
4044  norm_r=block_w[0].norm();
4045 
4046  // Calculate the norm of the imaginary part of w
4047  norm_c=block_w[1].norm();
4048 
4049  // Calculate the norm of the vector w using norm_r and norm_c and assign
4050  // its value to the appropriate entry in the Hessenberg matrix
4051  hessenberg[j][j+1]=sqrt(pow(norm_r,2.0)+pow(norm_c,2.0));
4052 
4053  // Build the (j+1)-th basis vector
4054  for (unsigned dof_type=0;dof_type<n_dof_types;dof_type++)
4055  {
4056  // Build the k-th part of the zeroth vector. Here k=0 and k=1 correspond
4057  // to the real and imaginary part of the zeroth vector, respectively
4058  block_v[j+1][dof_type].build(this->block_distribution_pt(dof_type),0.0);
4059  }
4060 
4061  // Check if the value of hessenberg[j][j+1] is zero. If it
4062  // isn't then we update the next entries in v
4063  if (hessenberg[j][j+1]!=0.0)
4064  {
4065  // For fast access
4066  double* block_v_r_pt=block_v[j+1][0].values_pt();
4067 
4068  // For fast access
4069  double* block_v_c_pt=block_v[j+1][1].values_pt();
4070 
4071  // For fast access
4072  const double* block_w_r_pt=block_w[0].values_pt();
4073 
4074  // For fast access
4075  const double* block_w_c_pt=block_w[1].values_pt();
4076 
4077  // Notice, the value of H(j,j+1), as calculated above, is clearly a real
4078  // number. As such, calculating the division
4079  // v_{j+1}=w_{j}/h_{j+1,j},
4080  // here is simple, i.e. we don't need to worry about cross terms in the
4081  // algebra. To avoid computing h_{j+1,j} several times we precompute it
4082  double h_subdiag_val=hessenberg[j][j+1].real();
4083 
4084  // Loop over the entries of the new orthogonal vector and set its values
4085  for(unsigned k=0;k<n_row;k++)
4086  {
4087  // The i-th entry of the real component is given by
4088  block_v_r_pt[k]=block_w_r_pt[k]/h_subdiag_val;
4089 
4090  // Similarly, the i-th entry of the imaginary component is given by
4091  block_v_c_pt[k]=block_w_c_pt[k]/h_subdiag_val;
4092  }
4093  }
4094  // Otherwise, we have to jump to the next part of the algorithm; if
4095  // the value of hessenberg[j][j+1] is zero then the norm of the latest
4096  // orthogonal vector is zero. This is only possible if the entries
4097  // in w are all zero. As a result, the Krylov space of A and r_0 has
4098  // been spanned by the previously calculated orthogonal vectors
4099  else
4100  {
4101  // Book says "Set m=j and jump to step 11" (p.172)...
4102  // Do something here!
4103  oomph_info << "Subdiagonal Hessenberg entry is zero. "
4104  << "Do something here..." << std::endl;
4105  } // if (hessenberg[j][j+1]!=0.0)
4106 
4107  // Loop over the entries in the Hessenberg matrix and calculate the
4108  // entries of the Givens rotation matrices
4109  for (unsigned k=0;k<j;k++)
4110  {
4111  // Apply the plane rotation to all of the previous entries in the
4112  // (j)-th column (remembering the indexing is reversed)
4113  this->apply_plane_rotation(hessenberg[j][k],hessenberg[j][k+1],cs[k],sn[k]);
4114  }
4115 
4116  // Now calculate the entries of the latest Givens rotation matrix
4117  this->generate_plane_rotation(hessenberg[j][j],hessenberg[j][j+1],cs[j],sn[j]);
4118 
4119  // Apply the plane rotation using the newly calculated entries
4120  this->apply_plane_rotation(hessenberg[j][j],hessenberg[j][j+1],cs[j],sn[j]);
4121 
4122  // Apply a plane rotation to the corresponding entry in the vector
4123  // s used in the minimisation problem, J(y)=min||s-R_m*y||
4124  this->apply_plane_rotation(s[j],s[j+1],cs[j],sn[j]);
4125 
4126  // Compute current residual using equation (6.42) in Saad Y, "Iterative
4127  // methods for sparse linear systems", p.177.]. Note, since s has complex
4128  // entries we have to use std::abs instead of std::fabs
4129  beta=std::abs(s[j+1]);
4130 
4131  // Compute the relative residual
4132  resid=beta/normb;
4133 
4134  // If required will document convergence history to screen or file (if
4135  // stream open)
4136  if (this->Doc_convergence_history)
4137  {
4138  // If an output file which is open isn't provided then output to screen
4139  if (!(this->Output_file_stream.is_open()))
4140  {
4141  // Output the residual value to the screen
4142  oomph_info << j+1 << " " << resid << std::endl;
4143  }
4144  // Otherwise, output to file
4145  else
4146  {
4147  // Output the residual value to file
4148  this->Output_file_stream << j+1 << " " << resid <<std::endl;
4149  }
4150  } // if (Doc_convergence_history)
4151 
4152  // If the required tolerance has been met
4153  if (resid<this->Tolerance)
4154  {
4155  // Store the number of iterations taken
4156  this->Iterations=j+1;
4157 
4158  // Update the result vector using the result, x=x_0+V_m*y (where V_m
4159  // is given by v here)
4160  update(j,hessenberg,s,block_z,block_solution);
4161 
4162  // Copy the vectors in block_solution to solution
4163  this->return_block_vectors(block_solution,solution);
4164 
4165  // If time documentation was enabled
4166  if(this->Doc_time)
4167  {
4168  // Output the current normalised residual norm
4169  oomph_info << "\nFGMRES converged (1). Normalised residual norm: "
4170  << resid << std::endl;
4171 
4172  // Output the number of iterations it took for convergence
4173  oomph_info << "Number of iterations to convergence: "
4174  << j+1 << "\n" << std::endl;
4175  }
4176 
4177  // Stop the timer
4178  double t_end=TimingHelpers::timer();
4179 
4180  // Calculate the time taken to calculate the solution
4181  this->Solution_time=t_end-t_start;
4182 
4183  // If time documentation was enabled
4184  if(this->Doc_time)
4185  {
4186  // Output the time taken to solve the problem using GMRES
4187  oomph_info << "Time for solve with FGMRES [sec]: "
4188  << this->Solution_time << std::endl;
4189  }
4190 
4191  // As we've met the tolerance for the solver and everything that should
4192  // be documented, has been, finish using the solver
4193  return;
4194  } // if (resid<Tolerance)
4195  } // for (unsigned j=0;j<Max_iter;j++)
4196 
4197  // Store the number of iterations taken
4198  this->Iterations=this->Max_iter;
4199 
4200  // Only update if we actually did something
4201  if (this->Max_iter>0)
4202  {
4203  // Update the result vector using the result, x=x_0+V_m*y (where V_m
4204  // is given by v here)
4205  update(this->Max_iter-1,hessenberg,s,block_z,block_solution);
4206 
4207  // Copy the vectors in block_solution to solution
4208  this->return_block_vectors(block_solution,solution);
4209  }
4210 
4211  // Compute the current residual
4212  beta=0.0;
4213 
4214  // Get access to the values pointer (real)
4215  norm_r=block_r[0].norm();
4216 
4217  // Get access to the values pointer (imaginary)
4218  norm_c=block_r[1].norm();
4219 
4220  // Calculate the full norm
4221  beta=sqrt(pow(norm_r,2.0)+pow(norm_c,2.0));
4222 
4223  // Calculate the relative residual
4224  resid=beta/normb;
4225 
4226  // If the relative residual lies within tolerance
4227  if (resid<this->Tolerance)
4228  {
4229  // If time documentation is enabled
4230  if(this->Doc_time)
4231  {
4232  // Notify the user
4233  oomph_info << "\nFGMRES converged (2). Normalised residual norm: "
4234  << resid
4235  << "\nNumber of iterations to convergence: "
4236  << this->Iterations << "\n" << std::endl;
4237  }
4238 
4239  // End the timer
4240  double t_end=TimingHelpers::timer();
4241 
4242  // Calculate the time taken for the solver
4243  this->Solution_time=t_end-t_start;
4244 
4245  // If time documentation is enabled
4246  if(this->Doc_time)
4247  {
4248  oomph_info << "Time for solve with FGMRES [sec]: "
4249  << this->Solution_time << std::endl;
4250  }
4251  return;
4252  }
4253 
4254  // Otherwise GMRES failed convergence
4255  oomph_info << "\nFGMRES did not converge to required tolerance! "
4256  << "\nReturning with normalised residual norm: " << resid
4257  << "\nafter " << this->Max_iter << " iterations.\n" << std::endl;
4258 
4259  // Throw an error if requested
4260  if(this->Throw_error_after_max_iter)
4261  {
4262  std::string err="Solver failed to converge and you requested an error";
4263  err+=" on convergence failures.";
4264  throw OomphLibError(err,OOMPH_EXCEPTION_LOCATION,
4265  OOMPH_CURRENT_FUNCTION);
4266  }
4267 
4268  // Finish using the solver
4269  return;
4270  } // End of solve_helper
4271 } // End of namespace oomph
4272 
4273 #endif
bool Throw_error_after_max_iter
Should we throw an error instead of just returning when we hit the max iterations?
virtual void solve(Problem *const &problem_pt, DoubleVector &result)=0
Solver: Takes pointer to problem and returns the results vector which contains the solution of the li...
void broken_copy(const std::string &class_name)
Issue error message and terminate execution.
~ComplexDampedJacobi()
Empty destructor.
void solve(Problem *const &problem_pt, DoubleVector &result)
Solver: Takes pointer to problem and returns the results vector which contains the solution of the li...
void apply_plane_rotation(std::complex< double > &dx, std::complex< double > &dy, std::complex< double > &cs, std::complex< double > &sn)
Helper function: Apply plane rotation. This is done using the update: Taken from: Saad Y...
void solve(Problem *const &problem_pt, DoubleVector &result)
Solver: Takes pointer to problem and returns the results vector which contains the solution of the li...
void solve(Problem *const &problem_pt, DoubleVector &result)
Use damped Jacobi iteration as an IterativeLinearSolver: This obtains the Jacobian matrix J and the r...
Preconditioner *& preconditioner_pt()
Access function to preconditioner.
void solve_helper(DoubleMatrixBase *const &matrix_pt, const DoubleVector &rhs, DoubleVector &solution)
General interface to solve function.
void complex_matrix_multiplication(Vector< CRDoubleMatrix *> const matrices_pt, const Vector< DoubleVector > &x, Vector< DoubleVector > &soln)
Helper function to calculate a complex matrix-vector product. Assumes the matrix has been provided as...
void set_preconditioner_RHS()
Enable right preconditioning.
virtual void preconditioner_solve(const DoubleVector &r, DoubleVector &z)=0
Apply the preconditioner. Pure virtual generic interface function. This method should apply the preco...
double & tolerance()
Access to convergence tolerance.
The GMRES method for the Helmholtz solver.
unsigned iterations() const
Number of iterations taken.
void redistribute(const LinearAlgebraDistribution *const &dist_pt)
The contents of the vector are redistributed to match the new distribution. In a non-MPI rebuild this...
The GMRES method rewritten for complex matrices.
double Tolerance
Convergence tolerance.
CRDoubleMatrix * Matrix_imag_pt
Pointer to the real part of the system matrix.
bool Matrix_can_be_deleted
Boolean flag to indicate if the matrices pointed to by Matrix_real_pt and Matrix_imag_pt can be delet...
cstr elem_len * i
Definition: cfortran.h:607
void operator=(const ComplexGMRES &)
Broken assignment operator.
void operator=(const ComplexDampedJacobi &)
Broken assignment operator.
void clean_up_memory()
Cleanup data that&#39;s stored for resolve (if any has been stored)
unsigned Iterations
Number of iterations taken.
unsigned Max_iter
Maximum number of iterations.
HelmholtzSmoother()
Empty constructor.
const double Pi
50 digits from maple
Vector< double > Matrix_diagonal_real
Vector containing the diagonal entries of A_r (real(A))
bool Enable_resolve
Boolean that indicates whether the matrix (or its factors, in the case of direct solver) should be st...
Definition: linear_solver.h:80
The Problem class.
Definition: problem.h:152
void solve(Problem *const &problem_pt, DoubleVector &result)
Solver: Takes pointer to problem and returns the results vector which contains the solution of the li...
unsigned Iterations
Number of iterations taken.
void update(const unsigned &k, const Vector< Vector< std::complex< double > > > &hessenberg, const Vector< std::complex< double > > &s, const Vector< Vector< DoubleVector > > &z_m, Vector< DoubleVector > &x)
Helper function to update the result vector.
bool Matrix_can_be_deleted
Boolean flag to indicate if the real and imaginary system matrices can be deleted.
static OomphCommunicator * communicator_pt()
access to the global oomph-lib communicator
Mesh *& mesh_pt()
Return a pointer to the global mesh.
Definition: problem.h:1264
OomphInfo oomph_info
bool distributed() const
access function to the distributed - indicates whether the distribution is serial or distributed ...
std::ofstream Output_file_stream
Output file stream for convergence history.
double & omega()
Get access to the value of Omega (lvalue)
virtual ~HelmholtzFGMRESMG()
Destructor (cleanup storage)
ComplexDampedJacobi(const ComplexDampedJacobi &)
Broken copy constructor.
OomphCommunicator * communicator_pt()
access function to the oomph-lib communicator
Definition: problem.h:1221
~ComplexGMRES()
Empty destructor.
void complex_smoother_solve(const Vector< DoubleVector > &rhs, Vector< DoubleVector > &solution)
The smoother_solve function performs fixed number of iterations on the system A*result=rhs. The number of (smoothing) iterations is the same as the max. number of iterations in the underlying IterativeLinearSolver class.
void complex_matrix_multiplication(Vector< CRDoubleMatrix *> matrices_pt, const Vector< DoubleVector > &x, Vector< DoubleVector > &soln)
Helper function to calculate a complex matrix-vector product. Assumes the matrix has been provided as...
bool built() const
bool distributed() const
distribution is serial or distributed
virtual void complex_smoother_solve(const Vector< DoubleVector > &rhs, Vector< DoubleVector > &result)=0
The smoother_solve function performs fixed number of iterations on the system A*result=rhs. The number of (smoothing) iterations is the same as the max. number of iterations in the underlying IterativeLinearSolver class.
Describes the distribution of a distributable linear algebra type object. Typically this is a contain...
void build(const DoubleVector &old_vector)
Just copys the argument DoubleVector.
void complex_smoother_setup(Vector< CRDoubleMatrix *> helmholtz_matrix_pt)
Setup: Pass pointer to the matrix and store in cast form.
void disable_resolve()
Overload disable resolve so that it cleans up memory too.
HelmholtzFGMRESMG(const HelmholtzFGMRESMG &)
Broken copy constructor.
Vector< CRDoubleMatrix * > Matrices_storage_pt
Vector of pointers to the real and imaginary part of the system matrix.
void operator=(const HelmholtzFGMRESMG &)
Broken assignment operator.
bool Doc_time
Boolean flag that indicates whether the time taken.
Definition: linear_solver.h:84
The FGMRES method, i.e. the flexible variant of the GMRES method which allows for nonconstant precond...
void solve(DoubleMatrixBase *const &matrix_pt, const Vector< double > &rhs, Vector< double > &result)
Linear-algebra-type solver: Takes pointer to a matrix and rhs vector and returns the solution of the ...
double Jacobian_setup_time
Jacobian setup time.
bool Matrix_can_be_deleted
Boolean flag to indicate if the matrix pointed to be Matrix_pt can be deleted.
bool Use_as_smoother
When a derived class object is being used as a smoother in the MG algorithm the residual norm does no...
void initialise(const double &v)
initialise the whole vector with value v
void complex_solve_helper(const Vector< DoubleVector > &rhs, Vector< DoubleVector > &solution)
This is where the actual work is done.
void preconditioner_solve(const DoubleVector &r, DoubleVector &z)
Implementation of the pure virtual base class function. The function has been broken because this is ...
static char t char * s
Definition: cfortran.h:572
virtual ~HelmholtzGMRESMG()
Destructor (cleanup storage)
unsigned Iterations
Number of iterations taken.
void set_preconditioner_LHS()
Set left preconditioning (the default)
void generate_plane_rotation(std::complex< double > &dx, std::complex< double > &dy, std::complex< double > &cs, std::complex< double > &sn)
Helper function: Generate a plane rotation. This is done by finding the value of (i...
Vector< double > Matrix_diagonal_imag
Vector containing the diagonal entries of A_c (imag(A))
void clean_up_memory()
Cleanup data that&#39;s stored for resolve (if any has been stored)
HelmholtzGMRESMG()
Constructor.
bool Resolving
Boolean flag to indicate if the solve is done in re-solve mode, bypassing setup of matrix and precond...
bool Preconditioner_LHS
boolean indicating use of left hand preconditioning (if true) or right hand preconditioning (if false...
Vector< CRDoubleMatrix * > Matrices_storage_pt
Vector of pointers to the real and imaginary part of the system matrix.
double timer()
returns the time in seconds after some point in past
virtual ~HelmholtzSmoother()
Virtual empty destructor.
void initialise(const _Tp &__value)
Iterate over all values and set to the desired value.
Definition: Vector.h:171
void setup(DoubleMatrixBase *matrix_pt)
Setup the preconditioner: store the matrix pointer and the communicator pointer then call preconditio...
void complex_smoother_setup(Vector< CRDoubleMatrix *> helmholtz_matrix_pt)
Setup: Pass pointer to the matrix and store in cast form.
CRDoubleMatrix * Matrix_real_pt
Pointer to the real part of the system matrix.
void check_validity_of_solve_helper_inputs(CRDoubleMatrix *const &real_matrix_pt, CRDoubleMatrix *const &imag_matrix_pt, const Vector< DoubleVector > &rhs, Vector< DoubleVector > &solution, const double &n_dof)
Self-test to check that all the dimensions of the inputs to solve helper are consistent and everythin...
double Solution_time
linear solver solution time
unsigned long ndof() const
Return the number of dofs.
Definition: problem.h:1574
virtual void disable_resolve()
Disable resolve (i.e. store matrix and/or LU decomposition, say) This function simply resets an inter...
bool Resolving
Boolean flag to indicate if the solve is done in re-solve mode, bypassing setup of matrix and precond...
virtual void get_jacobian(DoubleVector &residuals, DenseDoubleMatrix &jacobian)
Return the fully-assembled Jacobian and residuals for the problem Interface for the case when the Jac...
Definition: problem.cc:3852
virtual void clean_up_memory()
Empty virtual function that can be overloaded in specific linear solvers to clean up any memory that ...
void calculate_omega(const double &k, const double &h)
Function to calculate the value of Omega by passing in the value of k and h [see Elman et al...
double Omega
Damping factor.
void broken_assign(const std::string &class_name)
Issue error message and terminate execution.
unsigned nrow() const
access function to the number of global rows.
bool Doc_convergence_history
Flag indicating if the convergence history is to be documented.
std::string string(const unsigned &i)
Return the i-th string or "" if the relevant string hasn&#39;t been defined.
HelmholtzFGMRESMG()
Constructor (empty)
void set_preconditioner_LHS()
Overloaded function to let the user know that left preconditioning is not possible with FGMRES...
void build_distribution(const LinearAlgebraDistribution *const dist_pt)
setup the distribution of this distributable linear algebra object
void apply_plane_rotation(std::complex< double > &dx, std::complex< double > &dy, std::complex< double > &cs, std::complex< double > &sn)
Helper function: Apply plane rotation. This is done using the update: Taken from: Saad Y...
void update(const unsigned &k, const Vector< Vector< std::complex< double > > > &hessenberg, const Vector< std::complex< double > > &s, const Vector< Vector< DoubleVector > > &v, Vector< DoubleVector > &x)
Helper function to update the result vector.
void update(const unsigned &k, const Vector< Vector< std::complex< double > > > &hessenberg, const Vector< std::complex< double > > &s, const Vector< Vector< DoubleVector > > &v, Vector< DoubleVector > &x)
Helper function to update the result vector.
void disable_resolve()
Overload disable resolve so that it cleans up memory too.
void solve_helper(DoubleMatrixBase *const &matrix_pt, const DoubleVector &rhs, DoubleVector &solution)
General interface to solve function.
unsigned iterations() const
Number of iterations taken.
A vector in the mathematical sense, initially developed for linear algebra type applications. If MPI then this vector can be distributed - its distribution is described by the LinearAlgebraDistribution object at Distribution_pt. Data is stored in a C-style pointer vector (double*)
Definition: double_vector.h:61
double norm() const
compute the 2 norm of this vector
void resolve(const DoubleVector &rhs, DoubleVector &result)
Re-solve the system defined by the last assembled Jacobian and the rhs vector specified here...
ComplexGMRES(const ComplexGMRES &)
Broken copy constructor.
Vector< double > Matrix_diagonal_inv_sq
Vector whose j&#39;th entry is given by 1/(A_r(j,j)^2 + A_c(j,j)^2)
bool Setup_preconditioner_before_solve
indicates whether the preconditioner should be setup before solve. Default = true; ...
void operator=(const HelmholtzGMRESMG &)
Broken assignment operator.
void complex_solve_helper(const Vector< DoubleVector > &rhs, Vector< DoubleVector > &solution)
This is where the actual work is done.
void setup()
Implementation of the pure virtual base class function. This accompanies the preconditioner_solve fun...
void solve(DoubleMatrixBase *const &matrix_pt, const DoubleVector &rhs, DoubleVector &solution)
Linear-algebra-type solver: Takes pointer to a matrix and rhs vector and returns the solution of the ...
Abstract base class for matrices of doubles – adds abstract interfaces for solving, LU decomposition and multiplication by vectors.
Definition: matrices.h:275
A class for compressed row matrices. This is a distributable object.
Definition: matrices.h:872
ComplexGMRES()
Constructor.
void generate_plane_rotation(std::complex< double > &dx, std::complex< double > &dy, std::complex< double > &cs, std::complex< double > &sn)
Helper function: Generate a plane rotation. This is done by finding the value of (i...
LinearAlgebraDistribution * distribution_pt() const
access to the LinearAlgebraDistribution
double Preconditioner_setup_time
Preconditioner setup time.
void solve(DoubleMatrixBase *const &matrix_pt, const Vector< double > &rhs, Vector< double > &result)
Linear-algebra-type solver: Takes pointer to a matrix and rhs vector and returns the solution of the ...
ComplexDampedJacobi(const double &omega=0.5)
Constructor (empty)
virtual void complex_smoother_setup(Vector< CRDoubleMatrix *> matrix_pt)=0
Setup the smoother for the matrix specified by the pointer.
void build(const LinearAlgebraDistribution *distribution_pt, const unsigned &ncol, const Vector< double > &value, const Vector< int > &column_index, const Vector< int > &row_start)
build method: vector of values, vector of column indices, vector of row starts and number of rows and...
Definition: matrices.cc:1692
Base class for all linear iterative solvers. This merely defines standard interfaces for linear itera...
unsigned iterations() const
Number of iterations taken.
void complex_smoother_solve(const Vector< DoubleVector > &rhs, Vector< DoubleVector > &solution)
The smoother_solve function performs fixed number of iterations on the system A*result=rhs. The number of (smoothing) iterations is the same as the max. number of iterations in the underlying IterativeLinearSolver class.
void clean_up_memory()
Cleanup data that&#39;s stored for resolve (if any has been stored)
HelmholtzGMRESMG(const HelmholtzGMRESMG &)
Broken copy constructor.