eigen_solver.cc
Go to the documentation of this file.
1 // LIC// ====================================================================
2 // LIC// This file forms part of oomph-lib, the object-oriented,
3 // LIC// multi-physics finite-element library, available
4 // LIC// at http://www.oomph-lib.org.
5 // LIC//
6 // LIC// Copyright (C) 2006-2023 Matthias Heil and Andrew Hazel
7 // LIC//
8 // LIC// This library is free software; you can redistribute it and/or
9 // LIC// modify it under the terms of the GNU Lesser General Public
10 // LIC// License as published by the Free Software Foundation; either
11 // LIC// version 2.1 of the License, or (at your option) any later version.
12 // LIC//
13 // LIC// This library is distributed in the hope that it will be useful,
14 // LIC// but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // LIC// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // LIC// Lesser General Public License for more details.
17 // LIC//
18 // LIC// You should have received a copy of the GNU Lesser General Public
19 // LIC// License along with this library; if not, write to the Free Software
20 // LIC// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 // LIC// 02110-1301 USA.
22 // LIC//
23 // LIC// The authors may be contacted at oomph-lib@maths.man.ac.uk.
24 // LIC//
25 // LIC//====================================================================
26 // Non-inline functions for the eigensolvers
27 
28 #ifdef OOMPH_HAS_MPI
29 #include "mpi.h"
30 #endif
31 
32 // Include cfortran.h and the header for the FORTRAN ARPACK routines
33 #include "cfortran.h"
34 #include "arpack.h"
35 #include "lapack_qz.h"
36 
37 // Oomph-lib headers
38 #include "eigen_solver.h"
39 #include "linear_solver.h"
40 #include "problem.h"
41 
42 
43 namespace oomph
44 {
45  //===============================================================
46  /// Constructor, set default values and set the initial
47  /// linear solver to be superlu
48  //===============================================================
50  : EigenSolver(),
51  Spectrum(1),
52  NArnoldi(30),
53  Small(true),
54  Compute_eigenvectors(true)
55  {
57  }
58 
59  //===============================================================
60  /// Destructor, delete the default linear solver
61  //===============================================================
63  {
65  }
66 
67 
68  //==========================================================================
69  /// Use ARPACK to solve an eigen problem that is assembled by elements in
70  /// a mesh in a Problem object.
71  //==========================================================================
73  Problem* const& problem_pt,
74  const int& n_eval,
75  Vector<std::complex<double>>& eigenvalue,
76  Vector<DoubleVector>& eigenvector,
77  const bool& do_adjoint_problem)
78  {
79  if (do_adjoint_problem)
80  {
81  throw OomphLibError("Solving an adjoint eigenproblem is not currently "
82  "implemented for ARPACK.",
83  OOMPH_CURRENT_FUNCTION,
84  OOMPH_EXCEPTION_LOCATION);
85  }
86 
87  bool Verbose = false;
88 
89  // Control parameters
90  int ido = 0; // Reverse communication flag
91  char bmat[2]; // Specifies the type of matrix on the RHS
92  // Must be 'I' for standard eigenvalue problem
93  // 'G' for generalised eigenvalue problem
94  int n; // Dimension of the problem
95  char which[3]; // Set which eigenvalues are required.
96  int nev; // Number of eigenvalues desired
97  double tol = 0.0; // Stopping criteria
98  int ncv; // Number of columns of the matrix V (Dimension of Arnoldi
99  // subspace)
100  int iparam[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Control parameters
101  // Pointers to starting locations in the workspace vectors
102  int ipntr[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
103  int info =
104  0; // Setting to zero gives random initial guess for vector to start
105  // Arnoldi iteration
106 
107  // Set up the sizes of the matrix
108  n = problem_pt->ndof(); // Total size of matrix
109  nev = n_eval; // Number of desired eigenvalues
110  ncv = NArnoldi < n ? NArnoldi : n; // Number of Arnoldi vectors allowed
111  // Maximum possible value is max
112  // dimension of matrix
113 
114  // If we don't have enough Arnoldi vectors to compute the desired number
115  // of eigenvalues then complain
116  if (nev > ncv)
117  {
118  std::ostringstream warning_stream;
119  warning_stream << "Number of requested eigenvalues " << nev << "\n"
120  << "is greater than the number of Arnoldi vectors:" << ncv
121  << "\n";
122  // Increase the number of Arnoldi vectors
123  ncv = nev + 10;
124  if (ncv > n)
125  {
126  ncv = n;
127  }
128 
129  warning_stream << "Increasing number of Arnoldi vectors to " << ncv
130  << "\n but you may want to increase further using\n"
131  << "ARPACK::narnoldi()\n"
132  << "which will also get rid of this warning.\n";
133 
135  warning_stream.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
136  }
137 
138  // Allocate some workspace
139  int lworkl = 3 * ncv * ncv + 6 * ncv;
140 
141  // Array that holds the final set of Arnoldi basis vectors
142  double** v = new double*;
143  *v = new double[ncv * n];
144  // Leading dimension of v (n)
145  int ldv = n;
146 
147  // Residual vector
148  double* resid = new double[n];
149  // Workspace vector
150  double* workd = new double[3 * n];
151  // Workspace vector
152  double* workl = new double[lworkl];
153 
154  bmat[0] = 'G';
155  // If we require eigenvalues to the left of the shift
156  if (Small)
157  {
158  which[0] = 'S';
159  }
160  // Otherwise we require eigenvalues to the right of the shift
161  else
162  {
163  which[0] = 'L';
164  }
165 
166  // Which part of the eigenvalue interests us
167  switch (Spectrum)
168  {
169  // Imaginary part
170  case -1:
171  which[1] = 'I';
172  break;
173 
174  // Magnitude
175  case 0:
176  which[1] = 'M';
177  break;
178 
179  // Real part
180  case 1:
181  which[1] = 'R';
182  break;
183 
184  default:
185  std::ostringstream error_stream;
186  error_stream
187  << "Spectrum is set to an invalid value. It must be 0, 1 or -1\n"
188  << "Not " << Spectrum << std::endl;
189 
190  throw OomphLibError(
191  error_stream.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
192  }
193 
194  // %---------------------------------------------------%
195  // | This program uses exact shifts with respect to |
196  // | the current Hessenberg matrix (IPARAM(1) = 1). |
197  // | IPARAM(3) specifies the maximum number of Arnoldi |
198  // | iterations allowed. Mode 3 of DNAUPD is used |
199  // | (IPARAM(7) = 3). All these options can be changed |
200  // | by the user. For details see the documentation in |
201  // | DNAUPD. |
202  // %---------------------------------------------------%
203 
204  int ishifts = 1;
205  int maxitr = 300;
206  int mode = 3; // M is symetric and semi-definite
207 
208  iparam[0] = ishifts; // Exact shifts wrt Hessenberg matrix H
209  iparam[2] = maxitr; // Maximum number of allowed iterations
210  iparam[3] = 1; // Bloacksize to be used in the recurrence
211  iparam[6] = mode; // Mode is shift and invert in real arithmetic
212 
213  // Real and imaginary parts of the shift
214  double sigmar = Sigma_real, sigmai = 0.0;
215 
216  // TEMPORARY
217  // only use non distributed matrice and vectors
218  LinearAlgebraDistribution dist(problem_pt->communicator_pt(), n, false);
219  this->build_distribution(dist);
220 
221  // Before we get into the Arnoldi loop solve the shifted matrix problem
222  // Allocated Row compressed matrices for the mass matrix and shifted main
223  // matrix
224  CRDoubleMatrix M(this->distribution_pt()), AsigmaM(this->distribution_pt());
225 
226  // Assemble the matrices
227  problem_pt->get_eigenproblem_matrices(M, AsigmaM, sigmar);
228 
229  // Allocate storage for the vectors to be used in matrix vector products
230  DoubleVector rhs(this->distribution_pt(), 0.0);
231  DoubleVector x(this->distribution_pt(), 0.0);
232 
233 
234  bool LOOP_FLAG = true;
235  bool First = true;
236 
237  // Enable resolves for the linear solver
239 
240  // Do not report the time taken
242 
243  do
244  {
245  // %---------------------------------------------%
246  // | Repeatedly call the routine DNAUPD and take |
247  // | actions indicated by parameter IDO until |
248  // | either convergence is indicated or maxitr |
249  // | has been exceeded. |
250  // %---------------------------------------------%
251  //
252 
253  DNAUPD(ido,
254  bmat,
255  n,
256  which,
257  nev,
258  tol,
259  resid,
260  ncv,
261  v,
262  ldv,
263  iparam,
264  ipntr,
265  workd,
266  workl,
267  lworkl,
268  info);
269 
270  if (ido == -1)
271  {
272  // %-------------------------------------------%
273  // | Perform matrix vector multiplication |
274  // | y <--- OP*x |
275  // | The user should supply his/her own |
276  // | matrix vector multiplication routine here |
277  // | that takes workd(ipntr(1)) as the input |
278  // | vector, and return the matrix vector |
279  // | product to workd(ipntr(2)). |
280  // %-------------------------------------------%
281  //
282 
283  // Firstly multiply by mx
284  for (int i = 0; i < n; i++)
285  {
286  x[i] = workd[ipntr[0] - 1 + i];
287  }
288  M.multiply(x, rhs);
289 
290  // Now solve the system
291  DoubleVector temp(rhs);
292  if (First)
293  {
294  Linear_solver_pt->solve(&AsigmaM, temp, rhs);
295  First = false;
296  }
297  else
298  {
299  Linear_solver_pt->resolve(temp, rhs);
300  }
301  temp.clear();
302  // AsigmaM.lubksub(rhs);
303  // Put the solution into the workspace
304  for (int i = 0; i < n; i++)
305  {
306  workd[ipntr[1] - 1 + i] = rhs[i];
307  }
308 
309  continue;
310  }
311  else if (ido == 1)
312  {
313  // Already done multiplication by Mx
314  // Need to load the rhs vector
315  for (int i = 0; i < n; i++)
316  {
317  rhs[i] = workd[ipntr[2] - 1 + i];
318  }
319  // Now solve the system
320  // AsigmaM.lubksub(rhs);
321  DoubleVector temp(rhs);
322  if (First)
323  {
324  Linear_solver_pt->solve(&AsigmaM, temp, rhs);
325  First = false;
326  }
327  else
328  {
329  Linear_solver_pt->resolve(temp, rhs);
330  }
331  // Put the solution into the workspace
332  for (int i = 0; i < n; i++)
333  {
334  workd[ipntr[1] - 1 + i] = rhs[i];
335  }
336  continue;
337  }
338  else if (ido == 2)
339  {
340  // Need to multiply by mass matrix
341  // Vector<double> x(n);
342  for (int i = 0; i < n; i++)
343  {
344  x[i] = workd[ipntr[0] - 1 + i];
345  }
346  M.multiply(x, rhs);
347 
348  for (int i = 0; i < n; i++)
349  {
350  workd[ipntr[1] - 1 + i] = rhs[i];
351  }
352  continue;
353  }
354 
355  if (info < 0)
356  {
357  oomph_info << "ERROR" << std::endl;
358  oomph_info << "Error with _naupd, info = '" << info << std::endl;
359  if (info == -7)
360  {
361  oomph_info << "Not enough memory " << std::endl;
362  }
363  }
364 
365  // %-------------------------------------------%
366  // | No fatal errors occurred. |
367  // | Post-Process using DNEUPD. |
368  // | |
369  // | Computed eigenvalues may be extracted. |
370  // | |
371  // | Eigenvectors may also be computed now if |
372  // | desired. (indicated by rvec = .true.) |
373  // %-------------------------------------------%
374  //
375  LOOP_FLAG = false;
376  } while (LOOP_FLAG);
377 
378  // Report any problems
379  if (info == 1)
380  {
381  oomph_info << "Maximum number of iterations reached." << std::endl;
382  }
383  else if (info == 3)
384  {
385  oomph_info << "No shifts could be applied during implicit Arnoldi "
386  << "update, try increasing NCV. " << std::endl;
387  }
388 
389  // Disable resolves for the linear solver
391 
392  // Compute Ritz or Schur vectors, if desired
393  int rvec = Compute_eigenvectors;
394  // Specify the form of the basis computed
395  char howmany[2];
396  howmany[0] = 'A';
397  // Find out the number of converged eigenvalues
398  int nconv = iparam[4];
399 
400  // Note that there is an error (feature) in ARPACK that
401  // means in certain cases, if we request eigenvectors,
402  // consequent Schur factorization of the matrix spanned
403  // by the Arnoldi vectors leads to more converged eigenvalues
404  // than reported by DNAPUD. This is a pain because it
405  // causes a segmentation fault and in every case that I've found
406  // the eigenvalues/vectors are not those desired.
407  // At the moment, I'm just going to leave it, but I note
408  // the problem here to remind myself about it.
409 
410  // Array to select which Ritz vectors are computed
411  int select[ncv];
412  // Array that holds the real part of the eigenvalues
413  double* eval_real = new double[nconv + 1];
414  // Array that holds the imaginary part of the eigenvalues
415  double* eval_imag = new double[nconv + 1];
416 
417  // Workspace
418  double* workev = new double[3 * ncv];
419  // Array to hold the eigenvectors
420  double** z = new double*;
421  *z = new double[(nconv + 1) * n];
422 
423  // Error flag
424  int ierr;
425 
426  DNEUPD(rvec,
427  howmany,
428  select,
429  eval_real,
430  eval_imag,
431  z,
432  ldv,
433  sigmar,
434  sigmai,
435  workev,
436  bmat,
437  n,
438  which,
439  nev,
440  tol,
441  resid,
442  ncv,
443  v,
444  ldv,
445  iparam,
446  ipntr,
447  workd,
448  workl,
449  lworkl,
450  ierr);
451  //
452  // %-----------------------------------------------%
453  // | The real part of the eigenvalue is returned |
454  // | in the first column of the two dimensional |
455  // | array D, and the imaginary part is returned |
456  // | in the second column of D. The corresponding |
457  // | eigenvectors are returned in the first NEV |
458  // | columns of the two dimensional array V if |
459  // | requested. Otherwise, an orthogonal basis |
460  // | for the invariant subspace corresponding to |
461  // | the eigenvalues in D is returned in V. |
462  // %-----------------------------------------------%
463 
464  if (ierr != 0)
465  {
466  oomph_info << "Error with _neupd, info = " << ierr << std::endl;
467  }
468  else
469  // Print the output anyway
470  {
471  // Resize the eigenvalue output vector
472  eigenvalue.resize(nconv);
473  for (int j = 0; j < nconv; j++)
474  {
475  // Turn the output from ARPACK into a complex number
476  std::complex<double> eigen(eval_real[j], eval_imag[j]);
477  // Add the eigenvalues to the output vector
478  eigenvalue[j] = eigen;
479  }
480 
482  {
483  // Load the eigenvectors
484  eigenvector.resize(nconv);
485  for (int j = 0; j < nconv; j++)
486  {
487  eigenvector[j].build(this->distribution_pt(), 0.0);
488  for (int i = 0; i < n; i++)
489  {
490  eigenvector[j][i] = z[0][j * n + i];
491  }
492  }
493  }
494  else
495  {
496  eigenvector.resize(0);
497  }
498 
499  // Report the information
500  if (Verbose)
501  {
502  oomph_info << " ARPACK " << std::endl;
503  oomph_info << " ====== " << std::endl;
504  oomph_info << " Size of the matrix is " << n << std::endl;
505  oomph_info << " The number of Ritz values requested is " << nev
506  << std::endl;
507  oomph_info << " The number of Arnoldi vectors generated (NCV) is "
508  << ncv << std::endl;
509  oomph_info << " What portion of the spectrum: " << which << std::endl;
510  oomph_info << " The number of converged Ritz values is " << nconv
511  << std::endl;
512  oomph_info
513  << " The number of Implicit Arnoldi update iterations taken is "
514  << iparam[2] << std::endl;
515  oomph_info << " The number of OP*x is " << iparam[8] << std::endl;
516  oomph_info << " The convergence criterion is " << tol << std::endl;
517  }
518  }
519 
520  // Clean up the allocated memory
521  delete[] * z;
522  *z = 0;
523  delete z;
524  z = 0;
525  delete[] workev;
526  workev = 0;
527  delete[] eval_imag;
528  eval_imag = 0;
529  delete[] eval_real;
530  eval_real = 0;
531 
532  // Clean up the memory
533  delete[] workl;
534  workl = 0;
535  delete[] workd;
536  workd = 0;
537  delete[] resid;
538  resid = 0;
539  delete[] * v;
540  *v = 0;
541  delete v;
542  v = 0;
543  }
544 
545 
546  ////////////////////////////////////////////////////////////////////////////
547  ////////////////////////////////////////////////////////////////////////////
548  ////////////////////////////////////////////////////////////////////////////
549 
550 
551  //==========================================================================
552  /// Use LAPACK QZ to solve the real eigenproblem that is assembled by elements
553  /// in a mesh in a Problem object. Note that the assembled matrices include
554  /// the shift and are real. The eigenvalues and eigenvectors are, in general,
555  /// complex.
556  /// Eigenvalues may be infinite and are therefore returned as
557  /// \f$ \lambda_i = \alpha_i / \beta_i \f$ where \f$ \alpha_i \f$ is complex
558  /// while \f$ \beta_i \f$ is real. The actual eigenvalues may then be
559  /// computed by doing the division, checking for zero betas to avoid NaNs.
560  /// This is actually a helper function that stores re & imag parts of
561  /// eigenvectors in a collection of real vectors; they
562  /// are disentangled in the alternative version of this function that returns
563  /// Vectors of complex vectors.
564  /// At least n_eval eigenvalues are computed.
565  //==========================================================================
567  Problem* const& problem_pt,
568  const int& n_eval,
569  Vector<std::complex<double>>& alpha_eval,
570  Vector<double>& beta_eval,
571  Vector<DoubleVector>& eigenvector_aux)
572  {
573  // Some character identifiers for use in the LAPACK routine
574  // Do not calculate the left eigenvectors
575  char no_eigvecs[2] = "N";
576 
577  // Do caculate the eigenvectors
578  char eigvecs[2] = "V";
579 
580  // Get the dimension of the matrix
581  int n = problem_pt->ndof(); // Total size of matrix
582 
583  // Use padding?
584  bool use_padding = false;
585 
586  // If the dimension of the matrix is even, then pad the arrays to
587  // make the size odd. This somehow sorts out a strange run-time behaviour
588  // identified by Rich Hewitt.
589  // Actual size of matrix that will be allocated
590  int padded_n = n;
591  if (n % 2 == 0)
592  {
593  use_padding = true;
594  padded_n += 1;
595  }
596 
597  // Storage for the matrices in the packed form required by the LAPACK
598  // routine
599  double* M = new double[padded_n * padded_n];
600  double* A = new double[padded_n * padded_n];
601 
602  // TEMPORARY
603  // only use non-distributed matrices and vectors
604  LinearAlgebraDistribution dist(problem_pt->communicator_pt(), n, false);
605  this->build_distribution(dist);
606 
607  // Enclose in a separate scope so that memory is cleaned after assembly
608  {
609  // Allocated Row compressed matrices for the mass matrix and shifted main
610  // matrix
611  CRDoubleMatrix temp_M(this->distribution_pt()),
612  temp_AsigmaM(this->distribution_pt());
613 
614  // Assemble the matrices; pass the shift into the assembly
615  problem_pt->get_eigenproblem_matrices(temp_M, temp_AsigmaM, Sigma_real);
616 
617  // Now convert these matrices into the appropriate packed form
618  unsigned index = 0;
619  for (int i = 0; i < n; ++i)
620  {
621  for (int j = 0; j < n; ++j)
622  {
623  M[index] = temp_M(j, i);
624  A[index] = temp_AsigmaM(j, i);
625  ++index;
626  }
627  // If necessary pad the columns with zeros
628  if (use_padding)
629  {
630  M[index] = 0.0;
631  A[index] = 0.0;
632  ++index;
633  }
634  }
635  // No need to pad the final row because it is never accessed by the
636  // routine.
637  }
638 
639  // Temporary eigenvalue storage
640  double* alpha_r = new double[n];
641  double* alpha_i = new double[n];
642  double* beta = new double[n];
643  // Temporary eigenvector storage
644  double* vec_left = new double[1];
645  const int leading_dimension_vec_left = 1;
646  double* vec_right = new double[n * n];
647  const int leading_dimension_vec_right = n;
648 
649  // Workspace for the LAPACK routine
650  std::vector<double> work(1, 0.0);
651  // The dimension of the workspace array. Set to -1 so that a workspace
652  // query is assumed and LAPACK calculates the optimum size which is
653  // returned as the first value of work.
654  const int query_workspace = -1;
655  // Info flag for the LAPACK routine
656  int info = 0;
657 
658  // Call FORTRAN LAPACK to get the required workspace
659  // Note the use of the padding leading dimension for the matrices
660  LAPACK_DGGEV(no_eigvecs,
661  eigvecs,
662  n,
663  &A[0],
664  padded_n,
665  &M[0],
666  padded_n,
667  alpha_r,
668  alpha_i,
669  beta,
670  vec_left,
671  leading_dimension_vec_left,
672  vec_right,
673  leading_dimension_vec_right,
674  &work[0],
675  query_workspace,
676  info);
677 
678  // Succesful completion?
679  if (info != 0)
680  {
681  DGGEV_error(info, n);
682  }
683 
684  // Get the amount of requires workspace
685  int required_workspace = (int)work[0];
686  // If we need it
687  work.resize(required_workspace);
688 
689  // call FORTRAN LAPACK again with the optimum workspace
690  LAPACK_DGGEV(no_eigvecs,
691  eigvecs,
692  n,
693  &A[0],
694  padded_n,
695  &M[0],
696  padded_n,
697  alpha_r,
698  alpha_i,
699  beta,
700  vec_left,
701  leading_dimension_vec_left,
702  vec_right,
703  leading_dimension_vec_right,
704  &work[0],
705  required_workspace,
706  info);
707 
708  // Succesful completion?
709  if (info != 0)
710  {
711  DGGEV_error(info, n);
712  }
713 
714  // Now resize storage for the eigenvalues and eigenvectors
715  // We get them all!
716  alpha_eval.resize(n);
717  beta_eval.resize(n);
718  eigenvector_aux.resize(n);
719 
720  // Now convert the output into our format
721  for (int i = 0; i < n; ++i)
722  {
723  // Encode eigenvalues in alpha and beta vectors. Shift is undone here
724  alpha_eval[i] = std::complex<double>(Sigma_real + alpha_r[i], alpha_i[i]);
725  beta_eval[i] = beta[i];
726 
727  // Resize the eigenvector storage
728  eigenvector_aux[i].build(this->distribution_pt(), 0.0);
729 
730  // Load up the mangeled storage for the eigenvector. What's called
731  // eigenvector here isn't necessarily an eigenvector but provides storage
732  // for the real and imaginary parts of the real or complex conjugate
733  // eigenvectors. They are translated into actual complex eigenvectors
734  // (at the cost of some additional storage and the gain of considerable
735  // clarity) in the public version of this function.
736  for (int k = 0; k < n; ++k)
737  {
738  eigenvector_aux[i][k] = vec_right[i * n + k];
739  }
740  }
741 
742  // Delete all allocated storage
743  delete[] vec_right;
744  delete[] vec_left;
745  delete[] beta;
746  delete[] alpha_r;
747  delete[] alpha_i;
748  delete[] A;
749  delete[] M;
750  }
751 
752 
753  //==========================================================================
754  /// Use LAPACK QZ to solve the real eigenproblem that is assembled by elements
755  /// in a mesh in a Problem object. Note that the assembled matrices include
756  /// the shift and are real. The eigenvalues and eigenvectors are, in general,
757  /// complex. This is a legacy version of this function that stores re & imag
758  /// parts of eigenvectors in some solver-specific collection of real vectors;
759  /// they are disentangled in the alternative version of this function that
760  /// returns Vectors of complex vectors. At least n_eval eigenvalues are
761  /// computed.
762  //==========================================================================
764  Problem* const& problem_pt,
765  const int& n_eval,
766  Vector<std::complex<double>>& eigenvalue,
767  Vector<DoubleVector>& eigenvector_aux,
768  const bool& do_adjoint_problem)
769  {
770  if (do_adjoint_problem)
771  {
772  throw OomphLibError("Solving an adjoint eigenproblem is not currently "
773  "implemented for LAPACK_QZ.",
774  OOMPH_CURRENT_FUNCTION,
775  OOMPH_EXCEPTION_LOCATION);
776  }
777  Vector<std::complex<double>> alpha_eval;
778  Vector<double> beta_eval;
779 
780  // Call raw interface to lapack qz
782  problem_pt, n_eval, alpha_eval, beta_eval, eigenvector_aux);
783 
784  // Brute force computation of eigenvalues (allowing for NaNs and Infs)
785  unsigned n = problem_pt->ndof();
786  eigenvalue.resize(n);
787  for (unsigned i = 0; i < n; ++i)
788  {
789  // N.B. This is naive and dangerous according to the documentation
790  // beta could be very small giving over or under flow
791  // Shift has already been taken out in helper function
792  eigenvalue[i] = std::complex<double>(alpha_eval[i].real() / beta_eval[i],
793  alpha_eval[i].imag() / beta_eval[i]);
794  }
795  }
796 
797  //==========================================================================
798  /// Solve the real eigenproblem that is assembled by elements in
799  /// a mesh in a Problem object. Note that the assembled matrices include the
800  /// shift and are real. The eigenvalues and eigenvectors are,
801  /// in general, complex. Eigenvalues may be infinite and are therefore
802  /// returned as
803  /// \f$ \lambda_i = \alpha_i / \beta_i \f$ where \f$ \alpha_i \f$ is complex
804  /// while \f$ \beta_i \f$ is real. The actual eigenvalues may then be
805  /// computed by doing the division, checking for zero betas to avoid NaNs.
806  /// There's a convenience wrapper to this function that simply computes
807  /// these eigenvalues regardless. That version may die in NaN checking is
808  /// enabled (via the fenv.h header and the associated feenable function).
809  /// At least n_eval eigenvalues are computed.
810  //==========================================================================
811  void LAPACK_QZ::solve_eigenproblem(Problem* const& problem_pt,
812  const int& n_eval,
813  Vector<std::complex<double>>& alpha_eval,
814  Vector<double>& beta_eval,
815  Vector<DoubleVector>& eigenvector_real,
816  Vector<DoubleVector>& eigenvector_imag,
817  const bool& do_adjoint_problem)
818  {
819  if (do_adjoint_problem)
820  {
821  throw OomphLibError("Solving an adjoint eigenproblem is not currently "
822  "implemented for LAPACK_QZ.",
823  OOMPH_CURRENT_FUNCTION,
824  OOMPH_EXCEPTION_LOCATION);
825  }
826  Vector<DoubleVector> eigenvector_aux;
827 
828  // Call raw interface to lapack qz
830  problem_pt, n_eval, alpha_eval, beta_eval, eigenvector_aux);
831 
832  // Now move auxiliary data into actual complex eigenvalues
833  // Instrutions from lapack qz (where VR translates into eigenvector_aux):
834  // VR is DOUBLE PRECISION array, dimension (LDVR,N)
835  // If JOBVR = 'V', the right eigenvectors v(j) are stored one
836  // after another in the columns of VR, in the same order as
837  // their eigenvalues. If the j-th eigenvalue is real, then
838  // v(j) = VR(:,j), the j-th column of VR. If the j-th and
839  // (j+1)-th eigenvalues form a complex conjugate pair, then
840  // v(j) = VR(:,j)+i*VR(:,j+1) and v(j+1) = VR(:,j)-i*VR(:,j+1).
841  // Each eigenvector is scaled so the largest component has
842  // abs(real part)+abs(imag. part)=1.
843  unsigned n = problem_pt->ndof();
844  eigenvector_real.resize(n);
845  eigenvector_imag.resize(n);
846  unsigned eval_count = 0;
847  while (eval_count < n)
848  {
849  // i-th eigenvalue is real:
850  if (alpha_eval[eval_count].imag() == 0.0)
851  {
852  // Resize the single eigenvector associated with this
853  // single real eigenvalue
854  eigenvector_real[eval_count].build(this->distribution_pt(), 0.0);
855  eigenvector_imag[eval_count].build(this->distribution_pt(), 0.0);
856  for (unsigned j = 0; j < n; ++j)
857  {
858  eigenvector_real[eval_count][j] = eigenvector_aux[eval_count][j];
859  eigenvector_imag[eval_count][j] = 0.0;
860  }
861  eval_count++;
862  }
863  // Assume (and check!) that complex conjugate pairs follow each other
864  // as implied by
865  // http://www.netlib.org/lapack/explore-html/d9/d8e/group__double_g_eeigen_ga4f59e87e670a755b41cbdd7e97f36bea.html
866  else
867  {
868 #ifdef PARANOID
869 
870  // Are the eigenvalues finite? If not skip test.
871  if ((beta_eval[eval_count] != 0.0) &&
872  (beta_eval[eval_count + 1] != 0.0))
873  {
874  std::complex<double> lambda_this =
875  alpha_eval[eval_count] / beta_eval[eval_count];
876 
877  std::complex<double> lambda_next =
878  alpha_eval[eval_count + 1] / beta_eval[eval_count + 1];
879 
880  // Check failure of cc-ness to within tolerance
881  if (fabs(lambda_this.imag() + lambda_next.imag()) >
883  {
884  std::ostringstream error_stream;
885  error_stream << "Non-zero imaginary part of eigenvalue "
886  << eval_count << " : " << lambda_this.imag()
887  << std::endl;
888  error_stream
889  << "isn't the negative of its subsequent value : "
890  << lambda_next.imag() << std::endl
891  << "Their sum " << (lambda_this.imag() + lambda_next.imag())
892  << " is greater than Tolerance_for_ccness_check = "
893  << Tolerance_for_ccness_check << std::endl;
894  throw OomphLibError(error_stream.str(),
895  OOMPH_CURRENT_FUNCTION,
896  OOMPH_EXCEPTION_LOCATION);
897  }
898  if (fabs(lambda_this.real() - lambda_next.real()) >
900  {
901  std::ostringstream error_stream;
902  error_stream << "Real parts of complex eigenvalue " << eval_count
903  << " : " << lambda_this.real() << std::endl;
904  error_stream
905  << " doesn't agree with its supposed-to-be cc counterpart : "
906  << lambda_next.real() << std::endl
907  << "Their difference "
908  << (lambda_this.real() - lambda_next.real())
909  << " is greater than Tolerance_for_ccness_check = "
910  << Tolerance_for_ccness_check << std::endl;
911  throw OomphLibError(error_stream.str(),
912  OOMPH_CURRENT_FUNCTION,
913  OOMPH_EXCEPTION_LOCATION);
914  }
915  }
916 
917 #endif
918 
919 
920  // Resize the two cc eigenvectors associated with the
921  // two cc eigenvalues
922  eigenvector_real[eval_count].build(this->distribution_pt(), 0.0);
923  eigenvector_imag[eval_count].build(this->distribution_pt(), 0.0);
924  eigenvector_real[eval_count + 1].build(this->distribution_pt(), 0.0);
925  eigenvector_imag[eval_count + 1].build(this->distribution_pt(), 0.0);
926  for (unsigned j = 0; j < n; ++j)
927  {
928  eigenvector_real[eval_count][j] = eigenvector_aux[eval_count][j];
929  eigenvector_imag[eval_count][j] = eigenvector_aux[eval_count + 1][j];
930 
931  eigenvector_real[eval_count + 1][j] = eigenvector_aux[eval_count][j];
932  eigenvector_imag[eval_count + 1][j] =
933  -eigenvector_aux[eval_count + 1][j];
934  }
935  eval_count += 2;
936  }
937  }
938  }
939 
940  //==========================================================================
941  /// Use LAPACK to solve a complex eigen problem specified by the given
942  /// matrices. Note: the (real) shift
943  /// that's specifiable via the EigenSolver base class is ignored here.
944  /// A warning gets issued if it's set to a nonzero value.
945  //==========================================================================
947  const ComplexMatrixBase& A,
948  const ComplexMatrixBase& M,
949  Vector<std::complex<double>>& eigenvalue,
950  Vector<Vector<std::complex<double>>>& eigenvector)
951  {
952 #ifdef PARANOID
953  if (Sigma_real != 0.0)
954  {
955  std::stringstream error_stream;
956  error_stream << "Non-zero shift Sigma_real = " << Sigma_real
957  << " ignored in LAPACK_QZ::find_eigenvalues\n";
959  error_stream.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
960  }
961 #endif
962 
963  // Some character identifiers for use in the LAPACK routine
964  // Do not calculate the left eigenvectors
965  char no_eigvecs[2] = "N";
966 
967  // Do caculate the eigenvectors
968  char eigvecs[2] = "V";
969 
970  // Get the dimension of the matrix
971  int n = A.nrow(); // Total size of matrix
972 
973  // Storage for the matrices in the packed form required by the LAPACK
974  // routine
975  double* M_linear = new double[2 * n * n];
976  double* A_linear = new double[2 * n * n];
977 
978  // Now convert the matrices into the appropriate packed form
979  unsigned index = 0;
980  for (int i = 0; i < n; ++i)
981  {
982  for (int j = 0; j < n; ++j)
983  {
984  M_linear[index] = M(j, i).real();
985  A_linear[index] = A(j, i).real();
986  ++index;
987  M_linear[index] = M(j, i).imag();
988  A_linear[index] = A(j, i).imag();
989  ++index;
990  }
991  }
992 
993  // Temporary eigenvalue storage
994  double* alpha = new double[2 * n];
995  double* beta = new double[2 * n];
996  // Temporary eigenvector storage
997  double* vec_left = new double[2];
998  const int leading_dimension_vec_left = 1;
999  double* vec_right = new double[2 * n * n];
1000  const int leading_dimension_vec_right = n;
1001 
1002  // Workspace for the LAPACK routine
1003  std::vector<double> work(2, 0.0);
1004  // The dimension of the workspace array. Set to -1 so that a workspace
1005  // query is assumed and LAPACK calculates the optimum size which is
1006  // returned as the first value of work.
1007  const int query_workspace = -1;
1008  std::vector<double> rwork(8 * n, 0.0);
1009 
1010  // Info flag for the LAPACK routine
1011  int info = 0;
1012 
1013  // Call FORTRAN LAPACK to get the required workspace
1014  LAPACK_ZGGEV(no_eigvecs,
1015  eigvecs,
1016  n,
1017  &A_linear[0],
1018  n,
1019  &M_linear[0],
1020  n,
1021  alpha,
1022  beta,
1023  vec_left,
1024  leading_dimension_vec_left,
1025  vec_right,
1026  leading_dimension_vec_right,
1027  &work[0],
1028  query_workspace,
1029  &rwork[0],
1030  info);
1031 
1032  // Succesful completion?
1033  if (info != 0)
1034  {
1035  ZGGEV_error(info, n);
1036  }
1037 
1038 
1039  // Get the amount of requires workspace
1040  int required_workspace = (int)work[0];
1041  // If we need it
1042  work.resize(2 * required_workspace);
1043 
1044  // call FORTRAN LAPACK again with the optimum workspace
1045  LAPACK_ZGGEV(no_eigvecs,
1046  eigvecs,
1047  n,
1048  &A_linear[0],
1049  n,
1050  &M_linear[0],
1051  n,
1052  alpha,
1053  beta,
1054  vec_left,
1055  1,
1056  vec_right,
1057  n,
1058  &work[0],
1059  required_workspace,
1060  &rwork[0],
1061  info);
1062 
1063  // Succesful completion?
1064  if (info != 0)
1065  {
1066  ZGGEV_error(info, n);
1067  }
1068 
1069  // Now resize storage for the eigenvalues and eigenvectors
1070  // We get them all!
1071  eigenvalue.resize(n);
1072  eigenvector.resize(n);
1073 
1074  // Now convert the output into our format
1075  for (int i = 0; i < n; ++i)
1076  {
1077  // We have temporary complex numbers
1078  std::complex<double> num(alpha[2 * i], alpha[2 * i + 1]);
1079  std::complex<double> den(beta[2 * i], beta[2 * i + 1]);
1080 
1081  // N.B. This is naive and dangerous according to the documentation
1082  // beta could be very small giving over or under flow
1083  eigenvalue[i] = num / den;
1084 
1085  // Resize the eigenvector storage
1086  eigenvector[i].resize(n);
1087 
1088  // Copy across into the complex valued eigenvector
1089  for (int k = 0; k < n; ++k)
1090  {
1091  eigenvector[i][k] = std::complex<double>(
1092  vec_right[2 * i * n + 2 * k], vec_right[2 * i * n + 2 * k + 1]);
1093  }
1094  }
1095 
1096  // Delete all allocated storage
1097  delete[] vec_right;
1098  delete[] vec_left;
1099  delete[] beta;
1100  delete[] alpha;
1101  delete[] A_linear;
1102  delete[] M_linear;
1103  }
1104 } // namespace oomph
cstr elem_len * i
Definition: cfortran.h:603
int Spectrum
Integer to set whether the real, imaginary or magnitude is required to be small or large.
Definition: eigen_solver.h:187
int NArnoldi
Number of Arnoldi vectors to compute.
Definition: eigen_solver.h:190
void solve_eigenproblem_legacy(Problem *const &problem_pt, const int &n_eval, Vector< std::complex< double >> &eigenvalue, Vector< DoubleVector > &eigenvector, const bool &do_adjoint_problem=false)
Solve the eigen problem.
Definition: eigen_solver.cc:72
LinearSolver * Linear_solver_pt
Pointer to a linear solver.
Definition: eigen_solver.h:179
virtual ~ARPACK()
Destructor, delete the linear solver.
Definition: eigen_solver.cc:62
bool Small
Boolean to set which part of the spectrum left (default) or right of the shifted value.
Definition: eigen_solver.h:194
LinearSolver * Default_linear_solver_pt
Pointer to a default linear solver.
Definition: eigen_solver.h:182
ARPACK()
Constructor.
Definition: eigen_solver.cc:49
bool Compute_eigenvectors
Boolean to indicate whether or not to compute the eigenvectors.
Definition: eigen_solver.h:197
A class for compressed row matrices. This is a distributable object.
Definition: matrices.h:888
Abstract base class for matrices of complex doubles – adds abstract interfaces for solving,...
virtual unsigned long nrow() const =0
Return the number of rows of the matrix.
LinearAlgebraDistribution * distribution_pt() const
access to the LinearAlgebraDistribution
void build_distribution(const LinearAlgebraDistribution *const dist_pt)
setup the distribution of this distributable linear algebra object
A vector in the mathematical sense, initially developed for linear algebra type applications....
Definition: double_vector.h:58
void clear()
wipes the DoubleVector
Base class for all EigenProblem solves. This simply defines standard interfaces so that different sol...
Definition: eigen_solver.h:61
double Sigma_real
Double value that represents the real part of the shift in shifted eigensolvers.
Definition: eigen_solver.h:65
void ZGGEV_error(const int &info, const int &n)
Provide diagonstic for ZGGEV error return.
Definition: eigen_solver.h:441
void solve_eigenproblem_helper(Problem *const &problem_pt, const int &n_eval, Vector< std::complex< double >> &alpha, Vector< double > &beta, Vector< DoubleVector > &eigenvector)
Helper function called from legacy and updated version from "raw" lapack code.
void find_eigenvalues(const ComplexMatrixBase &A, const ComplexMatrixBase &M, Vector< std::complex< double >> &eigenvalue, Vector< Vector< std::complex< double >>> &eigenvector)
Find the eigenvalues of a complex generalised eigenvalue problem specified by . Note: the (real) shif...
void solve_eigenproblem(Problem *const &problem_pt, const int &n_eval, Vector< std::complex< double >> &alpha, Vector< double > &beta, Vector< DoubleVector > &eigenvector_real, Vector< DoubleVector > &eigenvector_imag, const bool &do_adjoint_problem=false)
Solve the real eigenproblem that is assembled by elements in a mesh in a Problem object....
void DGGEV_error(const int &info, const int &n)
Provide diagonstic for DGGEV error return.
Definition: eigen_solver.h:402
double Tolerance_for_ccness_check
Tolerance for checking complex conjugateness of eigenvalues.
Definition: eigen_solver.h:479
void solve_eigenproblem_legacy(Problem *const &problem_pt, const int &n_eval, Vector< std::complex< double >> &eigenvalue, Vector< DoubleVector > &eigenvector, const bool &do_adjoint_problem=false)
Use LAPACK QZ to solve the real eigenproblem that is assembled by elements in a mesh in a Problem obj...
Describes the distribution of a distributable linear algebra type object. Typically this is a contain...
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...
virtual void enable_resolve()
Enable resolve (i.e. store matrix and/or LU decomposition, say) Virtual so it can be overloaded to pe...
virtual void resolve(const DoubleVector &rhs, DoubleVector &result)
Resolve the system defined by the last assembled jacobian and the rhs vector. Solution is returned in...
void disable_doc_time()
Disable documentation of solve times.
virtual void disable_resolve()
Disable resolve (i.e. store matrix and/or LU decomposition, say) This function simply resets an inter...
An OomphLibError object which should be thrown when an run-time error is encountered....
An OomphLibWarning object which should be created as a temporary object to issue a warning....
////////////////////////////////////////////////////////////////// //////////////////////////////////...
Definition: problem.h:151
virtual void get_eigenproblem_matrices(CRDoubleMatrix &mass_matrix, CRDoubleMatrix &main_matrix, const double &shift=0.0)
Get the matrices required by a eigensolver. If the shift parameter is non-zero the second matrix will...
Definition: problem.cc:8602
unsigned long ndof() const
Return the number of dofs.
Definition: problem.h:1678
OomphCommunicator * communicator_pt()
access function to the oomph-lib communicator
Definition: problem.h:1246
//////////////////////////////////////////////////////////////////////////// ////////////////////////...
A slight extension to the standard template vector class so that we can include "graceful" array rang...
Definition: Vector.h:58
//////////////////////////////////////////////////////////////////// ////////////////////////////////...
OomphInfo oomph_info
Single (global) instantiation of the OomphInfo object – this is used throughout the library as a "rep...