ENH: add iterative eigen decomposition solver, EigenMatrix

ENH: add Test-EigenMatrix application

  The new iterative eigen decomposition functionality is
  derived from:

    Passalacqua et al.'s OpenQBMM (openqbmm.org/),
    which is mostly derived from JAMA (math.nist.gov/javanumerics/jama/).
This commit is contained in:
Kutalmis Bercin
2020-03-03 17:35:39 +00:00
committed by Andrew Heather
parent 153f847ad2
commit ef9ee7a8b1
5 changed files with 1933 additions and 0 deletions

View File

@ -0,0 +1,3 @@
Test-EigenMatrix.C
EXE = $(FOAM_USER_APPBIN)/Test-EigenMatrix

View File

@ -0,0 +1,2 @@
EXE_INC = -I../../TestTools
/* EXE_LIBS = -lfiniteVolume */

View File

@ -0,0 +1,556 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2020 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is derivative work of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Application
Test-EigenMatrix
Description
Tests for \c EigenMatrix constructors, and member functions
using \c floatScalar, and \c doubleScalar base types.
Cross-checks were obtained from 'NumPy 1.15.1' if no theoretical
cross-check exists (like eigendecomposition relations), and
were hard-coded for elementwise comparisons.
\*---------------------------------------------------------------------------*/
#include "scalarMatrices.H"
#include "RectangularMatrix.H"
#include "SquareMatrix.H"
#include "complex.H"
#include "IOmanip.H"
#include "EigenMatrix.H"
#include "TestTools.H"
#include <algorithm>
using namespace Foam;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Return the absolute tolerance value for bitwise comparisons of floatScalars
floatScalar getTol(floatScalar)
{
return 1e-2;
}
// Return the absolute tolerance value for bitwise comparisons of doubleScalars
doubleScalar getTol(doubleScalar)
{
return 1e-10;
}
// Create each constructor of EigenMatrix<Type>, and print output
template<class Type>
void test_constructors(Type)
{
{
Info<< "# Construct from a SquareMatrix<Type>" << nl;
const SquareMatrix<Type> A(5, Zero);
const EigenMatrix<Type> EM(A);
}
{
Info<< "# Construct from a SquareMatrix<Type> and symmetry flag" << nl;
const SquareMatrix<Type> A(5, Zero);
const EigenMatrix<Type> EM(A, true);
}
}
// Execute each member function of EigenMatrix<Type>, and print output
template<class Type>
void test_member_funcs(Type)
{
SquareMatrix<Type> A(3, Zero);
assignMatrix
(
A,
{
Type(1), Type(2), Type(3),
Type(4), Type(5), Type(6),
Type(7), Type(8), Type(9)
}
);
Info<< "# Operand: " << nl
<< " SquareMatrix = " << A << endl;
// Since eigenvalues are unique and eigenvectors are not unique,
// the bitwise comparisons are limited to eigenvalue computations.
// Here, only the execution of the functions is tested, rather than
// the verification of the eigendecomposition through theoretical relations.
{
const EigenMatrix<Type> EM(A);
const DiagonalMatrix<Type> EValsRe = EM.EValsRe();
const DiagonalMatrix<Type>& EValsIm = EM.EValsIm();
const SquareMatrix<Type>& EVecs = EM.EVecs();
cmp
(
" Return real eigenvalues or real part of complex eigenvalues = ",
EValsRe,
List<Type>
({
Type(16.116844),
Type(-1.116844),
Type(0)
}),
getTol(Type(0)),
1e-6
);
cmp
(
" Return zero-matrix for real eigenvalues "
"or imaginary part of complex eigenvalues = ",
EValsIm,
List<Type>(3, Zero)
);
Info<< " Return eigenvectors matrix = " << EVecs << endl;
}
}
// Test the relation: "sum(eigenvalues) = trace(A)"
// w.wiki/4zs (Retrieved: 16-06-19) # Item-1
template<class Type>
void test_eigenvalues_sum
(
const SquareMatrix<Type>& A,
const DiagonalMatrix<Type>& EValsRe
)
{
const Type trace = A.trace();
// Imaginary part of complex conjugates cancel each other
const Type EValsSum = sum(EValsRe);
Info<< " # A.mRows = " << A.m() << nl;
cmp
(
" # sum(eigenvalues) = trace(A) = ",
EValsSum,
trace,
getTol(Type(0))
);
}
// Test the relation: "prod(eigenvalues) = det(A)"
// w.wiki/4zs (Retrieved: 16-06-19) # Item-2
// Note that the determinant computation may fail
// which is not a suggestion that eigendecomposition fails
template<class Type>
void test_eigenvalues_prod
(
const SquareMatrix<Type>& A,
const DiagonalMatrix<Type>& EValsRe,
const DiagonalMatrix<Type>& EValsIm
)
{
const Type determinant = mag(det(A));
Type EValsProd = Type(1);
if (EValsIm.empty())
{
for (label i = 0; i < EValsRe.size(); ++i)
{
EValsProd *= Foam::sqrt(sqr(EValsRe[i]));
}
}
else
{
for (label i = 0; i < EValsRe.size(); ++i)
{
EValsProd *= Foam::sqrt(sqr(EValsRe[i]) + sqr(EValsIm[i]));
}
}
cmp
(
" # prod(eigenvalues) = det(A) = ",
EValsProd,
determinant,
getTol(Type(0))
);
}
// Test eigenvalues in eigendecomposition relations
// Relations: (Beauregard & Fraleigh (1973), ISBN 0-395-14017-X, p. 307)
template<class Type>
void test_eigenvalues(Type)
{
Random rndGen(1234);
const label numberOfTests = 20;
// Non-symmetric
for (label i = 0; i < numberOfTests; ++i)
{
const label mRows = rndGen.position(100, 200);
const labelPair m(mRows, mRows);
const SquareMatrix<Type> A
(
makeRandomMatrix<SquareMatrix<Type>>(m, rndGen)
);
const EigenMatrix<Type> EM(A);
const DiagonalMatrix<Type>& EValsRe = EM.EValsRe();
test_eigenvalues_sum(A, EValsRe);
// LUDecompose does not work with floatScalar at the time of writing,
// hence det function. Once LUDecompose is refactored, comment out below
// const DiagonalMatrix<Type>& EValsIm = EM.EValsIm();
// test_eigenvalues_prod(A, EValsRe, EValsIm);
}
// Symmetric
for (label i = 0; i < numberOfTests; ++i)
{
const label mRows = rndGen.position(100, 200);
const labelPair m(mRows, mRows);
SquareMatrix<Type> A
(
makeRandomMatrix<SquareMatrix<Type>>(m, rndGen)
);
// Symmetrise with noise
for (label n = 0; n < A.n() - 1; ++n)
{
for (label m = A.m() - 1; m > n; --m)
{
A(n, m) = A(m, n) + SMALL;
}
}
const bool symmetric = true;
const EigenMatrix<Type> EM(A, symmetric);
const DiagonalMatrix<Type>& EValsRe = EM.EValsRe();
test_eigenvalues_sum(A, EValsRe);
}
}
// Test the relation: "(A & EVec - EVal*EVec) = 0"
template<class Type>
void test_characteristic_eq
(
const SquareMatrix<Type>& Aorig,
const DiagonalMatrix<Type>& EValsRe,
const DiagonalMatrix<Type>& EValsIm,
const SquareMatrix<complex>& EVecs
)
{
SquareMatrix<complex> A(Aorig.m());
auto convertToComplex = [&](const scalar& val) { return complex(val); };
std::transform
(
Aorig.cbegin(),
Aorig.cend(),
A.begin(),
convertToComplex
);
for (label i = 0; i < A.m(); ++i)
{
const RectangularMatrix<complex>& EVec(EVecs.subColumn(i));
const complex EVal(EValsRe[i], EValsIm[i]);
const RectangularMatrix<complex> leftSide(A*EVec);
const RectangularMatrix<complex> rightSide(EVal*EVec);
cmp
(
" # (A & EVec - EVal*EVec) = 0:",
flt(leftSide),
flt(rightSide),
getTol(Type(0))
);
}
}
// Test eigenvectors in eigendecomposition relations
template<class Type>
void test_eigenvectors(Type)
{
Random rndGen(1234);
const label numberOfTests = 20;
// Non-symmetric
for (label i = 0; i < numberOfTests; ++i)
{
const label mRows = rndGen.position(100, 200);
const labelPair m(mRows, mRows);
const SquareMatrix<Type> A
(
makeRandomMatrix<SquareMatrix<Type>>(m, rndGen)
);
const EigenMatrix<Type> EM(A);
const DiagonalMatrix<Type>& EValsRe = EM.EValsRe();
const DiagonalMatrix<Type>& EValsIm = EM.EValsIm();
const SquareMatrix<complex> EVecs(EM.complexEVecs());
test_characteristic_eq(A, EValsRe, EValsIm, EVecs);
}
// Symmetric
for (label i = 0; i < numberOfTests; ++i)
{
const label mRows = rndGen.position(100, 200);
const labelPair m(mRows, mRows);
SquareMatrix<Type> A
(
makeRandomMatrix<SquareMatrix<Type>>(m, rndGen)
);
// Symmetrise with noise
for (label n = 0; n < A.n() - 1; ++n)
{
for (label m = A.m() - 1; m > n; --m)
{
A(n, m) = A(m, n) + SMALL;
}
}
const bool symmetric = true;
const EigenMatrix<Type> EM(A, symmetric);
const DiagonalMatrix<Type>& EValsRe = EM.EValsRe();
const DiagonalMatrix<Type>& EValsIm = EM.EValsIm();
const SquareMatrix<complex> EVecs(EM.complexEVecs());
test_characteristic_eq(A, EValsRe, EValsIm, EVecs);
}
}
// Do compile-time recursion over the given types
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
run_tests(const std::tuple<Tp...>& types, const List<word>& typeID){}
template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
run_tests(const std::tuple<Tp...>& types, const List<word>& typeID)
{
Info<< nl << " ## Test constructors: "<< typeID[I] <<" ##" << nl;
test_constructors(std::get<I>(types));
Info<< nl << " ## Test member functions: "<< typeID[I] <<" ##" << nl;
test_member_funcs(std::get<I>(types));
Info<< nl << " ## Test eigenvalues: "<< typeID[I] <<" ##" << nl;
test_eigenvalues(std::get<I>(types));
Info<< nl << " ## Test eigenvectors: "<< typeID[I] <<" ##" << nl;
test_eigenvectors(std::get<I>(types));
run_tests<I + 1, Tp...>(types, typeID);
}
// * * * * * * * * * * * * * * * Main Program * * * * * * * * * * * * * * * //
int main()
{
Info<< setprecision(15);
const std::tuple<floatScalar, doubleScalar> types
(
std::make_tuple(Zero, Zero)
);
const List<word> typeID
({
"SquareMatrix<floatScalar>",
"SquareMatrix<doubleScalar>"
});
run_tests(types, typeID);
Info<< nl << " ## Test corner cases ##" << endl;
{
Info<< nl << " ## Rosser et al. (1951) matrix: ##" << nl;
// Rosser, J. B., Lanczos, C., Hestenes, M. R., & Karush, W. (1951).
// Separation of close eigenvalues of a real symmetric matrix.
// Jour. Research of the National Bureau of Standards, 47(4), 291-297.
// DOI:10.6028/jres.047.037
//
// 8x8 symmetric square matrix consisting of close real eigenvalues
// ibid, p. 294
// {
// 1020.04901843, 1020.000, 1019.90195136, 1000.000,
// 1000.000, 0.09804864072, 0.000, -1020.0490
// }
// Note that prod(eigenvalues) != determinant(A) for this matrix
// via the LAPACK routine z/dgetrf
SquareMatrix<doubleScalar> A(8, Zero);
assignMatrix
(
A,
{
611, 196, -192, 407, -8, -52, -49, 29,
196, 899, 113, -192, -71, -43, -8, -44,
-192, 113, 899, 196, 61, 49, 8, 52,
407, -192, 196, 611, 8, 44, 59, -23,
-8, -71, 61, 8, 411, -599, 208, 208,
-52, -43, 49, 44, -599, 411, 208, 208,
-49, -8, 8, 59, 208, 208, 99, -911,
29, -44, 52, -23, 208, 208, -911, 99
}
);
const EigenMatrix<doubleScalar> EM(A);
const DiagonalMatrix<doubleScalar>& EValsRe = EM.EValsRe();
const DiagonalMatrix<doubleScalar>& EValsIm = EM.EValsIm();
test_eigenvalues_sum(A, EValsRe);
cmp
(
" # Rosser et al. (1951) case, EValsRe = ",
EValsRe,
List<doubleScalar> // theoretical EValsRe
({
-1020.0490, 0.000, 0.09804864072, 1000.000,
1000.000, 1019.90195136, 1020.000, 1020.04901843
}),
1e-3
);
cmp
(
" # Rosser et al. (1951) case, EValsIm = ",
EValsIm,
List<doubleScalar>(8, Zero)
);
}
{
Info<< nl << " ## Test eigenvector unpacking: ##" << nl;
SquareMatrix<doubleScalar> A(3, Zero);
assignMatrix
(
A,
{
1, 2, 3,
-4, -5, 6,
7, -8, 9
}
);
const EigenMatrix<doubleScalar> EM(A);
const SquareMatrix<complex> complexEVecs(EM.complexEVecs());
SquareMatrix<complex> B(3, Zero);
assignMatrix
(
B,
{
complex(-0.373220280),
complex(0.417439996, 0.642691344),
complex(0.417439996, -0.642691344),
complex(-0.263919251),
complex(-1.165275867, 0.685068715),
complex(-1.165275867, -0.685068715),
complex(-0.889411744),
complex(-0.89990601, -0.3672785281),
complex(-0.89990601, 0.3672785281),
}
);
cmp
(
" # ",
flt(complexEVecs),
flt(B)
);
}
{
Info<< nl << " ## Test matrices with small values: ##" << nl;
const List<doubleScalar> epsilons
({
0, SMALL, Foam::sqrt(SMALL), sqr(SMALL), Foam::cbrt(SMALL),
-SMALL, -Foam::sqrt(SMALL), -sqr(SMALL), -Foam::cbrt(SMALL)
});
Random rndGen(1234);
const label numberOfTests = 20;
for (label i = 0; i < numberOfTests; ++i)
{
const label mRows = rndGen.position(100, 200);
for (const auto& eps : epsilons)
{
const SquareMatrix<doubleScalar> A(mRows, eps);
const EigenMatrix<doubleScalar> EM(A);
const DiagonalMatrix<doubleScalar>& EValsRe = EM.EValsRe();
const DiagonalMatrix<doubleScalar>& EValsIm = EM.EValsIm();
const SquareMatrix<complex> EVecs(EM.complexEVecs());
test_eigenvalues_sum(A, EValsRe);
test_characteristic_eq(A, EValsRe, EValsIm, EVecs);
}
}
}
{
Info<< nl << " ## Test matrices with repeating eigenvalues: ##" << nl;
SquareMatrix<doubleScalar> A(3, Zero);
assignMatrix
(
A,
{
0, 1, 1,
1, 0, 1,
1, 1, 0
}
);
const EigenMatrix<doubleScalar> EM(A);
const DiagonalMatrix<doubleScalar>& EValsRe = EM.EValsRe();
const DiagonalMatrix<doubleScalar>& EValsIm = EM.EValsIm();
const SquareMatrix<complex> EVecs(EM.complexEVecs());
test_eigenvalues_sum(A, EValsRe);
test_characteristic_eq(A, EValsRe, EValsIm, EVecs);
}
if (nFail_)
{
Info<< nl << " #### "
<< "Failed in " << nFail_ << " tests "
<< "out of total " << nTest_ << " tests "
<< "####\n" << endl;
return 1;
}
Info<< nl << " #### Passed all " << nTest_ <<" tests ####\n" << endl;
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,267 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Code created 2014-2018 by Alberto Passalacqua
Contributed 2018-07-31 to the OpenFOAM Foundation
Copyright (C) 2018 OpenFOAM Foundation
Copyright (C) 2019-2020 Alberto Passalacqua
Copyright (C) 2020 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Class
Foam::EigenMatrix
Description
EigenMatrix (i.e. eigendecomposition or spectral decomposition) decomposes
a diagonalisable nonsymmetric real square matrix into its canonical form,
whereby the matrix is represented in terms of its eigenvalues and
eigenvectors.
The eigenvalue equation (i.e. eigenvalue problem) is:
\f[
A v = \lambda v
\f]
where
\vartable
A | a diagonalisable square matrix of dimension m-by-m
v | a (non-zero) vector of dimension m (right eigenvector)
\lambda | a scalar corresponding to v (eigenvalue)
\endvartable
If \c A is symmetric, the following relation is satisfied:
\f[
A = v*D*v^T
\f]
where
\vartable
D | diagonal real eigenvalue matrix
v | orthogonal eigenvector matrix
\endvartable
If \c A is not symmetric, \c D becomes a block diagonal matrix wherein
the real eigenvalues are present on the diagonal within 1-by-1 blocks, and
complex eigenvalues within 2-by-2 blocks, i.e. \f$\lambda + i \mu\f$ with
\f$[\lambda, \mu; -\mu, \lambda]\f$.
The columns of \c v represent eigenvectors corresponding to eigenvalues,
satisfying the eigenvalue equation. Even though eigenvalues of a matrix
are unique, eigenvectors of the matrix are not. For the same eigenvalue,
the corresponding eigenvector can be real or complex with non-unique
entries. In addition, the validity of the equation \f$A = v*D*v^T\f$
depends on the condition number of \c v, which can be ill-conditioned,
or singular for invalidated equations.
References:
\verbatim
OpenFOAM-compatible implementation:
Passalacqua, A., Heylmun, J., Icardi, M.,
Madadi, E., Bachant, P., & Hu, X. (2019).
OpenQBMM 5.0.1 for OpenFOAM 7, Zenodo.
DOI:10.5281/zenodo.3471804
Implementations for the functions:
'tridiagonaliseSymmMatrix', 'symmTridiagonalQL',
'Hessenberg' and 'realSchur' (based on ALGOL-procedure:tred2):
Wilkinson, J. H., & Reinsch, C. (1971).
In Bauer, F. L. & Householder A. S. (Eds.),
Handbook for Automatic Computation: Volume II: Linear Algebra.
(Vol. 186), Springer-Verlag Berlin Heidelberg.
DOI: 10.1007/978-3-642-86940-2
Explanations on how real eigenvectors
can be unpacked into complex domain:
Moler, C. (1998).
Re: Eigenvectors.
Retrieved from https://bit.ly/3ao4Wat
TNT/JAMA implementation:
Pozo, R. (1997).
Template Numerical Toolkit for linear algebra:
High performance programming with C++
and the Standard Template Library.
The International Journal of Supercomputer Applications
and High Performance Computing, 11(3), 251-263.
DOI:10.1177/109434209701100307
(No particular order) Hicklin, J., Moler, C., Webb, P.,
Boisvert, R. F., Miller, B., Pozo, R., & Remington, K. (2012).
JAMA: A Java Matrix Package 1.0.3.
Retrived from https://math.nist.gov/javanumerics/jama/
\endverbatim
Note
- This implementation is an integration of the \c OpenQBMM \c eigenSolver
class (2019) without any changes to its internal mechanisms. Therefore, no
differences between EigenMatrix and \c eigenSolver (2019) classes should be
expected in terms of input-process-output operations.
- The \c OpenQBMM \c eigenSolver class derives almost completely from the
\c TNT/JAMA implementation, a public-domain library developed by \c NIST
and \c MathWorks from 1998 to 2012, available at
http://math.nist.gov/tnt/index.html (Retrieved June 6, 2020). Their
implementation was based upon \c EISPACK.
- The \c tridiagonaliseSymmMatrix, \c symmTridiagonalQL, \c Hessenberg and
\c realSchur methods are based on the \c Algol procedures \c tred2 by
Bowdler, Martin, Reinsch, and Wilkinson, Handbook for Auto. Comp.,
Vol. II-Linear Algebra, and the corresponding \c FORTRAN subroutine
in \c EISPACK.
See also
Test-EigenMatrix.C
SourceFiles
EigenMatrix.C
\*---------------------------------------------------------------------------*/
#ifndef EigenMatrix_H
#define EigenMatrix_H
#include "scalarMatrices.H"
#include "DiagonalMatrix.H"
#include "SquareMatrix.H"
#include "complex.H"
#include <algorithm>
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
namespace Foam
{
/*---------------------------------------------------------------------------*\
Class EigenMatrix Declaration
\*---------------------------------------------------------------------------*/
template<class cmptType>
class EigenMatrix
{
static_assert
(
std::is_floating_point<cmptType>::value,
"EigenMatrix operates only with scalar base type."
);
// Private Data
//- Number of rows and columns in input matrix
const label n_;
//- Real eigenvalues or real part of complex eigenvalues
DiagonalMatrix<cmptType> EValsRe_;
//- Zero-matrix for real eigenvalues
//- or imaginary part of complex eigenvalues
DiagonalMatrix<cmptType> EValsIm_;
//- Right eigenvectors matrix where each column is
//- a right eigenvector that corresponds to an eigenvalue
SquareMatrix<cmptType> EVecs_;
//- Copy of nonsymmetric input matrix evolving to eigenvectors matrix
SquareMatrix<cmptType> H_;
// Private Member Functions
//- Householder transform of a symmetric matrix to tri-diagonal form
void tridiagonaliseSymmMatrix();
//- Symmetric tri-diagonal QL algorithm
void symmTridiagonalQL();
//- Reduce non-symmetric matrix to upper-Hessenberg form
void Hessenberg();
//- Reduce matrix from Hessenberg to real Schur form
void realSchur();
public:
// Generated Methods
//- No default construct
EigenMatrix() = delete;
//- No copy construct
EigenMatrix(const EigenMatrix&) = delete;
//- No copy assignment
EigenMatrix& operator=(const EigenMatrix&) = delete;
// Constructors
//- Construct from a SquareMatrix<cmptType>
explicit EigenMatrix(const SquareMatrix<cmptType>& A);
//- Construct from a SquareMatrix<cmptType> and symmetry flag
// Does not perform symmetric check
EigenMatrix(const SquareMatrix<cmptType>& A, bool symmetric);
// Access
//- Return real eigenvalues or real part of complex eigenvalues
const DiagonalMatrix<cmptType>& EValsRe() const
{
return EValsRe_;
}
//- Return zero-matrix for real eigenvalues
//- or imaginary part of complex eigenvalues
const DiagonalMatrix<cmptType>& EValsIm() const
{
return EValsIm_;
}
//- Return right eigenvectors matrix where each column is
//- a right eigenvector that corresponds to an eigenvalue
const SquareMatrix<cmptType>& EVecs() const
{
return EVecs_;
}
//- Return right eigenvectors in unpacked form
const SquareMatrix<complex> complexEVecs() const;
};
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
} // End namespace Foam
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
#ifdef NoRepository
#include "EigenMatrix.C"
#endif
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
#endif
// ************************************************************************* //