ENH: improve analytical eigendecompositions

- `tensor` and `tensor2D` returns complex eigenvalues/vectors
  - `symmTensor` and `symmTensor2D` returns real eigenvalues/vectors
  - adds new test routines for eigendecompositions
  - improves numerical stability by:
    - using new robust algorithms,
    - reordering the conditional branches in root-type selection
This commit is contained in:
Kutalmis Bercin
2020-02-11 16:22:09 +00:00
committed by Andrew Heather
parent 6a53794e0a
commit 55e7da670c
28 changed files with 1411 additions and 284 deletions

View File

@ -6,6 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2017 OpenFOAM Foundation
Copyright (C) 2020 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -73,7 +74,7 @@ const Foam::tensor Foam::tensor::I
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
Foam::vector Foam::eigenValues(const tensor& t)
Foam::Vector<Foam::complex> Foam::eigenValues(const tensor& t)
{
// Coefficients of the characteristic cubic polynomial (a = 1)
const scalar b =
@ -86,117 +87,147 @@ Foam::vector Foam::eigenValues(const tensor& t)
- t.xy()*t.yz()*t.zx() - t.xz()*t.zy()*t.yx()
+ t.xx()*t.yz()*t.zy() + t.yy()*t.zx()*t.xz() + t.zz()*t.xy()*t.yx();
// Solve
Roots<3> roots = cubicEqn(1, b, c, d).roots();
// Determine the roots of the characteristic cubic polynomial
const Roots<3> roots(cubicEqn(1, b, c, d).roots());
// Check the root types
vector lambda = vector::zero;
bool isComplex = false;
forAll(roots, i)
{
switch (roots.type(i))
{
case roots::real:
lambda[i] = roots[i];
break;
case roots::complex:
WarningInFunction
<< "Complex eigenvalues detected for tensor: " << t
<< endl;
lambda[i] = 0;
isComplex = true;
break;
case roots::posInf:
lambda[i] = VGREAT;
break;
case roots::negInf:
lambda[i] = - VGREAT;
break;
case roots::nan:
FatalErrorInFunction
<< "Eigenvalue calculation failed for tensor: " << t
<< "Eigenvalue computation fails for tensor: " << t
<< "due to the not-a-number root = " << roots[i]
<< exit(FatalError);
case roots::real:
break;
}
}
// Sort the eigenvalues into ascending order
if (lambda.x() > lambda.y())
if (isComplex)
{
Swap(lambda.x(), lambda.y());
}
if (lambda.y() > lambda.z())
{
Swap(lambda.y(), lambda.z());
}
if (lambda.x() > lambda.y())
{
Swap(lambda.x(), lambda.y());
return
Vector<complex>
(
complex(roots[0], 0),
complex(roots[1], roots[2]),
complex(roots[1], -roots[2])
);
}
return lambda;
return
Vector<complex>
(
complex(roots[0], 0),
complex(roots[1], 0),
complex(roots[2], 0)
);
}
Foam::vector Foam::eigenVector
Foam::Vector<Foam::complex> Foam::eigenVector
(
const tensor& T,
const scalar lambda,
const vector& direction1,
const vector& direction2
const complex& eVal,
const Vector<complex>& standardBasis1,
const Vector<complex>& standardBasis2
)
{
// Construct the linear system for this eigenvalue
tensor A(T - lambda*I);
// Construct the characteristic equation system for this eigenvalue
Tensor<complex> A(Zero);
forAll(A, i)
{
A[i] = complex(T[i], 0);
}
A.xx() -= eVal;
A.yy() -= eVal;
A.zz() -= eVal;
// Determinants of the 2x2 sub-matrices used to find the eigenvectors
scalar sd0, sd1, sd2;
scalar magSd0, magSd1, magSd2;
// Sub-determinants for a unique eivenvalue
sd0 = A.yy()*A.zz() - A.yz()*A.zy();
sd1 = A.zz()*A.xx() - A.zx()*A.xz();
sd2 = A.xx()*A.yy() - A.xy()*A.yx();
magSd0 = mag(sd0);
magSd1 = mag(sd1);
magSd2 = mag(sd2);
// Sub-determinants for a unique eigenvenvalue
complex sd0 = A.yy()*A.zz() - A.yz()*A.zy();
complex sd1 = A.zz()*A.xx() - A.zx()*A.xz();
complex sd2 = A.xx()*A.yy() - A.xy()*A.yx();
scalar magSd0 = mag(sd0);
scalar magSd1 = mag(sd1);
scalar magSd2 = mag(sd2);
// Evaluate the eigenvector using the largest sub-determinant
if (magSd0 >= magSd1 && magSd0 >= magSd2 && magSd0 > SMALL)
{
vector ev
const Vector<complex> eVec
(
1,
complex(1, 0),
(A.yz()*A.zx() - A.zz()*A.yx())/sd0,
(A.zy()*A.yx() - A.yy()*A.zx())/sd0
);
return ev/mag(ev);
#ifdef FULLDEBUG
if (mag(eVec) < SMALL)
{
FatalErrorInFunction
<< "Eigenvector magnitude should be non-zero:"
<< "mag(eigenvector) = " << mag(eVec)
<< abort(FatalError);
}
#endif
return eVec/mag(eVec);
}
else if (magSd1 >= magSd2 && magSd1 > SMALL)
{
vector ev
const Vector<complex> eVec
(
(A.xz()*A.zy() - A.zz()*A.xy())/sd1,
1,
complex(1, 0),
(A.zx()*A.xy() - A.xx()*A.zy())/sd1
);
return ev/mag(ev);
#ifdef FULLDEBUG
if (mag(eVec) < SMALL)
{
FatalErrorInFunction
<< "Eigenvector magnitude should be non-zero:"
<< "mag(eigenvector) = " << mag(eVec)
<< abort(FatalError);
}
#endif
return eVec/mag(eVec);
}
else if (magSd2 > SMALL)
{
vector ev
const Vector<complex> eVec
(
(A.xy()*A.yz() - A.yy()*A.xz())/sd2,
(A.yx()*A.xz() - A.xx()*A.yz())/sd2,
1
complex(1, 0)
);
return ev/mag(ev);
#ifdef FULLDEBUG
if (mag(eVec) < SMALL)
{
FatalErrorInFunction
<< "Eigenvector magnitude should be non-zero:"
<< "mag(eigenvector) = " << mag(eVec)
<< abort(FatalError);
}
#endif
return eVec/mag(eVec);
}
// Sub-determinants for a repeated eigenvalue
sd0 = A.yy()*direction1.z() - A.yz()*direction1.y();
sd1 = A.zz()*direction1.x() - A.zx()*direction1.z();
sd2 = A.xx()*direction1.y() - A.xy()*direction1.x();
sd0 = A.yy()*standardBasis1.z() - A.yz()*standardBasis1.y();
sd1 = A.zz()*standardBasis1.x() - A.zx()*standardBasis1.z();
sd2 = A.xx()*standardBasis1.y() - A.xy()*standardBasis1.x();
magSd0 = mag(sd0);
magSd1 = mag(sd1);
magSd2 = mag(sd2);
@ -204,90 +235,96 @@ Foam::vector Foam::eigenVector
// Evaluate the eigenvector using the largest sub-determinant
if (magSd0 >= magSd1 && magSd0 >= magSd2 && magSd0 > SMALL)
{
vector ev
const Vector<complex> eVec
(
1,
(A.yz()*direction1.x() - direction1.z()*A.yx())/sd0,
(direction1.y()*A.yx() - A.yy()*direction1.x())/sd0
complex(1, 0),
(A.yz()*standardBasis1.x() - standardBasis1.z()*A.yx())/sd0,
(standardBasis1.y()*A.yx() - A.yy()*standardBasis1.x())/sd0
);
return ev/mag(ev);
#ifdef FULLDEBUG
if (mag(eVec) < SMALL)
{
FatalErrorInFunction
<< "Eigenvector magnitude should be non-zero:"
<< "mag(eigenvector) = " << mag(eVec)
<< abort(FatalError);
}
#endif
return eVec/mag(eVec);
}
else if (magSd1 >= magSd2 && magSd1 > SMALL)
{
vector ev
const Vector<complex> eVec
(
(direction1.z()*A.zy() - A.zz()*direction1.y())/sd1,
1,
(A.zx()*direction1.y() - direction1.x()*A.zy())/sd1
(standardBasis1.z()*A.zy() - A.zz()*standardBasis1.y())/sd1,
complex(1, 0),
(A.zx()*standardBasis1.y() - standardBasis1.x()*A.zy())/sd1
);
return ev/mag(ev);
#ifdef FULLDEBUG
if (mag(eVec) < SMALL)
{
FatalErrorInFunction
<< "Eigenvector magnitude should be non-zero:"
<< "mag(eigenvector) = " << mag(eVec)
<< abort(FatalError);
}
#endif
return eVec/mag(eVec);
}
else if (magSd2 > SMALL)
{
vector ev
const Vector<complex> eVec
(
(A.xy()*direction1.z() - direction1.y()*A.xz())/sd2,
(direction1.x()*A.xz() - A.xx()*direction1.z())/sd2,
1
(A.xy()*standardBasis1.z() - standardBasis1.y()*A.xz())/sd2,
(standardBasis1.x()*A.xz() - A.xx()*standardBasis1.z())/sd2,
complex(1, 0)
);
return ev/mag(ev);
#ifdef FULLDEBUG
if (mag(eVec) < SMALL)
{
FatalErrorInFunction
<< "Eigenvector magnitude should be non-zero:"
<< "mag(eigenvector) = " << mag(eVec)
<< abort(FatalError);
}
#endif
return eVec/mag(eVec);
}
// Triple eigenvalue
return direction1^direction2;
return standardBasis1^standardBasis2;
}
Foam::tensor Foam::eigenVectors(const tensor& T, const vector& lambdas)
{
vector Ux(1, 0, 0), Uy(0, 1, 0), Uz(0, 0, 1);
Ux = eigenVector(T, lambdas.x(), Uy, Uz);
Uy = eigenVector(T, lambdas.y(), Uz, Ux);
Uz = eigenVector(T, lambdas.z(), Ux, Uy);
return tensor(Ux, Uy, Uz);
}
Foam::tensor Foam::eigenVectors(const tensor& T)
{
const vector lambdas(eigenValues(T));
return eigenVectors(T, lambdas);
}
Foam::vector Foam::eigenValues(const symmTensor& T)
{
return eigenValues(tensor(T));
}
Foam::vector Foam::eigenVector
Foam::Tensor<Foam::complex> Foam::eigenVectors
(
const symmTensor& T,
const scalar lambda,
const vector& direction1,
const vector& direction2
const tensor& T,
const Vector<complex>& eVals
)
{
return eigenVector(tensor(T), lambda, direction1, direction2);
Vector<complex> Ux(complex(1, 0), Zero, Zero);
Vector<complex> Uy(Zero, complex(1, 0), Zero);
Vector<complex> Uz(Zero, Zero, complex(1, 0));
Ux = eigenVector(T, eVals.x(), Uy, Uz);
Uy = eigenVector(T, eVals.y(), Uz, Ux);
Uz = eigenVector(T, eVals.z(), Ux, Uy);
return Tensor<complex>(Ux, Uy, Uz);
}
Foam::tensor Foam::eigenVectors(const symmTensor& T, const vector& lambdas)
Foam::Tensor<Foam::complex> Foam::eigenVectors(const tensor& T)
{
return eigenVectors(tensor(T), lambdas);
}
const Vector<complex> eVals(eigenValues(T));
Foam::tensor Foam::eigenVectors(const symmTensor& T)
{
return eigenVectors(tensor(T));
return eigenVectors(T, eVals);
}

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2014 OpenFOAM Foundation
Copyright (C) 2019 OpenCFD Ltd.
Copyright (C) 2019-2020 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -28,7 +28,13 @@ Typedef
Foam::tensor
Description
Tensor of scalars.
Tensor of scalars, i.e. Tensor<scalar>.
Analytical functions for the computation of complex eigenvalues and
complex eigenvectors from a given tensor.
See also
Test-Tensor.C
SourceFiles
tensor.C
@ -42,6 +48,7 @@ SourceFiles
#include "vector.H"
#include "sphericalTensor.H"
#include "symmTensor.H"
#include "complex.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -52,27 +59,51 @@ namespace Foam
typedef Tensor<scalar> tensor;
vector eigenValues(const tensor& T);
vector eigenVector
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
//- Return complex eigenvalues of a given tensor
// \param T tensor
//
// \return Vector<complex> eigenvalues
Vector<complex> eigenValues(const tensor& T);
//- Return a complex eigenvector corresponding to
//- a given complex eigenvalue of a given tensor
// \param T tensor
// \param eVal complex eigenvalue
// \param standardBasis1 tensor orthogonal component 1
// \param standardBasis2 tensor orthogonal component 2
//
// \return Vector<complex> eigenvector
Vector<complex> eigenVector
(
const tensor& T,
const scalar lambda,
const vector& direction1,
const vector& direction2
const complex& eVal,
const Vector<complex>& standardBasis1,
const Vector<complex>& standardBasis2
);
tensor eigenVectors(const tensor& T, const vector& lambdas);
tensor eigenVectors(const tensor& T);
vector eigenValues(const symmTensor& T);
vector eigenVector
//- Return complex eigenvectors corresponding to
//- given complex eigenvalues of a given tensor
// \param T tensor
// \param eVals complex eigenvalues
//
// \return Tensor<complex> eigenvectors, each row is an eigenvector
Tensor<complex> eigenVectors
(
const symmTensor& T,
const scalar lambda,
const vector& direction1,
const vector& direction2
const tensor& T,
const Vector<complex>& eVals
);
tensor eigenVectors(const symmTensor& T, const vector& lambdas);
tensor eigenVectors(const symmTensor& T);
//- Return complex eigenvectors of a given tensor by computing
//- the complex eigenvalues of the tensor in the background
// \param T tensor
//
// \return Tensor<complex> complex eigenvectors, each row is an eigenvector
Tensor<complex> eigenVectors(const tensor& T);
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //