eigen_solver.h
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 // Header file for a class that defines interfaces to Eigensolvers
27 
28 // Include guard to prevent multiple inclusions of the header
29 #ifndef OOMPH_EIGEN_SOLVER_HEADER
30 #define OOMPH_EIGEN_SOLVER_HEADER
31 
32 // Include the header generated by autoconfig
33 #ifdef HAVE_CONFIG_H
34 #include <oomph-lib-config.h>
35 #endif
36 
37 #ifdef OOMPH_HAS_MPI
38 #include "mpi.h"
39 #endif
40 
41 #include <complex>
42 #include "Vector.h"
43 #include "complex_matrices.h"
44 
45 namespace oomph
46 {
47  // Forward definition of problem class
48  class Problem;
49 
50  // Forward definition of matrix class
51  class DoubleMatrixBase;
52 
53  // Forward definition of linear solver class
54  class LinearSolver;
55 
56  //=======================================================================
57  /// Base class for all EigenProblem solves. This simply defines standard
58  /// interfaces so that different solvers can be used easily.
59  //=======================================================================
61  {
62  protected:
63  /// Double value that represents the real part of the shift in
64  /// shifted eigensolvers
65  double Sigma_real;
66 
67  public:
68  /// Empty constructor
69  EigenSolver() : Sigma_real(0.0) {}
70 
71  /// Empty copy constructor
73 
74  /// Empty destructor
75  virtual ~EigenSolver() {}
76 
77  /// Eigensolver. This takes a pointer to a problem and returns
78  /// a vector of complex numbers representing the eigenvalues
79  /// and a corresponding vector of eigenvectors. n_eval specifies the min.
80  /// number of eigenvalues/vectors required. This is primarily used in
81  /// Arnoldi type implementations; direct solvers such as QZ compute all the
82  /// eigenvalues/vectors.
83  /// Note: this is a legacy version of this function that stores re & imag
84  /// parts of eigenvectors in some solver-specific collection of real
85  /// vectors.
87  Problem* const& problem_pt,
88  const int& n_eval,
89  Vector<std::complex<double>>& eigenvalue,
90  Vector<DoubleVector>& eigenvector,
91  const bool& do_adjoint_problem = false) = 0;
92 
93 
94  /// Solve the real eigenproblem that is assembled by elements in
95  /// a mesh in a Problem object. Note that the assembled matrices include the
96  /// shift and are real. The eigenvalues and eigenvectors are,
97  /// in general, complex, and the eigenvalues can be NaNs or Infs.
98  /// This function is therefore merely provided as a convenience, to be
99  /// used if the user is confident that NaNs don't arise (e.g. in Arnoldi
100  /// based solvers where typically only a small number of (finite)
101  /// eigenvalues are computed), or if the users is happy to deal with NaNs in
102  /// the subsequent post-processing.
103  /// Function is virtual so it can be overloaded for Arnoldi type solvers
104  /// that compute the (finite) eigenvalues directly
105  /// At least n_eval eigenvalues are computed.
106  virtual void solve_eigenproblem(Problem* const& problem_pt,
107  const int& n_eval,
108  Vector<std::complex<double>>& eigenvalue,
109  Vector<DoubleVector>& eigenvector_real,
110  Vector<DoubleVector>& eigenvector_imag,
111  const bool& do_adjoint_problem = false)
112  {
114  Vector<double> beta;
115 
116  // Call the "safe" version
117  solve_eigenproblem(problem_pt,
118  n_eval,
119  alpha,
120  beta,
121  eigenvector_real,
122  eigenvector_imag,
123  do_adjoint_problem);
124 
125  // Now do the brute force conversion, possibly creating NaNs and Infs...
126  unsigned n = alpha.size();
127  eigenvalue.resize(n);
128  for (unsigned i = 0; i < n; i++)
129  {
130  eigenvalue[i] = alpha[i] / beta[i];
131  }
132  }
133 
134  /// Solve the real eigenproblem that is assembled by elements in
135  /// a mesh in a Problem object. Note that the assembled matrices include the
136  /// shift and are real. The eigenvalues and eigenvectors are,
137  /// in general, complex. Eigenvalues may be infinite and are therefore
138  /// returned as
139  /// \f$ \lambda_i = \alpha_i / \beta_i \f$ where \f$ \alpha_i \f$ is complex
140  /// while \f$ \beta_i \f$ is real. The actual eigenvalues may then be
141  /// computed by doing the division, checking for zero betas to avoid NaNs.
142  /// There's a convenience wrapper to this function that simply computes
143  /// these eigenvalues regardless. That version may die in NaN checking is
144  /// enabled (via the fenv.h header and the associated feenable function).
145  /// At least n_eval eigenvalues are computed.
146  virtual void solve_eigenproblem(Problem* const& problem_pt,
147  const int& n_eval,
148  Vector<std::complex<double>>& alpha,
149  Vector<double>& beta,
150  Vector<DoubleVector>& eigenvector_real,
151  Vector<DoubleVector>& eigenvector_imag,
152  const bool& do_adjoint_problem = false) = 0;
153 
154  /// Set the value of the (real) shift
155  void set_shift(const double& shift_value)
156  {
157  Sigma_real = shift_value;
158  }
159 
160  /// Return the value of the (real) shift (const version)
161  const double& get_shift() const
162  {
163  return Sigma_real;
164  }
165  };
166 
167  ////////////////////////////////////////////////////////////////////////////////
168  ////////////////////////////////////////////////////////////////////////////////
169  ////////////////////////////////////////////////////////////////////////////////
170 
171 
172  //=====================================================================
173  /// Class for the ARPACK eigensolver
174  //=====================================================================
175  class ARPACK : public EigenSolver
176  {
177  private:
178  /// Pointer to a linear solver
180 
181  /// Pointer to a default linear solver
183 
184  /// Integer to set whether the real, imaginary or magnitude is
185  /// required
186  /// to be small or large.
187  int Spectrum;
188 
189  /// Number of Arnoldi vectors to compute
190  int NArnoldi;
191 
192  /// Boolean to set which part of the spectrum left (default) or right
193  /// of the shifted value.
194  bool Small;
195 
196  /// Boolean to indicate whether or not to compute the eigenvectors
198 
199 
200  public:
201  /// Constructor
202  ARPACK();
203 
204  /// Empty copy constructor
205  ARPACK(const ARPACK&) {}
206 
207  /// Destructor, delete the linear solver
208  virtual ~ARPACK();
209 
210  /// Access function for the number of Arnoldi vectors
211  int& narnoldi()
212  {
213  return NArnoldi;
214  }
215 
216  /// Access function for the number of Arnoldi vectors (const version)
217  const int& narnoldi() const
218  {
219  return NArnoldi;
220  }
221 
222  /// Set to enable the computation of the eigenvectors (default)
224  {
225  Compute_eigenvectors = true;
226  }
227 
228  /// Set to disable the computation of the eigenvectors
230  {
231  Compute_eigenvectors = false;
232  }
233 
234  /// Solve the eigen problem
235  void solve_eigenproblem_legacy(Problem* const& problem_pt,
236  const int& n_eval,
237  Vector<std::complex<double>>& eigenvalue,
238  Vector<DoubleVector>& eigenvector,
239  const bool& do_adjoint_problem = false);
240 
241 
242  /// Solve the real eigenproblem that is assembled by elements in
243  /// a mesh in a Problem object. Note that the assembled matrices include the
244  /// shift and are real. The eigenvalues and eigenvectors are,
245  /// in general, complex. Eigenvalues may be infinite and are therefore
246  /// returned as
247  /// \f$ \lambda_i = \alpha_i / \beta_i \f$ where \f$ \alpha_i \f$ is complex
248  /// while \f$ \beta_i \f$ is real. The actual eigenvalues may then be
249  /// computed by doing the division, checking for zero betas to avoid NaNs.
250  /// There's a convenience wrapper to this function that simply computes
251  /// these eigenvalues regardless. That version may die in NaN checking is
252  /// enabled (via the fenv.h header and the associated feenable function).
253  /// At least n_eval eigenvalues are computed.
254  void solve_eigenproblem(Problem* const& problem_pt,
255  const int& n_eval,
256  Vector<std::complex<double>>& alpha,
257  Vector<double>& beta,
258  Vector<DoubleVector>& eigenvector_real,
259  Vector<DoubleVector>& eigenvector_imag,
260  const bool& do_adjoint_problem = false)
261  {
262  oomph_info << "Broken, but then don't we want arpack to go anyway?\n";
263  abort();
264  }
265 
266  /// Set the desired eigenvalues to be left of the shift
268  {
269  Small = true;
270  }
271 
272  /// Set the desired eigenvalues to be right of the shift
274  {
275  Small = false;
276  }
277 
278  /// Set the real part to be the quantity of interest (default)
280  {
281  Spectrum = 1;
282  }
283 
284  /// Set the imaginary part fo the quantity of interest
286  {
287  Spectrum = -1;
288  }
289 
290  /// Set the magnitude to be the quantity of interest
292  {
293  Spectrum = 0;
294  }
295 
296  /// Return a pointer to the linear solver object
298  {
299  return Linear_solver_pt;
300  }
301 
302  /// Return a pointer to the linear solver object (const version)
304  {
305  return Linear_solver_pt;
306  }
307  };
308 
309 
310  ///////////////////////////////////////////////////////////////////////
311  ///////////////////////////////////////////////////////////////////////
312  ///////////////////////////////////////////////////////////////////////
313 
314  //=====================================================================
315  /// Class for the LAPACK QZ eigensolver
316  //=====================================================================
317  class LAPACK_QZ : public EigenSolver
318  {
319  public:
320  /// Empty constructor
322 
323  /// Broken copy constructor
324  LAPACK_QZ(const LAPACK_QZ&) = delete;
325 
326  /// Broken assignment operator
327  void operator=(const LAPACK_QZ&) = delete;
328 
329  /// Empty desctructor
330  virtual ~LAPACK_QZ() {}
331 
332  /// Use LAPACK QZ to solve the real eigenproblem that is assembled
333  /// by elements in a mesh in a Problem object. Note that the assembled
334  /// matrices include the shift and are real. The eigenvalues and
335  /// eigenvectors are, in general, complex.
336  /// This is a legacy version of this function that stores re & imag parts of
337  /// eigenvectors in some solver-specific collection of real vectors;
338  /// they are disentangled in the alternative version of this function
339  /// that returns Vectors of complex Vectors.
340  /// At least n_eval eigenvalues are computed.
341  void solve_eigenproblem_legacy(Problem* const& problem_pt,
342  const int& n_eval,
343  Vector<std::complex<double>>& eigenvalue,
344  Vector<DoubleVector>& eigenvector,
345  const bool& do_adjoint_problem = false);
346 
347  /// Solve the real eigenproblem that is assembled by elements in
348  /// a mesh in a Problem object. Note that the assembled matrices include the
349  /// shift and are real. The eigenvalues and eigenvectors are,
350  /// in general, complex. Eigenvalues may be infinite and are therefore
351  /// returned as
352  /// \f$ \lambda_i = \alpha_i / \beta_i \f$ where \f$ \alpha_i \f$ is complex
353  /// while \f$ \beta_i \f$ is real. The actual eigenvalues may then be
354  /// computed by doing the division, checking for zero betas to avoid NaNs.
355  /// There's a convenience wrapper to this function that simply computes
356  /// these eigenvalues regardless. That version may die in NaN checking is
357  /// enabled (via the fenv.h header and the associated feenable function).
358  /// At least n_eval eigenvalues are computed.
359  void solve_eigenproblem(Problem* const& problem_pt,
360  const int& n_eval,
361  Vector<std::complex<double>>& alpha,
362  Vector<double>& beta,
363  Vector<DoubleVector>& eigenvector_real,
364  Vector<DoubleVector>& eigenvector_imag,
365  const bool& do_adjoint_problem = false);
366 
367  /// Find the eigenvalues of a complex generalised eigenvalue problem
368  /// specified by \f$ Ax = \lambda Mx \f$. Note: the (real) shift
369  /// that's specifiable via the EigenSolver base class is ignored here.
370  /// A warning gets issued if it's set to a nonzero value.
371  void find_eigenvalues(const ComplexMatrixBase& A,
372  const ComplexMatrixBase& M,
373  Vector<std::complex<double>>& eigenvalue,
374  Vector<Vector<std::complex<double>>>& eigenvector);
375 
376 
377  /// Access to tolerance for checking complex conjugateness of eigenvalues
378  /// (const version)
380  {
382  }
383 
384 
385  /// Access to tolerance for checking complex conjugateness of eigenvalues
387  {
389  }
390 
391  private:
392  /// Helper function called from legacy and updated version from "raw" lapack
393  /// code
394  void solve_eigenproblem_helper(Problem* const& problem_pt,
395  const int& n_eval,
396  Vector<std::complex<double>>& alpha,
397  Vector<double>& beta,
398  Vector<DoubleVector>& eigenvector);
399 
400 
401  /// Provide diagonstic for DGGEV error return
402  void DGGEV_error(const int& info, const int& n)
403  {
404  // Throw an error
405  std::ostringstream error_stream;
406  error_stream << "Failure in LAPACK_DGGEV(...).\n"
407  << "info = " << info << std::endl;
408  error_stream
409  << "Diagnostics below are from \n\n"
410  << "http://www.netlib.org/lapack/explore-html/d9/d8e/"
411  "group__double_g_eeigen_ga4f59e87e670a755b41cbdd7e97f36bea.html"
412  << std::endl;
413  if (info < 0)
414  {
415  error_stream << -info << "-th input arg had an illegal value\n";
416  }
417  else if (info <= n)
418  {
419  error_stream << "The QZ iteration failed. No eigenvectors have been\n"
420  << "calculated, but ALPHAR(j), ALPHAI(j), and BETA(j)\n"
421  << "should be correct for j=INFO+1,...,N, where \n"
422  << "info = " << info << " and N = " << n << std::endl;
423  }
424  else if (info == (n + 1))
425  {
426  error_stream << "QZ iteration failed in DHGEQZ.\n";
427  }
428  else if (info == (n + 2))
429  {
430  error_stream << "error return from DTGEVC.\n";
431  }
432  error_stream
433  << "Aborting here; if you know how to proceed then\n"
434  << "then implement ability to catch this error and continue\n";
435 
436  throw OomphLibError(
437  error_stream.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
438  }
439 
440  /// Provide diagonstic for ZGGEV error return
441  void ZGGEV_error(const int& info, const int& n)
442  {
443  // Throw an error
444  std::ostringstream error_stream;
445  error_stream << "Failure in LAPACK_ZGGEV(...).\n"
446  << "info = " << info << std::endl;
447  error_stream << "Diagnostics below are from \n\n"
448  << "http://www.netlib.org/lapack/explore-html/"
449  << "db/d55/group__complex16_g_eeigen_ga79fcce20c"
450  << "617429ccf985e6f123a6171.html" << std::endl;
451  if (info < 0)
452  {
453  error_stream << -info << "-th input arg had an illegal value\n";
454  }
455  else if (info <= n)
456  {
457  error_stream << "The QZ iteration failed. No eigenvectors have been\n"
458  << "calculated, but ALPHAR(j), ALPHAI(j), and BETA(j)\n"
459  << "should be correct for j=INFO+1,...,N, where \n"
460  << "info = " << info << " and N = " << n << std::endl;
461  }
462  else if (info == (n + 1))
463  {
464  error_stream << "QZ iteration failed in ZHGEQZ.\n";
465  }
466  else if (info == (n + 2))
467  {
468  error_stream << "error return from ZTGEVC.\n";
469  }
470  error_stream
471  << "Aborting here; if you know how to proceed then\n"
472  << "then implement ability to catch this error and continue\n";
473 
474  throw OomphLibError(
475  error_stream.str(), OOMPH_CURRENT_FUNCTION, OOMPH_EXCEPTION_LOCATION);
476  }
477 
478  /// Tolerance for checking complex conjugateness of eigenvalues
480  };
481 
482 } // namespace oomph
483 
484 #endif
cstr elem_len * i
Definition: cfortran.h:603
Class for the ARPACK eigensolver.
Definition: eigen_solver.h:176
ARPACK(const ARPACK &)
Empty copy constructor.
Definition: eigen_solver.h:205
void get_eigenvalues_right_of_shift()
Set the desired eigenvalues to be right of the shift.
Definition: eigen_solver.h:273
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 track_eigenvalue_imaginary_part()
Set the imaginary part fo the quantity of interest.
Definition: eigen_solver.h:285
LinearSolver *const & linear_solver_pt() const
Return a pointer to the linear solver object (const version)
Definition: eigen_solver.h:303
const int & narnoldi() const
Access function for the number of Arnoldi vectors (const version)
Definition: eigen_solver.h:217
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
int & narnoldi()
Access function for the number of Arnoldi vectors.
Definition: eigen_solver.h:211
void get_eigenvalues_left_of_shift()
Set the desired eigenvalues to be left of the shift.
Definition: eigen_solver.h:267
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
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....
Definition: eigen_solver.h:254
void track_eigenvalue_magnitude()
Set the magnitude to be the quantity of interest.
Definition: eigen_solver.h:291
void enable_compute_eigenvectors()
Set to enable the computation of the eigenvectors (default)
Definition: eigen_solver.h:223
LinearSolver *& linear_solver_pt()
Return a pointer to the linear solver object.
Definition: eigen_solver.h:297
bool Small
Boolean to set which part of the spectrum left (default) or right of the shifted value.
Definition: eigen_solver.h:194
void track_eigenvalue_real_part()
Set the real part to be the quantity of interest (default)
Definition: eigen_solver.h:279
LinearSolver * Default_linear_solver_pt
Pointer to a default linear solver.
Definition: eigen_solver.h:182
void disable_compute_eigenvectors()
Set to disable the computation of the eigenvectors.
Definition: eigen_solver.h:229
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
Abstract base class for matrices of complex doubles – adds abstract interfaces for solving,...
Base class for any linear algebra object that is distributable. Just contains storage for the LinearA...
Base class for all EigenProblem solves. This simply defines standard interfaces so that different sol...
Definition: eigen_solver.h:61
virtual ~EigenSolver()
Empty destructor.
Definition: eigen_solver.h:75
EigenSolver(const EigenSolver &)
Empty copy constructor.
Definition: eigen_solver.h:72
EigenSolver()
Empty constructor.
Definition: eigen_solver.h:69
void set_shift(const double &shift_value)
Set the value of the (real) shift.
Definition: eigen_solver.h:155
virtual 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)=0
Solve the real eigenproblem that is assembled by elements in a mesh in a Problem object....
double Sigma_real
Double value that represents the real part of the shift in shifted eigensolvers.
Definition: eigen_solver.h:65
virtual void solve_eigenproblem(Problem *const &problem_pt, const int &n_eval, Vector< std::complex< double >> &eigenvalue, 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....
Definition: eigen_solver.h:106
const double & get_shift() const
Return the value of the (real) shift (const version)
Definition: eigen_solver.h:161
virtual 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)=0
Eigensolver. This takes a pointer to a problem and returns a vector of complex numbers representing t...
Class for the LAPACK QZ eigensolver.
Definition: eigen_solver.h:318
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...
double tolerance_for_ccness_check() const
Access to tolerance for checking complex conjugateness of eigenvalues (const version)
Definition: eigen_solver.h:379
void operator=(const LAPACK_QZ &)=delete
Broken assignment operator.
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
virtual ~LAPACK_QZ()
Empty desctructor.
Definition: eigen_solver.h:330
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...
LAPACK_QZ()
Empty constructor.
Definition: eigen_solver.h:321
LAPACK_QZ(const LAPACK_QZ &)=delete
Broken copy constructor.
double & tolerance_for_ccness_check()
Access to tolerance for checking complex conjugateness of eigenvalues.
Definition: eigen_solver.h:386
Base class for all linear solvers. This merely defines standard interfaces for linear solvers,...
Definition: linear_solver.h:68
An OomphLibError object which should be thrown when an run-time error is encountered....
////////////////////////////////////////////////////////////////// //////////////////////////////////...
Definition: problem.h:151
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...