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-2024 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 
33 // Include cfortran.h and the header for the LAPACK QZ routines
34 #include "cfortran.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  ////////////////////////////////////////////////////////////////////////////
47  ////////////////////////////////////////////////////////////////////////////
48 
49 
50  //==========================================================================
51  /// Use LAPACK QZ to solve the real eigenproblem that is assembled by elements
52  /// in a mesh in a Problem object. Note that the assembled matrices include
53  /// the shift and are real. The eigenvalues and eigenvectors are, in general,
54  /// complex.
55  /// Eigenvalues may be infinite and are therefore returned as
56  /// \f$ \lambda_i = \alpha_i / \beta_i \f$ where \f$ \alpha_i \f$ is complex
57  /// while \f$ \beta_i \f$ is real. The actual eigenvalues may then be
58  /// computed by doing the division, checking for zero betas to avoid NaNs.
59  /// This is actually a helper function that stores re & imag parts of
60  /// eigenvectors in a collection of real vectors; they
61  /// are disentangled in the alternative version of this function that returns
62  /// Vectors of complex vectors.
63  /// At least n_eval eigenvalues are computed.
64  //==========================================================================
66  Problem* const& problem_pt,
67  const int& n_eval,
68  Vector<std::complex<double>>& alpha_eval,
69  Vector<double>& beta_eval,
70  Vector<DoubleVector>& eigenvector_aux)
71  {
72  // Some character identifiers for use in the LAPACK routine
73  // Do not calculate the left eigenvectors
74  char no_eigvecs[2] = "N";
75 
76  // Do caculate the eigenvectors
77  char eigvecs[2] = "V";
78 
79  // Get the dimension of the matrix
80  int n = problem_pt->ndof(); // Total size of matrix
81 
82  // Use padding?
83  bool use_padding = false;
84 
85  // If the dimension of the matrix is even, then pad the arrays to
86  // make the size odd. This somehow sorts out a strange run-time behaviour
87  // identified by Rich Hewitt.
88  // Actual size of matrix that will be allocated
89  int padded_n = n;
90  if (n % 2 == 0)
91  {
92  use_padding = true;
93  padded_n += 1;
94  }
95 
96  // Storage for the matrices in the packed form required by the LAPACK
97  // routine
98  double* M = new double[padded_n * padded_n];
99  double* A = new double[padded_n * padded_n];
100 
101  // TEMPORARY
102  // only use non-distributed matrices and vectors
103  LinearAlgebraDistribution dist(problem_pt->communicator_pt(), n, false);
104  this->build_distribution(dist);
105 
106  // Enclose in a separate scope so that memory is cleaned after assembly
107  {
108  // Allocated Row compressed matrices for the mass matrix and shifted main
109  // matrix
110  CRDoubleMatrix temp_M(this->distribution_pt()),
111  temp_AsigmaM(this->distribution_pt());
112 
113  // Assemble the matrices; pass the shift into the assembly
114  problem_pt->get_eigenproblem_matrices(temp_M, temp_AsigmaM, Sigma_real);
115 
116  // Now convert these matrices into the appropriate packed form
117  unsigned index = 0;
118  for (int i = 0; i < n; ++i)
119  {
120  for (int j = 0; j < n; ++j)
121  {
122  M[index] = temp_M(j, i);
123  A[index] = temp_AsigmaM(j, i);
124  ++index;
125  }
126  // If necessary pad the columns with zeros
127  if (use_padding)
128  {
129  M[index] = 0.0;
130  A[index] = 0.0;
131  ++index;
132  }
133  }
134  // No need to pad the final row because it is never accessed by the
135  // routine.
136  }
137 
138  // Temporary eigenvalue storage
139  double* alpha_r = new double[n];
140  double* alpha_i = new double[n];
141  double* beta = new double[n];
142  // Temporary eigenvector storage
143  double* vec_left = new double[1];
144  const int leading_dimension_vec_left = 1;
145  double* vec_right = new double[n * n];
146  const int leading_dimension_vec_right = n;
147 
148  // Workspace for the LAPACK routine
149  std::vector<double> work(1, 0.0);
150  // The dimension of the workspace array. Set to -1 so that a workspace
151  // query is assumed and LAPACK calculates the optimum size which is
152  // returned as the first value of work.
153  const int query_workspace = -1;
154  // Info flag for the LAPACK routine
155  int info = 0;
156 
157  // Call FORTRAN LAPACK to get the required workspace
158  // Note the use of the padding leading dimension for the matrices
159  LAPACK_DGGEV(no_eigvecs,
160  eigvecs,
161  n,
162  &A[0],
163  padded_n,
164  &M[0],
165  padded_n,
166  alpha_r,
167  alpha_i,
168  beta,
169  vec_left,
170  leading_dimension_vec_left,
171  vec_right,
172  leading_dimension_vec_right,
173  &work[0],
174  query_workspace,
175  info);
176 
177  // Succesful completion?
178  if (info != 0)
179  {
180  DGGEV_error(info, n);
181  }
182 
183  // Get the amount of requires workspace
184  int required_workspace = (int)work[0];
185  // If we need it
186  work.resize(required_workspace);
187 
188  // call FORTRAN LAPACK again with the optimum workspace
189  LAPACK_DGGEV(no_eigvecs,
190  eigvecs,
191  n,
192  &A[0],
193  padded_n,
194  &M[0],
195  padded_n,
196  alpha_r,
197  alpha_i,
198  beta,
199  vec_left,
200  leading_dimension_vec_left,
201  vec_right,
202  leading_dimension_vec_right,
203  &work[0],
204  required_workspace,
205  info);
206 
207  // Succesful completion?
208  if (info != 0)
209  {
210  DGGEV_error(info, n);
211  }
212 
213  // Now resize storage for the eigenvalues and eigenvectors
214  // We get them all!
215  alpha_eval.resize(n);
216  beta_eval.resize(n);
217  eigenvector_aux.resize(n);
218 
219  // Now convert the output into our format
220  for (int i = 0; i < n; ++i)
221  {
222  // Encode eigenvalues in alpha and beta vectors. Shift is undone here
223  alpha_eval[i] = std::complex<double>(Sigma_real + alpha_r[i], alpha_i[i]);
224  beta_eval[i] = beta[i];
225 
226  // Resize the eigenvector storage
227  eigenvector_aux[i].build(this->distribution_pt(), 0.0);
228 
229  // Load up the mangeled storage for the eigenvector. What's called
230  // eigenvector here isn't necessarily an eigenvector but provides storage
231  // for the real and imaginary parts of the real or complex conjugate
232  // eigenvectors. They are translated into actual complex eigenvectors
233  // (at the cost of some additional storage and the gain of considerable
234  // clarity) in the public version of this function.
235  for (int k = 0; k < n; ++k)
236  {
237  eigenvector_aux[i][k] = vec_right[i * n + k];
238  }
239  }
240 
241  // Delete all allocated storage
242  delete[] vec_right;
243  delete[] vec_left;
244  delete[] beta;
245  delete[] alpha_r;
246  delete[] alpha_i;
247  delete[] A;
248  delete[] M;
249  }
250 
251 
252  //==========================================================================
253  /// Solve the real eigenproblem that is assembled by elements in
254  /// a mesh in a Problem object. Note that the assembled matrices include the
255  /// shift and are real. The eigenvalues and eigenvectors are,
256  /// in general, complex. Eigenvalues may be infinite and are therefore
257  /// returned as
258  /// \f$ \lambda_i = \alpha_i / \beta_i \f$ where \f$ \alpha_i \f$ is complex
259  /// while \f$ \beta_i \f$ is real. The actual eigenvalues may then be
260  /// computed by doing the division, checking for zero betas to avoid NaNs.
261  /// There's a convenience wrapper to this function that simply computes
262  /// these eigenvalues regardless. That version may die in NaN checking is
263  /// enabled (via the fenv.h header and the associated feenable function).
264  /// At least n_eval eigenvalues are computed.
265  //==========================================================================
266  void LAPACK_QZ::solve_eigenproblem(Problem* const& problem_pt,
267  const int& n_eval,
268  Vector<std::complex<double>>& alpha_eval,
269  Vector<double>& beta_eval,
270  Vector<DoubleVector>& eigenvector_real,
271  Vector<DoubleVector>& eigenvector_imag,
272  const bool& do_adjoint_problem)
273  {
274  if (do_adjoint_problem)
275  {
276  throw OomphLibError("Solving an adjoint eigenproblem is not currently "
277  "implemented for LAPACK_QZ.",
278  OOMPH_CURRENT_FUNCTION,
279  OOMPH_EXCEPTION_LOCATION);
280  }
281  Vector<DoubleVector> eigenvector_aux;
282 
283  // Call raw interface to lapack qz
285  problem_pt, n_eval, alpha_eval, beta_eval, eigenvector_aux);
286 
287  // Now move auxiliary data into actual complex eigenvalues
288  // Instrutions from lapack qz (where VR translates into eigenvector_aux):
289  // VR is DOUBLE PRECISION array, dimension (LDVR,N)
290  // If JOBVR = 'V', the right eigenvectors v(j) are stored one
291  // after another in the columns of VR, in the same order as
292  // their eigenvalues. If the j-th eigenvalue is real, then
293  // v(j) = VR(:,j), the j-th column of VR. If the j-th and
294  // (j+1)-th eigenvalues form a complex conjugate pair, then
295  // v(j) = VR(:,j)+i*VR(:,j+1) and v(j+1) = VR(:,j)-i*VR(:,j+1).
296  // Each eigenvector is scaled so the largest component has
297  // abs(real part)+abs(imag. part)=1.
298  unsigned n = problem_pt->ndof();
299  eigenvector_real.resize(n);
300  eigenvector_imag.resize(n);
301  unsigned eval_count = 0;
302  while (eval_count < n)
303  {
304  // i-th eigenvalue is real:
305  if (alpha_eval[eval_count].imag() == 0.0)
306  {
307  // Resize the single eigenvector associated with this
308  // single real eigenvalue
309  eigenvector_real[eval_count].build(this->distribution_pt(), 0.0);
310  eigenvector_imag[eval_count].build(this->distribution_pt(), 0.0);
311  for (unsigned j = 0; j < n; ++j)
312  {
313  eigenvector_real[eval_count][j] = eigenvector_aux[eval_count][j];
314  eigenvector_imag[eval_count][j] = 0.0;
315  }
316  eval_count++;
317  }
318  // Assume (and check!) that complex conjugate pairs follow each other
319  // as implied by
320  // http://www.netlib.org/lapack/explore-html/d9/d8e/group__double_g_eeigen_ga4f59e87e670a755b41cbdd7e97f36bea.html
321  else
322  {
323 #ifdef PARANOID
324 
325  // Are the eigenvalues finite? If not skip test.
326  if ((beta_eval[eval_count] != 0.0) &&
327  (beta_eval[eval_count + 1] != 0.0))
328  {
329  std::complex<double> lambda_this =
330  alpha_eval[eval_count] / beta_eval[eval_count];
331 
332  std::complex<double> lambda_next =
333  alpha_eval[eval_count + 1] / beta_eval[eval_count + 1];
334 
335  // Check failure of cc-ness to within tolerance
336  if (fabs(lambda_this.imag() + lambda_next.imag()) >
338  {
339  std::ostringstream error_stream;
340  error_stream << "Non-zero imaginary part of eigenvalue "
341  << eval_count << " : " << lambda_this.imag()
342  << std::endl;
343  error_stream
344  << "isn't the negative of its subsequent value : "
345  << lambda_next.imag() << std::endl
346  << "Their sum " << (lambda_this.imag() + lambda_next.imag())
347  << " is greater than Tolerance_for_ccness_check = "
348  << Tolerance_for_ccness_check << std::endl;
349  throw OomphLibError(error_stream.str(),
350  OOMPH_CURRENT_FUNCTION,
351  OOMPH_EXCEPTION_LOCATION);
352  }
353  if (fabs(lambda_this.real() - lambda_next.real()) >
355  {
356  std::ostringstream error_stream;
357  error_stream << "Real parts of complex eigenvalue " << eval_count
358  << " : " << lambda_this.real() << std::endl;
359  error_stream
360  << " doesn't agree with its supposed-to-be cc counterpart : "
361  << lambda_next.real() << std::endl
362  << "Their difference "
363  << (lambda_this.real() - lambda_next.real())
364  << " is greater than Tolerance_for_ccness_check = "
365  << Tolerance_for_ccness_check << std::endl;
366  throw OomphLibError(error_stream.str(),
367  OOMPH_CURRENT_FUNCTION,
368  OOMPH_EXCEPTION_LOCATION);
369  }
370  }
371 
372 #endif
373 
374 
375  // Resize the two cc eigenvectors associated with the
376  // two cc eigenvalues
377  eigenvector_real[eval_count].build(this->distribution_pt(), 0.0);
378  eigenvector_imag[eval_count].build(this->distribution_pt(), 0.0);
379  eigenvector_real[eval_count + 1].build(this->distribution_pt(), 0.0);
380  eigenvector_imag[eval_count + 1].build(this->distribution_pt(), 0.0);
381  for (unsigned j = 0; j < n; ++j)
382  {
383  eigenvector_real[eval_count][j] = eigenvector_aux[eval_count][j];
384  eigenvector_imag[eval_count][j] = eigenvector_aux[eval_count + 1][j];
385 
386  eigenvector_real[eval_count + 1][j] = eigenvector_aux[eval_count][j];
387  eigenvector_imag[eval_count + 1][j] =
388  -eigenvector_aux[eval_count + 1][j];
389  }
390  eval_count += 2;
391  }
392  }
393  }
394 
395  //==========================================================================
396  /// Use LAPACK to solve a complex eigen problem specified by the given
397  /// matrices. Note: the (real) shift
398  /// that's specifiable via the EigenSolver base class is ignored here.
399  /// A warning gets issued if it's set to a nonzero value.
400  //==========================================================================
402  const ComplexMatrixBase& A,
403  const ComplexMatrixBase& M,
404  Vector<std::complex<double>>& eigenvalue,
405  Vector<Vector<std::complex<double>>>& eigenvector)
406  {
407 #ifdef PARANOID
408  if (Sigma_real != 0.0)
409  {
410  std::stringstream error_stream;
411  error_stream << "Non-zero shift Sigma_real = " << Sigma_real
412  << " ignored in LAPACK_QZ::find_eigenvalues\n";
414  error_stream.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
415  }
416 #endif
417 
418  // Some character identifiers for use in the LAPACK routine
419  // Do not calculate the left eigenvectors
420  char no_eigvecs[2] = "N";
421 
422  // Do caculate the eigenvectors
423  char eigvecs[2] = "V";
424 
425  // Get the dimension of the matrix
426  int n = A.nrow(); // Total size of matrix
427 
428  // Storage for the matrices in the packed form required by the LAPACK
429  // routine
430  double* M_linear = new double[2 * n * n];
431  double* A_linear = new double[2 * n * n];
432 
433  // Now convert the matrices into the appropriate packed form
434  unsigned index = 0;
435  for (int i = 0; i < n; ++i)
436  {
437  for (int j = 0; j < n; ++j)
438  {
439  M_linear[index] = M(j, i).real();
440  A_linear[index] = A(j, i).real();
441  ++index;
442  M_linear[index] = M(j, i).imag();
443  A_linear[index] = A(j, i).imag();
444  ++index;
445  }
446  }
447 
448  // Temporary eigenvalue storage
449  double* alpha = new double[2 * n];
450  double* beta = new double[2 * n];
451  // Temporary eigenvector storage
452  double* vec_left = new double[2];
453  const int leading_dimension_vec_left = 1;
454  double* vec_right = new double[2 * n * n];
455  const int leading_dimension_vec_right = n;
456 
457  // Workspace for the LAPACK routine
458  std::vector<double> work(2, 0.0);
459  // The dimension of the workspace array. Set to -1 so that a workspace
460  // query is assumed and LAPACK calculates the optimum size which is
461  // returned as the first value of work.
462  const int query_workspace = -1;
463  std::vector<double> rwork(8 * n, 0.0);
464 
465  // Info flag for the LAPACK routine
466  int info = 0;
467 
468  // Call FORTRAN LAPACK to get the required workspace
469  LAPACK_ZGGEV(no_eigvecs,
470  eigvecs,
471  n,
472  &A_linear[0],
473  n,
474  &M_linear[0],
475  n,
476  alpha,
477  beta,
478  vec_left,
479  leading_dimension_vec_left,
480  vec_right,
481  leading_dimension_vec_right,
482  &work[0],
483  query_workspace,
484  &rwork[0],
485  info);
486 
487  // Succesful completion?
488  if (info != 0)
489  {
490  ZGGEV_error(info, n);
491  }
492 
493 
494  // Get the amount of requires workspace
495  int required_workspace = (int)work[0];
496  // If we need it
497  work.resize(2 * required_workspace);
498 
499  // call FORTRAN LAPACK again with the optimum workspace
500  LAPACK_ZGGEV(no_eigvecs,
501  eigvecs,
502  n,
503  &A_linear[0],
504  n,
505  &M_linear[0],
506  n,
507  alpha,
508  beta,
509  vec_left,
510  1,
511  vec_right,
512  n,
513  &work[0],
514  required_workspace,
515  &rwork[0],
516  info);
517 
518  // Succesful completion?
519  if (info != 0)
520  {
521  ZGGEV_error(info, n);
522  }
523 
524  // Now resize storage for the eigenvalues and eigenvectors
525  // We get them all!
526  eigenvalue.resize(n);
527  eigenvector.resize(n);
528 
529  // Now convert the output into our format
530  for (int i = 0; i < n; ++i)
531  {
532  // We have temporary complex numbers
533  std::complex<double> num(alpha[2 * i], alpha[2 * i + 1]);
534  std::complex<double> den(beta[2 * i], beta[2 * i + 1]);
535 
536  // N.B. This is naive and dangerous according to the documentation
537  // beta could be very small giving over or under flow
538  eigenvalue[i] = num / den;
539 
540  // Resize the eigenvector storage
541  eigenvector[i].resize(n);
542 
543  // Copy across into the complex valued eigenvector
544  for (int k = 0; k < n; ++k)
545  {
546  eigenvector[i][k] = std::complex<double>(
547  vec_right[2 * i * n + 2 * k], vec_right[2 * i * n + 2 * k + 1]);
548  }
549  }
550 
551  // Delete all allocated storage
552  delete[] vec_right;
553  delete[] vec_left;
554  delete[] beta;
555  delete[] alpha;
556  delete[] A_linear;
557  delete[] M_linear;
558  }
559 } // namespace oomph
cstr elem_len * i
Definition: cfortran.h:603
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
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:266
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 "raw" lapack code.
Definition: eigen_solver.cc:65
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:227
double Tolerance_for_ccness_check
Tolerance for checking complex conjugateness of eigenvalues.
Definition: eigen_solver.h:304
Describes the distribution of a distributable linear algebra type object. Typically this is a contain...
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:154
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:8429
unsigned long ndof() const
Return the number of dofs.
Definition: problem.h:1704
OomphCommunicator * communicator_pt()
access function to the oomph-lib communicator
Definition: problem.h:1266
A slight extension to the standard template vector class so that we can include "graceful" array rang...
Definition: Vector.h:58
//////////////////////////////////////////////////////////////////// ////////////////////////////////...