Commit 0be6269a authored by Will Bainbridge's avatar Will Bainbridge

Added robust primitive cubic/quadratic/linear equation solutions.

Applied to eigen-value calculations. Fixed repeated-eigen-value issues
in eigen-vector generation.
parent 406a2c5d
Test-cubicEqn.C
EXE = $(FOAM_USER_APPBIN)/Test-cubicEqn
#include <ctime>
#include <random>
#include "cubicEqn.H"
#include "IOstreams.H"
#include "stringList.H"
using namespace Foam;
scalar randomScalar(const scalar min, const scalar max)
{
static_assert
(
sizeof(long) == sizeof(scalar),
"Scalar and long are not the same size"
);
static std::default_random_engine generator(std::time(0));
static std::uniform_int_distribution<long>
distribution
(
std::numeric_limits<long>::min(),
std::numeric_limits<long>::max()
);
scalar x;
do
{
long i = distribution(generator);
x = reinterpret_cast<scalar&>(i);
}
while (min > mag(x) || mag(x) > max || !std::isfinite(x));
return x;
};
template <class Type>
void test(const Type& polynomialEqn, const scalar tol)
{
Roots<Type::nComponents - 1> r = polynomialEqn.roots();
const scalar nan = std::numeric_limits<scalar>::quiet_NaN();
const scalar inf = std::numeric_limits<scalar>::infinity();
FixedList<label, Type::nComponents - 1> t;
FixedList<scalar, Type::nComponents - 1> v(nan);
FixedList<scalar, Type::nComponents - 1> e(nan);
bool ok = true;
forAll(r, i)
{
t[i] = r.type(i);
switch (t[i])
{
case roots::real:
v[i] = polynomialEqn.value(r[i]);
e[i] = polynomialEqn.error(r[i]);
ok = ok && mag(v[i]) < tol*mag(e[i]);
break;
case roots::posInf:
v[i] = + inf;
e[i] = nan;
break;
case roots::negInf:
v[i] = - inf;
e[i] = nan;
break;
default:
v[i] = e[i] = nan;
break;
}
}
if (!ok)
{
Info<< "Coeffs: " << polynomialEqn << endl
<< " Types: " << t << endl
<< " Roots: " << r << endl
<< "Values: " << v << endl
<< "Errors: " << e << endl << endl;
}
}
int main()
{
const int nTests = 1000000;
for (int t = 0; t < nTests; ++ t)
{
test
(
cubicEqn
(
randomScalar(1e-50, 1e+50),
randomScalar(1e-50, 1e+50),
randomScalar(1e-50, 1e+50),
randomScalar(1e-50, 1e+50)
),
100
);
}
Info << nTests << " cubics tested" << endl;
return 0;
}
...@@ -125,6 +125,9 @@ $(spatialVectorAlgebra)/SpatialVector/spatialVector/spatialVector.C ...@@ -125,6 +125,9 @@ $(spatialVectorAlgebra)/SpatialVector/spatialVector/spatialVector.C
$(spatialVectorAlgebra)/SpatialTensor/spatialTensor/spatialTensor.C $(spatialVectorAlgebra)/SpatialTensor/spatialTensor/spatialTensor.C
$(spatialVectorAlgebra)/CompactSpatialTensor/compactSpatialTensor/compactSpatialTensor.C $(spatialVectorAlgebra)/CompactSpatialTensor/compactSpatialTensor/compactSpatialTensor.C
primitives/polynomialEqns/cubicEqn/cubicEqn.C
primitives/polynomialEqns/quadraticEqn/quadraticEqn.C
containers/HashTables/HashTable/HashTableCore.C containers/HashTables/HashTable/HashTableCore.C
containers/HashTables/StaticHashTable/StaticHashTableCore.C containers/HashTables/StaticHashTable/StaticHashTableCore.C
containers/Lists/SortableList/ParSortableListName.C containers/Lists/SortableList/ParSortableListName.C
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
========= | ========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration | \\ / O peration |
\\ / A nd | Copyright (C) 2011-2016 OpenFOAM Foundation \\ / A nd | Copyright (C) 2011-2017 OpenFOAM Foundation
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
...@@ -24,6 +24,7 @@ License ...@@ -24,6 +24,7 @@ License
\*---------------------------------------------------------------------------*/ \*---------------------------------------------------------------------------*/
#include "tensor.H" #include "tensor.H"
#include "cubicEqn.H"
#include "mathematicalConstants.H" #include "mathematicalConstants.H"
using namespace Foam::constant::mathematical; using namespace Foam::constant::mathematical;
...@@ -72,137 +73,76 @@ const Foam::tensor Foam::tensor::I ...@@ -72,137 +73,76 @@ const Foam::tensor Foam::tensor::I
Foam::vector Foam::eigenValues(const tensor& t) Foam::vector Foam::eigenValues(const tensor& t)
{ {
// The eigenvalues // Coefficients of the characteristic cubic polynomial (a = 1)
scalar i, ii, iii; const scalar b =
- t.xx() - t.yy() - t.zz();
// diagonal matrix const scalar c =
if t.xx()*t.yy() + t.xx()*t.zz() + t.yy()*t.zz()
( - t.xy()*t.yx() - t.yz()*t.zy() - t.zx()*t.xz();
( const scalar d =
mag(t.xy()) + mag(t.xz()) + mag(t.yx()) - t.xx()*t.yy()*t.zz()
+ mag(t.yz()) + mag(t.zx()) + mag(t.zy()) - 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();
< SMALL
) // Solve
{ Roots<3> roots = cubicEqn(1, b, c, d).roots();
i = t.xx();
ii = t.yy(); // Check the root types
iii = t.zz(); vector lambda = vector::zero;
} forAll(roots, i)
// non-diagonal matrix
else
{ {
// Coefficients of the characteristic polynmial switch (roots.type(i))
// x^3 + a*x^2 + b*x + c = 0
scalar a =
- t.xx() - t.yy() - t.zz();
scalar b =
t.xx()*t.yy() + t.xx()*t.zz() + t.yy()*t.zz()
- t.xy()*t.yx() - t.yz()*t.zy() - t.zx()*t.xz();
scalar c =
- t.xx()*t.yy()*t.zz()
- 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();
// Auxillary variables
scalar aBy3 = a/3;
scalar P = (a*a - 3*b)/9; // == -p_wikipedia/3
scalar PPP = P*P*P;
scalar Q = (2*a*a*a - 9*a*b + 27*c)/54; // == q_wikipedia/2
scalar QQ = Q*Q;
// Three identical roots
if (mag(P) < SMALL && mag(Q) < SMALL)
{ {
return vector(- aBy3, - aBy3, - aBy3); case roots::real:
} lambda[i] = roots[i];
break;
// Two identical roots and one distinct root case roots::complex:
else if (mag(QQ) > SMALL && mag(PPP/QQ - 1) < SMALL) WarningInFunction
{ << "Complex eigenvalues detected for tensor: " << t
scalar sqrtP = sqrt(P); << endl;
scalar signQ = sign(Q); lambda[i] = 0;
break;
i = ii = signQ*sqrtP - aBy3; case roots::posInf:
iii = - 2*signQ*sqrtP - aBy3; lambda[i] = VGREAT;
} break;
case roots::negInf:
// Three distinct roots lambda[i] = - VGREAT;
else if (PPP > QQ) break;
{ case roots::nan:
scalar sqrtP = sqrt(P); FatalErrorInFunction
scalar value = cos(acos(Q/sqrt(PPP))/3); << "Eigenvalue calculation failed for tensor: " << t
scalar delta = sqrt(3 - 3*value*value); << exit(FatalError);
i = - 2*sqrtP*value - aBy3;
ii = sqrtP*(value + delta) - aBy3;
iii = sqrtP*(value - delta) - aBy3;
}
// One real root, two imaginary roots
// based on the above logic, PPP must be less than QQ
else
{
WarningInFunction
<< "complex eigenvalues detected for tensor: " << t
<< endl;
if (mag(P) < SMALL)
{
i = cbrt(QQ/2);
}
else
{
scalar w = cbrt(- Q - sqrt(QQ - PPP));
i = w + P/w - aBy3;
}
return vector(-VGREAT, i, VGREAT);
} }
} }
// Sort the eigenvalues into ascending order // Sort the eigenvalues into ascending order
if (i > ii) if (lambda.x() > lambda.y())
{ {
Swap(i, ii); Swap(lambda.x(), lambda.y());
} }
if (lambda.y() > lambda.z())
if (ii > iii)
{ {
Swap(ii, iii); Swap(lambda.y(), lambda.z());
} }
if (lambda.x() > lambda.y())
if (i > ii)
{ {
Swap(i, ii); Swap(lambda.x(), lambda.y());
} }
return vector(i, ii, iii); return lambda;
} }
Foam::vector Foam::eigenVector Foam::vector Foam::eigenVector
( (
const tensor& t, const tensor& T,
const scalar lambda const scalar lambda,
const vector& direction1,
const vector& direction2
) )
{ {
// Constantly rotating direction ensures different eigenvectors are
// generated when called sequentially with a multiple eigenvalue
static vector direction(1,0,0);
vector oldDirection(direction);
scalar temp = direction[2];
direction[2] = direction[1];
direction[1] = direction[0];
direction[0] = temp;
// Construct the linear system for this eigenvalue // Construct the linear system for this eigenvalue
tensor A(t - lambda*I); tensor A(T - lambda*I);
// Determinants of the 2x2 sub-matrices used to find the eigenvectors // Determinants of the 2x2 sub-matrices used to find the eigenvectors
scalar sd0, sd1, sd2; scalar sd0, sd1, sd2;
...@@ -252,9 +192,9 @@ Foam::vector Foam::eigenVector ...@@ -252,9 +192,9 @@ Foam::vector Foam::eigenVector
} }
// Sub-determinants for a repeated eigenvalue // Sub-determinants for a repeated eigenvalue
sd0 = A.yy()*direction.z() - A.yz()*direction.y(); sd0 = A.yy()*direction1.z() - A.yz()*direction1.y();
sd1 = A.zz()*direction.x() - A.zx()*direction.z(); sd1 = A.zz()*direction1.x() - A.zx()*direction1.z();
sd2 = A.xx()*direction.y() - A.xy()*direction.x(); sd2 = A.xx()*direction1.y() - A.xy()*direction1.x();
magSd0 = mag(sd0); magSd0 = mag(sd0);
magSd1 = mag(sd1); magSd1 = mag(sd1);
magSd2 = mag(sd2); magSd2 = mag(sd2);
...@@ -265,8 +205,8 @@ Foam::vector Foam::eigenVector ...@@ -265,8 +205,8 @@ Foam::vector Foam::eigenVector
vector ev vector ev
( (
1, 1,
(A.yz()*direction.x() - direction.z()*A.yx())/sd0, (A.yz()*direction1.x() - direction1.z()*A.yx())/sd0,
(direction.y()*A.yx() - A.yy()*direction.x())/sd0 (direction1.y()*A.yx() - A.yy()*direction1.x())/sd0
); );
return ev/mag(ev); return ev/mag(ev);
...@@ -275,9 +215,9 @@ Foam::vector Foam::eigenVector ...@@ -275,9 +215,9 @@ Foam::vector Foam::eigenVector
{ {
vector ev vector ev
( (
(direction.z()*A.zy() - A.zz()*direction.y())/sd1, (direction1.z()*A.zy() - A.zz()*direction1.y())/sd1,
1, 1,
(A.zx()*direction.y() - direction.x()*A.zy())/sd1 (A.zx()*direction1.y() - direction1.x()*A.zy())/sd1
); );
return ev/mag(ev); return ev/mag(ev);
...@@ -286,8 +226,8 @@ Foam::vector Foam::eigenVector ...@@ -286,8 +226,8 @@ Foam::vector Foam::eigenVector
{ {
vector ev vector ev
( (
(A.xy()*direction.z() - direction.y()*A.xz())/sd2, (A.xy()*direction1.z() - direction1.y()*A.xz())/sd2,
(direction.x()*A.xz() - A.xx()*direction.z())/sd2, (direction1.x()*A.xz() - A.xx()*direction1.z())/sd2,
1 1
); );
...@@ -295,40 +235,57 @@ Foam::vector Foam::eigenVector ...@@ -295,40 +235,57 @@ Foam::vector Foam::eigenVector
} }
// Triple eigenvalue // Triple eigenvalue
return oldDirection; return direction1^direction2;
}
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) Foam::tensor Foam::eigenVectors(const tensor& T)
{ {
vector evals(eigenValues(t)); const vector lambdas(eigenValues(T));
return eigenVectors(T, lambdas);
}
tensor evs
(
eigenVector(t, evals.x()),
eigenVector(t, evals.y()),
eigenVector(t, evals.z())
);
return evs; Foam::vector Foam::eigenValues(const symmTensor& T)
{
return eigenValues(tensor(T));
} }
Foam::vector Foam::eigenValues(const symmTensor& t) Foam::vector Foam::eigenVector
(
const symmTensor& T,
const scalar lambda,
const vector& direction1,
const vector& direction2
)
{ {
return eigenValues(tensor(t)); return eigenVector(tensor(T), lambda, direction1, direction2);
} }
Foam::vector Foam::eigenVector(const symmTensor& t, const scalar lambda) Foam::tensor Foam::eigenVectors(const symmTensor& T, const vector& lambdas)
{ {
return eigenVector(tensor(t), lambda); return eigenVectors(tensor(T), lambdas);
} }
Foam::tensor Foam::eigenVectors(const symmTensor& t) Foam::tensor Foam::eigenVectors(const symmTensor& T)
{ {
return eigenVectors(tensor(t)); return eigenVectors(tensor(T));
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
========= | ========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration | \\ / O peration |
\\ / A nd | Copyright (C) 2011 OpenFOAM Foundation \\ / A nd | Copyright (C) 2011-2017 OpenFOAM Foundation
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
...@@ -50,13 +50,27 @@ namespace Foam ...@@ -50,13 +50,27 @@ namespace Foam
typedef Tensor<scalar> tensor; typedef Tensor<scalar> tensor;
vector eigenValues(const tensor&); vector eigenValues(const tensor& T);
vector eigenVector(const tensor&, const scalar lambda); vector eigenVector
tensor eigenVectors(const tensor&); (
const tensor& T,
vector eigenValues(const symmTensor&); const scalar lambda,
vector eigenVector(const symmTensor&, const scalar lambda); const vector& direction1,
tensor eigenVectors(const symmTensor&); const vector& direction2
);
tensor eigenVectors(const tensor& T, const vector& lambdas);
tensor eigenVectors(const tensor& T);
vector eigenValues(const symmTensor& T);
vector eigenVector
(
const symmTensor& T,
const scalar lambda,
const vector& direction1,
const vector& direction2
);
tensor eigenVectors(const symmTensor& T, const vector& lambdas);
tensor eigenVectors(const symmTensor& T);
//- Data associated with tensor type are contiguous //- Data associated with tensor type are contiguous
template<> template<>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
========= | ========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration | \\ / O peration |
\\ / A nd | Copyright (C) 2011-2016 OpenFOAM Foundation \\ / A nd | Copyright (C) 2011-2017 OpenFOAM Foundation
\\/ M anipulation | \\/ M anipulation |
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
License License
...@@ -24,6 +24,7 @@ License ...@@ -24,6 +24,7 @@ License
\*---------------------------------------------------------------------------*/ \*---------------------------------------------------------------------------*/
#include "tensor2D.H" #include "tensor2D.H"
#include "quadraticEqn.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
...@@ -85,95 +86,96 @@ const Foam::tensor2D Foam::tensor2D::I ...@@ -85,95 +86,96 @@ const Foam::tensor2D Foam::tensor2D::I
Foam::vector2D Foam::eigenValues(const tensor2D& t) Foam::vector2D Foam::eigenValues(const tensor2D& t)
{ {
scalar i = 0; // Coefficients of the characteristic quadratic polynomial (a = 1)
scalar ii = 0; const scalar b = - t.xx() - t.yy();
const scalar c = t.xx()*t.yy() - t.xy()*t.yx();
if (mag(t.xy())