mirror of
https://develop.openfoam.com/Development/openfoam.git
synced 2025-11-28 03:28:01 +00:00
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:
committed by
Andrew Heather
parent
153f847ad2
commit
ef9ee7a8b1
3
applications/test/matrices/EigenMatrix/Make/files
Normal file
3
applications/test/matrices/EigenMatrix/Make/files
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Test-EigenMatrix.C
|
||||||
|
|
||||||
|
EXE = $(FOAM_USER_APPBIN)/Test-EigenMatrix
|
||||||
2
applications/test/matrices/EigenMatrix/Make/options
Normal file
2
applications/test/matrices/EigenMatrix/Make/options
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
EXE_INC = -I../../TestTools
|
||||||
|
/* EXE_LIBS = -lfiniteVolume */
|
||||||
556
applications/test/matrices/EigenMatrix/Test-EigenMatrix.C
Normal file
556
applications/test/matrices/EigenMatrix/Test-EigenMatrix.C
Normal 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;
|
||||||
|
}
|
||||||
1105
src/OpenFOAM/matrices/EigenMatrix/EigenMatrix.C
Normal file
1105
src/OpenFOAM/matrices/EigenMatrix/EigenMatrix.C
Normal file
File diff suppressed because it is too large
Load Diff
267
src/OpenFOAM/matrices/EigenMatrix/EigenMatrix.H
Normal file
267
src/OpenFOAM/matrices/EigenMatrix/EigenMatrix.H
Normal 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
|
||||||
|
|
||||||
|
// ************************************************************************* //
|
||||||
Reference in New Issue
Block a user