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-2025 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
43namespace 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,
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
112
113 // Assemble the matrices; pass the shift into the assembly
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
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,
171 vec_right,
173 &work[0],
175 info);
176
177 // Succesful completion?
178 if (info != 0)
179 {
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
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,
201 vec_right,
203 &work[0],
205 info);
206
207 // Succesful completion?
208 if (info != 0)
209 {
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,
272 const bool& do_adjoint_problem)
273 {
275 {
276 throw OomphLibError("Solving an adjoint eigenproblem is not currently "
277 "implemented for LAPACK_QZ.",
280 }
282
283 // Call raw interface to lapack qz
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 {
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 =
331
332 std::complex<double> lambda_next =
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;
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(),
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;
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(),
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 {
385
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";
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
470 eigvecs,
471 n,
472 &A_linear[0],
473 n,
474 &M_linear[0],
475 n,
476 alpha,
477 beta,
478 vec_left,
480 vec_right,
482 &work[0],
484 &rwork[0],
485 info);
486
487 // Succesful completion?
488 if (info != 0)
489 {
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
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],
515 &rwork[0],
516 info);
517
518 // Succesful completion?
519 if (info != 0)
520 {
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.
void ZGGEV_error(const int &info, const int &n)
Provide diagonstic for ZGGEV error return.
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.
void DGGEV_error(const int &info, const int &n)
Provide diagonstic for DGGEV error return.
double Tolerance_for_ccness_check
Tolerance for checking complex conjugateness of eigenvalues.
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....
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:8442
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
TAdvectionDiffusionReactionElement<NREAGENT,DIM,NNODE_1D> elements are isoparametric triangular DIM-d...
A slight extension to the standard template vector class so that we can include "graceful" array rang...
Definition Vector.h:58
DRAIG: Change all instances of (SPATIAL_DIM) to (DIM-1).