diff --git a/applications/test/DiagTensor/Make/files b/applications/test/DiagTensor/Make/files
new file mode 100644
index 0000000000..a3e36220b3
--- /dev/null
+++ b/applications/test/DiagTensor/Make/files
@@ -0,0 +1,3 @@
+Test-DiagTensor.C
+
+EXE = $(FOAM_USER_APPBIN)/Test-DiagTensor
diff --git a/applications/test/tensor/Make/options b/applications/test/DiagTensor/Make/options
similarity index 100%
rename from applications/test/tensor/Make/options
rename to applications/test/DiagTensor/Make/options
diff --git a/applications/test/DiagTensor/Test-DiagTensor.C b/applications/test/DiagTensor/Test-DiagTensor.C
new file mode 100644
index 0000000000..48b68be74c
--- /dev/null
+++ b/applications/test/DiagTensor/Test-DiagTensor.C
@@ -0,0 +1,439 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / 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 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 .
+
+Application
+ Test-DiagTensor
+
+Description
+ Tests for \c DiagTensor constructors, member functions and operators
+ using \c floatScalar, \c doubleScalar, and \c complex base types.
+
+ Cross-checks were obtained from 'NumPy 1.15.1' and 'SciPy 1.1.0' if no
+ theoretical cross-check exists (like eigendecomposition relations), and
+ were hard-coded for elementwise comparisons.
+
+ For \c complex base type, the cross-checks do only involve zero imag part.
+
+\*---------------------------------------------------------------------------*/
+
+#include "Tensor.H"
+#include "SymmTensor.H"
+#include "SphericalTensor.H"
+#include "DiagTensor.H"
+#include "floatScalar.H"
+#include "doubleScalar.H"
+#include "complex.H"
+
+using namespace Foam;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Total number of unit tests
+unsigned nTest_ = 0;
+
+
+// Total number of failed unit tests
+unsigned nFail_ = 0;
+
+
+// Compare two floating point types, and print output.
+// Do ++nFail_ if values of two objects are not equal within a given tolerance.
+// The function is converted from PEP-485.
+template
+typename std::enable_if
+<
+ std::is_same::value ||
+ std::is_same::value ||
+ std::is_same::value,
+ void
+>::type cmp
+(
+ const word& msg,
+ const Type& x,
+ const Type& y,
+ const scalar relTol = 1e-8, //
+typename std::enable_if
+<
+ !std::is_same::value &&
+ !std::is_same::value &&
+ !std::is_same::value,
+ void
+>::type cmp
+(
+ const word& msg,
+ const Type& x,
+ const Type& y,
+ const scalar relTol = 1e-8,
+ const scalar absTol = 0
+)
+{
+ Info<< msg << x << endl;
+
+ unsigned nFail = 0;
+
+ for (label i = 0; i < pTraits::nComponents; ++i)
+ {
+ if (max(absTol, relTol*max(mag(x[i]), mag(y[i]))) < mag(x[i] - y[i]))
+ {
+ ++nFail;
+ }
+ }
+
+ if (nFail)
+ {
+ Info<< nl
+ << " #### Fail in " << nFail << " comps ####" << nl << endl;
+ ++nFail_;
+ }
+ ++nTest_;
+}
+
+
+// Create each constructor of DiagTensor, and print output
+template
+void test_constructors(Type)
+{
+ {
+ Info<< "# Construct initialized to zero:" << nl;
+ const DiagTensor dT(Zero);
+ Info<< dT << endl;
+ }
+ {
+ Info<< "# Construct given VectorSpace of the same rank:" << nl;
+ const VectorSpace, Type, 3> V(Zero);
+ const DiagTensor dT(V);
+ Info<< dT << endl;
+ }
+ {
+ Info<< "# Construct given the three components:" << nl;
+ const DiagTensor dT
+ (
+ Type(1),
+ Type(5),
+ Type(-9)
+ );
+ Info<< dT << endl;
+ }
+ {
+ Info<< "# Copy construct:" << nl;
+ const DiagTensor dT(Zero);
+ const DiagTensor copydT(dT);
+ Info<< dT << tab << copydT << endl;
+ }
+}
+
+
+// Execute each member function of DiagTensor, and print output
+template
+void test_member_funcs(Type)
+{
+ DiagTensor dT(Type(1), Type(5), Type(-9));
+ const DiagTensor cdT(Type(-9), Type(5), Type(1));
+
+ Info<< "# Operand: " << nl
+ << " DiagTensor = " << dT << endl;
+
+
+ {
+ Info<< "# Component access:" << nl;
+
+ DiagTensor cpdT(dT.xx(), dT.yy(), dT.zz());
+ cmp(" 'DiagTensor' access:", dT, cpdT);
+
+ const DiagTensor cpcdT(cdT.xx(), cdT.yy(), cdT.zz());
+ cmp(" 'const DiagTensor' access:", cdT, cpcdT);
+ }
+}
+
+
+// Execute each global function of DiagTensor, and print output
+template
+void test_global_funcs(Type)
+{
+ const Tensor T
+ (
+ Type(-1), Type(2), Type(-3),
+ Type(4), Type(5), Type(-6),
+ Type(7), Type(8), Type(-9)
+ );
+ const SymmTensor sT
+ (
+ Type(-1), Type(2), Type(-3),
+ Type(5), Type(-6),
+ Type(-9)
+ );
+ const DiagTensor dT(Type(1), Type(5), Type(-9));
+
+ Info<< "# Operands: " << nl
+ << " Tensor = " << T << nl
+ << " SymmTensor = " << sT << nl
+ << " DiagTensor = " << dT << endl;
+
+
+ cmp(" Trace = ", tr(dT), Type(-3));
+ cmp(" Spherical part = ", sph(dT), SphericalTensor(tr(dT)/Type(3)));
+ cmp(" Determinant = ", det(dT), Type(-44.99999999999999));
+ cmp
+ (
+ " Inverse = ",
+ inv(dT),
+ DiagTensor(Type(1), Type(0.2), Type(-0.11111111))
+ );
+ cmp
+ (
+ " Diagonal of Tensor = ",
+ diag(T),
+ DiagTensor(Type(-1), Type(5), Type(-9))
+ );
+ cmp
+ (
+ " Diagonal of SymmTensor = ",
+ diag(sT),
+ DiagTensor(Type(-1), Type(5), Type(-9))
+ );
+}
+
+
+// Execute each global operator of DiagTensor, and print output
+template
+void test_global_opers(Type)
+{
+ const Tensor T
+ (
+ Type(-1), Type(2), Type(-3),
+ Type(4), Type(5), Type(-6),
+ Type(7), Type(8), Type(-9)
+ );
+ const SymmTensor sT
+ (
+ Type(-1), Type(2), Type(-3),
+ Type(5), Type(-6),
+ Type(-9)
+ );
+ const DiagTensor dT(Type(1), Type(5), Type(-9));
+ const SphericalTensor spT(Type(1));
+ const Vector v(Type(3), Type(2), Type(1));
+ const Type x(4);
+
+ Info<< "# Operands:" << nl
+ << " Tensor = " << T << nl
+ << " SymmTensor = " << sT << nl
+ << " DiagTensor = " << dT << nl
+ << " SphericalTensor = " << spT << nl
+ << " Vector = " << v << nl
+ << " Type = " << x << endl;
+
+
+ cmp
+ (
+ " Sum of DiagTensor-Tensor = ",
+ (dT + T),
+ Tensor
+ (
+ Type(0), Type(2), Type(-3),
+ Type(4), Type(10), Type(-6),
+ Type(7), Type(8), Type(-18)
+ )
+ );
+ cmp
+ (
+ " Sum of Tensor-DiagTensor = ",
+ (T + dT),
+ Tensor
+ (
+ Type(0), Type(2), Type(-3),
+ Type(4), Type(10), Type(-6),
+ Type(7), Type(8), Type(-18)
+ )
+ );
+ cmp
+ (
+ " Subtract Tensor from DiagTensor = ",
+ (dT - T),
+ Tensor
+ (
+ Type(2), Type(-2), Type(3),
+ Type(-4), Type(0), Type(6),
+ Type(-7), Type(-8), Type(0)
+ )
+ );
+ cmp
+ (
+ " Subtract DiagTensor from Tensor = ",
+ (T - dT),
+ Tensor
+ (
+ Type(-2), Type(2), Type(-3),
+ Type(4), Type(0), Type(-6),
+ Type(7), Type(8), Type(0)
+ )
+ );
+ cmp
+ (
+ " Division of Type by DiagTensor = ",
+ (x/dT),
+ DiagTensor(Type(4), Type(0.8), Type(-0.44444444))
+ );
+ cmp
+ (
+ " Division of DiagTensor by Type = ",
+ (dT/x),
+ DiagTensor(Type(0.25), Type(1.25), Type(-2.25))
+ );
+ cmp
+ (
+ " Division of Vector by DiagTensor = ",
+ (v/dT),
+ Vector(Type(3), Type(0.4), Type(-0.11111111))
+ );
+ cmp
+ (
+ " Inner-product of DiagTensor-DiagTensor = ",
+ (dT & dT),
+ DiagTensor(Type(1), Type(25), Type(81))
+ );
+ cmp
+ (
+ " Inner-product of DiagTensor-Tensor = ",
+ (dT & T),
+ Tensor
+ (
+ Type(-1), Type(2), Type(-3),
+ Type(20), Type(25), Type(-30),
+ Type(-63), Type(-72), Type(81)
+ )
+ );
+ cmp
+ (
+ " Inner-product of Tensor-DiagTensor = ",
+ (T & dT),
+ Tensor
+ (
+ Type(-1), Type(10), Type(27),
+ Type(4), Type(25), Type(54),
+ Type(7), Type(40), Type(81)
+ )
+ );
+ cmp
+ (
+ " Inner-product of DiagTensor-Vector = ",
+ (dT & v),
+ Vector(Type(3), Type(10), Type(-9))
+ );
+ cmp
+ (
+ " Inner-product of Vector-DiagTensor = ",
+ (v & dT),
+ Vector(Type(3), Type(10), Type(-9))
+ );
+}
+
+
+// Do compile-time recursion over the given types
+template
+inline typename std::enable_if::type
+run_tests(const std::tuple& types, const List& typeID){}
+
+
+template
+inline typename std::enable_if::type
+run_tests(const std::tuple& types, const List& typeID)
+{
+ Info<< nl << " ## Test constructors: "<< typeID[I] <<" ##" << nl;
+ test_constructors(std::get(types));
+
+ Info<< nl << " ## Test member functions: "<< typeID[I] <<" ##" << nl;
+ test_member_funcs(std::get(types));
+
+ Info<< nl << " ## Test global functions: "<< typeID[I] << " ##" << nl;
+ test_global_funcs(std::get(types));
+
+ Info<< nl << " ## Test global operators: "<< typeID[I] <<" ##" << nl;
+ test_global_opers(std::get(types));
+
+ run_tests(types, typeID);
+}
+
+
+// * * * * * * * * * * * * * * * Main Program * * * * * * * * * * * * * * * //
+
+int main()
+{
+ const std::tuple types
+ (
+ std::make_tuple(Zero, Zero, Zero)
+ );
+
+ const List typeID
+ ({
+ "DiagTensor",
+ "DiagTensor",
+ "DiagTensor"
+ });
+
+ run_tests(types, typeID);
+
+
+ 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;
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/applications/test/SphericalTensor/Make/files b/applications/test/SphericalTensor/Make/files
new file mode 100644
index 0000000000..0a2c967665
--- /dev/null
+++ b/applications/test/SphericalTensor/Make/files
@@ -0,0 +1,3 @@
+Test-SphericalTensor.C
+
+EXE = $(FOAM_USER_APPBIN)/Test-SphericalTensor
diff --git a/applications/test/SphericalTensor/Make/options b/applications/test/SphericalTensor/Make/options
new file mode 100644
index 0000000000..18e6fe47af
--- /dev/null
+++ b/applications/test/SphericalTensor/Make/options
@@ -0,0 +1,2 @@
+/* EXE_INC = */
+/* EXE_LIBS = */
diff --git a/applications/test/SphericalTensor/Test-SphericalTensor.C b/applications/test/SphericalTensor/Test-SphericalTensor.C
new file mode 100644
index 0000000000..79f2b9d738
--- /dev/null
+++ b/applications/test/SphericalTensor/Test-SphericalTensor.C
@@ -0,0 +1,348 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / 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 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 .
+
+Application
+ Test-SphericalTensor
+
+Description
+ Tests for \c SphericalTensor constructors, member functions and operators
+ using \c floatScalar, \c doubleScalar, and \c complex base types.
+
+ Cross-checks were obtained from 'NumPy 1.15.1' and 'SciPy 1.1.0' if no
+ theoretical cross-check exists (like eigendecomposition relations), and
+ were hard-coded for elementwise comparisons.
+
+ For \c complex base type, the cross-checks do only involve zero imag part.
+
+\*---------------------------------------------------------------------------*/
+
+#include "Tensor.H"
+#include "SymmTensor.H"
+#include "SphericalTensor.H"
+#include "DiagTensor.H"
+#include "floatScalar.H"
+#include "doubleScalar.H"
+#include "complex.H"
+
+using namespace Foam;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Total number of unit tests
+unsigned nTest_ = 0;
+
+
+// Total number of failed unit tests
+unsigned nFail_ = 0;
+
+
+// Compare two floating point types, and print output.
+// Do ++nFail_ if values of two objects are not equal within a given tolerance.
+// The function is converted from PEP-485.
+template
+typename std::enable_if
+<
+ std::is_same::value ||
+ std::is_same::value ||
+ std::is_same::value,
+ void
+>::type cmp
+(
+ const word& msg,
+ const Type& x,
+ const Type& y,
+ const scalar relTol = 1e-8, //
+typename std::enable_if
+<
+ !std::is_same::value &&
+ !std::is_same::value &&
+ !std::is_same::value,
+ void
+>::type cmp
+(
+ const word& msg,
+ const Type& x,
+ const Type& y,
+ const scalar relTol = 1e-8,
+ const scalar absTol = 0
+)
+{
+ Info<< msg << x << endl;
+
+ unsigned nFail = 0;
+
+ for (label i = 0; i < pTraits::nComponents; ++i)
+ {
+ if (max(absTol, relTol*max(mag(x[i]), mag(y[i]))) < mag(x[i] - y[i]))
+ {
+ ++nFail;
+ }
+ }
+
+ if (nFail)
+ {
+ Info<< nl
+ << " #### Fail in " << nFail << " comps ####" << nl << endl;
+ ++nFail_;
+ }
+ ++nTest_;
+}
+
+
+// Create each constructor of SphericalTensor, and print output
+template
+void test_constructors(Type)
+{
+ {
+ Info<< "# Construct initialized to zero:" << nl;
+ const SphericalTensor spT(Zero);
+ Info<< spT << endl;
+ }
+ {
+ Info<< "# Construct given VectorSpace of the same rank:" << nl;
+ const VectorSpace, Type, 1> V(Zero);
+ const SphericalTensor spT(V);
+ Info<< spT << endl;
+ }
+ {
+ Info<< "# Construct given the component:" << nl;
+ const SphericalTensor spT(Type(1));
+ Info<< spT << endl;
+ }
+ {
+ Info<< "# Copy construct:" << nl;
+ const SphericalTensor spT(Zero);
+ const SphericalTensor copyspT(spT);
+ Info<< spT << tab << copyspT << endl;
+ }
+}
+
+
+// Execute each member function of SphericalTensor, and print output
+template
+void test_member_funcs(Type)
+{
+ SphericalTensor spT(Type(1));
+ const SphericalTensor cspT(Type(-9));
+
+ Info<< "# Operand: " << nl
+ << " SphericalTensor = " << spT << endl;
+
+
+ {
+ Info<< "# Component access:" << nl;
+
+ SphericalTensor cpspT(spT.ii());
+ cmp(" 'SphericalTensor' access:", spT, cpspT);
+
+ const SphericalTensor cpcspT(cspT.ii());
+ cmp(" 'const SphericalTensor' access:", cspT, cpcspT);
+ }
+ {
+ Info<< "# SphericalTensor operations:" << nl;
+
+ Info<< " Transpose:" << nl;
+ cmp(" 'SphericalTensor'.T():", spT.T(), spT);
+ }
+}
+
+
+// Execute each global function of SphericalTensor, and print output
+template
+void test_global_funcs(Type)
+{
+ const SphericalTensor spT(Type(5));
+
+ Info<< "# Operand: " << nl
+ << " SphericalTensor = " << spT << endl;
+
+
+ cmp(" Trace = ", tr(spT), Type(15));
+ cmp(" Spherical part = ", sph(spT), spT);
+ cmp(" Determinant = ", det(spT), Type(124.99999999999994));
+ cmp
+ (
+ " Inverse = ",
+ inv(spT),
+ SphericalTensor(Type(0.2))
+ );
+ cmp(" Square of Frobenius norm = ", magSqr(spT), Type(75));
+ cmp(" Max component = ", cmptMax(spT), Type(5));
+ cmp(" Min component = ", cmptMax(spT), Type(5));
+ cmp(" Sum of components = ", cmptSum(spT), Type(15));
+ cmp(" Arithmetic average of components = ", cmptAv(spT), Type(5));
+}
+
+
+// Execute each global operator of SphericalTensor, and print output
+template
+void test_global_opers(Type)
+{
+ const Tensor T
+ (
+ Type(-1), Type(2), Type(-3),
+ Type(4), Type(5), Type(-6),
+ Type(7), Type(8), Type(-9)
+ );
+ const SymmTensor sT
+ (
+ Type(-1), Type(2), Type(-3),
+ Type(5), Type(-6),
+ Type(-9)
+ );
+ const DiagTensor dT(Type(1), Type(5), Type(-9));
+ const SphericalTensor spT(Type(-2));
+ const Vector v(Type(3), Type(2), Type(1));
+ const Type x(4);
+
+ Info<< "# Operands:" << nl
+ << " Tensor = " << T << nl
+ << " SymmTensor = " << sT << nl
+ << " DiagTensor = " << dT << nl
+ << " SphericalTensor = " << spT << nl
+ << " Vector = " << v << nl
+ << " Type = " << x << endl;
+
+
+ cmp
+ (
+ " Division of Type by SpTensor = ",
+ (x/spT),
+ SphericalTensor(Type(-2))
+ );
+ cmp
+ (
+ " Division of SpTensor by Type = ",
+ (spT/x),
+ SphericalTensor(Type(-0.5))
+ );
+ cmp
+ (
+ " Inner-product of SpTensor-SpTensor = ",
+ (spT & spT),
+ SphericalTensor(Type(4))
+ );
+ cmp
+ (
+ " Inner-product of SpTensor-Vector = ",
+ (spT & v),
+ Vector(Type(-6), Type(-4), Type(-2)) // Column-vector
+ );
+ cmp
+ (
+ " Inner-product of Vector-SpTensor = ",
+ (v & spT),
+ Vector(Type(-6), Type(-4), Type(-2)) // Row-vector
+ );
+ cmp(" D-inner-product of SpTensor-SpTensor = ", (spT && spT), Type(12));
+}
+
+
+// Do compile-time recursion over the given types
+template
+inline typename std::enable_if::type
+run_tests(const std::tuple& types, const List& typeID){}
+
+
+template
+inline typename std::enable_if::type
+run_tests(const std::tuple& types, const List& typeID)
+{
+ Info<< nl << " ## Test constructors: "<< typeID[I] <<" ##" << nl;
+ test_constructors(std::get(types));
+
+ Info<< nl << " ## Test member functions: "<< typeID[I] <<" ##" << nl;
+ test_member_funcs(std::get(types));
+
+ Info<< nl << " ## Test global functions: "<< typeID[I] << " ##" << nl;
+ test_global_funcs(std::get(types));
+
+ Info<< nl << " ## Test global operators: "<< typeID[I] <<" ##" << nl;
+ test_global_opers(std::get(types));
+
+ run_tests(types, typeID);
+}
+
+
+// * * * * * * * * * * * * * * * Main Program * * * * * * * * * * * * * * * //
+
+int main()
+{
+ const std::tuple types
+ (
+ std::make_tuple(Zero, Zero, Zero)
+ );
+
+ const List typeID
+ ({
+ "SphericalTensor",
+ "SphericalTensor",
+ "SphericalTensor"
+ });
+
+ run_tests(types, typeID);
+
+
+ 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;
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/applications/test/SphericalTensor2D/Make/files b/applications/test/SphericalTensor2D/Make/files
new file mode 100644
index 0000000000..2b492a2f8c
--- /dev/null
+++ b/applications/test/SphericalTensor2D/Make/files
@@ -0,0 +1,3 @@
+Test-SphericalTensor2D.C
+
+EXE = $(FOAM_USER_APPBIN)/Test-SphericalTensor2D
diff --git a/applications/test/SphericalTensor2D/Make/options b/applications/test/SphericalTensor2D/Make/options
new file mode 100644
index 0000000000..18e6fe47af
--- /dev/null
+++ b/applications/test/SphericalTensor2D/Make/options
@@ -0,0 +1,2 @@
+/* EXE_INC = */
+/* EXE_LIBS = */
diff --git a/applications/test/SphericalTensor2D/Test-SphericalTensor2D.C b/applications/test/SphericalTensor2D/Test-SphericalTensor2D.C
new file mode 100644
index 0000000000..00d17610dc
--- /dev/null
+++ b/applications/test/SphericalTensor2D/Test-SphericalTensor2D.C
@@ -0,0 +1,331 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / 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 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 .
+
+Application
+ Test-SphericalTensor2D
+
+Description
+ Tests for \c SphericalTensor2D constructors, member functions and operators
+ using \c floatScalar, \c doubleScalar, and \c complex base types.
+
+ Cross-checks were obtained from 'NumPy 1.15.1' and 'SciPy 1.1.0' if no
+ theoretical cross-check exists (like eigendecomposition relations), and
+ were hard-coded for elementwise comparisons.
+
+ For \c complex base type, the cross-checks do only involve zero imag part.
+
+\*---------------------------------------------------------------------------*/
+
+#include "Tensor2D.H"
+#include "SymmTensor2D.H"
+#include "SphericalTensor2D.H"
+#include "floatScalar.H"
+#include "doubleScalar.H"
+#include "complex.H"
+
+using namespace Foam;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Total number of unit tests
+unsigned nTest_ = 0;
+
+
+// Total number of failed unit tests
+unsigned nFail_ = 0;
+
+
+// Compare two floating point types, and print output.
+// Do ++nFail_ if values of two objects are not equal within a given tolerance.
+// The function is converted from PEP-485.
+template
+typename std::enable_if
+<
+ std::is_same::value ||
+ std::is_same::value ||
+ std::is_same::value,
+ void
+>::type cmp
+(
+ const word& msg,
+ const Type& x,
+ const Type& y,
+ const scalar relTol = 1e-8, //
+typename std::enable_if
+<
+ !std::is_same::value &&
+ !std::is_same::value &&
+ !std::is_same::value,
+ void
+>::type cmp
+(
+ const word& msg,
+ const Type& x,
+ const Type& y,
+ const scalar relTol = 1e-8,
+ const scalar absTol = 0
+)
+{
+ Info<< msg << x << endl;
+
+ unsigned nFail = 0;
+
+ for (label i = 0; i < pTraits::nComponents; ++i)
+ {
+ if (max(absTol, relTol*max(mag(x[i]), mag(y[i]))) < mag(x[i] - y[i]))
+ {
+ ++nFail;
+ }
+ }
+
+ if (nFail)
+ {
+ Info<< nl
+ << " #### Fail in " << nFail << " comps ####" << nl << endl;
+ ++nFail_;
+ }
+ ++nTest_;
+}
+
+
+// Create each constructor of SphericalTensor2D, and print output
+template
+void test_constructors(Type)
+{
+ {
+ Info<< "# Construct initialized to zero:" << nl;
+ const SphericalTensor2D spT(Zero);
+ Info<< spT << endl;
+ }
+ {
+ Info<< "# Construct given VectorSpace of the same rank:" << nl;
+ const VectorSpace, Type, 1> V(Zero);
+ const SphericalTensor2D spT(V);
+ Info<< spT << endl;
+ }
+ {
+ Info<< "# Construct given the component:" << nl;
+ const SphericalTensor2D spT(Type(1));
+ Info<< spT << endl;
+ }
+ {
+ Info<< "# Copy construct:" << nl;
+ const SphericalTensor2D spT(Zero);
+ const SphericalTensor2D copyspT(spT);
+ Info<< spT << tab << copyspT << endl;
+ }
+}
+
+
+// Execute each member function of SphericalTensor2D, and print output
+template
+void test_member_funcs(Type)
+{
+ SphericalTensor2D spT(Type(1));
+ const SphericalTensor2D cspT(Type(-9));
+
+ Info<< "# Operand: " << nl
+ << " SphericalTensor2D = " << spT << endl;
+
+
+ {
+ Info<< "# Component access:" << nl;
+
+ SphericalTensor2D cpspT(spT.ii());
+ cmp(" 'SphericalTensor2D' access:", spT, cpspT);
+
+ const SphericalTensor2D cpcspT(cspT.ii());
+ cmp(" 'const SphericalTensor2D' access:", cspT, cpcspT);
+ }
+}
+
+
+// Execute each global function of SphericalTensor2D, and print output
+template
+void test_global_funcs(Type)
+{
+ const SphericalTensor2D spT(Type(5));
+
+ Info<< "# Operand: " << nl
+ << " SphericalTensor2D = " << spT << endl;
+
+
+ cmp(" Trace = ", tr(spT), Type(10));
+ cmp(" Spherical part = ", sph(spT), spT);
+ cmp(" Determinant = ", det(spT), Type(24.99999999999994));
+ cmp
+ (
+ " Inverse = ",
+ inv(spT),
+ SphericalTensor2D(Type(0.2))
+ );
+}
+
+
+// Execute each global operator of SphericalTensor2D, and print output
+template
+void test_global_opers(Type)
+{
+ const Tensor2D T
+ (
+ Type(-1), Type(2),
+ Type(4), Type(5)
+ );
+ const SymmTensor2D sT
+ (
+ Type(-1), Type(2),
+ Type(5)
+ );
+ const SphericalTensor2D spT(Type(-2));
+ const Vector2D v(Type(3), Type(2));
+ const Type x(4);
+
+ Info<< "# Operands:" << nl
+ << " Tensor2D = " << T << nl
+ << " SymmTensor2D = " << sT << nl
+ << " SphericalTensor2D = " << spT << nl
+ << " Vector2D = " << v << nl
+ << " Type = " << x << endl;
+
+
+ cmp
+ (
+ " Division of Type by SpTensor2D = ",
+ (x/spT),
+ SphericalTensor2D(Type(-2))
+ );
+ cmp
+ (
+ " Division of SpTensor2D by Type = ",
+ (spT/x),
+ SphericalTensor2D(Type(-0.5))
+ );
+ cmp
+ (
+ " Inner-product of SpTensor2D-SpTensor2D = ",
+ (spT & spT),
+ SphericalTensor2D(Type(4))
+ );
+ cmp
+ (
+ " Inner-product of SpTensor2D-Vector2D = ",
+ (spT & v),
+ Vector2D(Type(-6), Type(-4)) // Column-vector
+ );
+ cmp
+ (
+ " Inner-product of Vector2D-SpTensor2D = ",
+ (v & spT),
+ Vector2D(Type(-6), Type(-4)) // Row-vector
+ );
+}
+
+
+// Do compile-time recursion over the given types
+template
+inline typename std::enable_if::type
+run_tests(const std::tuple& types, const List& typeID){}
+
+
+template
+inline typename std::enable_if::type
+run_tests(const std::tuple& types, const List& typeID)
+{
+ Info<< nl << " ## Test constructors: "<< typeID[I] <<" ##" << nl;
+ test_constructors(std::get(types));
+
+ Info<< nl << " ## Test member functions: "<< typeID[I] <<" ##" << nl;
+ test_member_funcs(std::get(types));
+
+ Info<< nl << " ## Test global functions: "<< typeID[I] << " ##" << nl;
+ test_global_funcs(std::get(types));
+
+ Info<< nl << " ## Test global operators: "<< typeID[I] <<" ##" << nl;
+ test_global_opers(std::get(types));
+
+ run_tests(types, typeID);
+}
+
+
+// * * * * * * * * * * * * * * * Main Program * * * * * * * * * * * * * * * //
+
+int main()
+{
+ const std::tuple types
+ (
+ std::make_tuple(Zero, Zero, Zero)
+ );
+
+ const List typeID
+ ({
+ "SphericalTensor2D",
+ "SphericalTensor2D",
+ "SphericalTensor2D"
+ });
+
+ run_tests(types, typeID);
+
+
+ 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;
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/applications/test/SymmTensor/Make/files b/applications/test/SymmTensor/Make/files
new file mode 100644
index 0000000000..186a997815
--- /dev/null
+++ b/applications/test/SymmTensor/Make/files
@@ -0,0 +1,3 @@
+Test-SymmTensor.C
+
+EXE = $(FOAM_USER_APPBIN)/Test-SymmTensor
diff --git a/applications/test/SymmTensor/Make/options b/applications/test/SymmTensor/Make/options
new file mode 100644
index 0000000000..18e6fe47af
--- /dev/null
+++ b/applications/test/SymmTensor/Make/options
@@ -0,0 +1,2 @@
+/* EXE_INC = */
+/* EXE_LIBS = */
diff --git a/applications/test/SymmTensor/Test-SymmTensor.C b/applications/test/SymmTensor/Test-SymmTensor.C
new file mode 100644
index 0000000000..5e48ef7c34
--- /dev/null
+++ b/applications/test/SymmTensor/Test-SymmTensor.C
@@ -0,0 +1,576 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / 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 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 .
+
+Application
+ Test-SymmTensor
+
+Description
+ Tests for \c SymmTensor constructors, member functions and operators
+ using \c floatScalar, \c doubleScalar, and \c complex base types.
+
+ Eigen decomposition tests for \c symmTensor, i.e. SymmTensor.
+
+ Cross-checks were obtained from 'NumPy 1.15.1' and 'SciPy 1.1.0' if no
+ theoretical cross-check exists (like eigendecomposition relations), and
+ were hard-coded for elementwise comparisons.
+
+ For \c complex base type, the cross-checks do only involve zero imag part.
+
+\*---------------------------------------------------------------------------*/
+
+#include "symmTensor.H"
+#include "transform.H"
+#include "Random.H"
+#include "floatScalar.H"
+#include "doubleScalar.H"
+#include "complex.H"
+
+using namespace Foam;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// Total number of unit tests
+unsigned nTest_ = 0;
+
+
+// Total number of failed unit tests
+unsigned nFail_ = 0;
+
+
+// Create a random symmTensor
+symmTensor makeRandomContainer(Random& rnd)
+{
+ symmTensor A(Zero);
+ std::generate(A.begin(), A.end(), [&]{ return rnd.GaussNormal(); });
+ return A;
+}
+
+
+// Compare two floating point types, and print output.
+// Do ++nFail_ if values of two objects are not equal within a given tolerance.
+// The function is converted from PEP-485.
+template
+typename std::enable_if
+<
+ std::is_same::value ||
+ std::is_same::value ||
+ std::is_same::value,
+ void
+>::type cmp
+(
+ const word& msg,
+ const Type& x,
+ const Type& y,
+ const scalar relTol = 1e-8, //
+typename std::enable_if
+<
+ !std::is_same::value &&
+ !std::is_same::value &&
+ !std::is_same::value,
+ void
+>::type cmp
+(
+ const word& msg,
+ const Type& x,
+ const Type& y,
+ const scalar relTol = 1e-8,
+ const scalar absTol = 0
+)
+{
+ Info<< msg << x << endl;
+
+ unsigned nFail = 0;
+
+ for (label i = 0; i < pTraits::nComponents; ++i)
+ {
+ if (max(absTol, relTol*max(mag(x[i]), mag(y[i]))) < mag(x[i] - y[i]))
+ {
+ ++nFail;
+ }
+ }
+
+ if (nFail)
+ {
+ Info<< nl
+ << " #### Fail in " << nFail << " comps ####" << nl << endl;
+ ++nFail_;
+ }
+ ++nTest_;
+}
+
+
+// Create each constructor of SymmTensor, and print output
+template
+void test_constructors(Type)
+{
+ {
+ Info<< "# Construct initialized to zero:" << nl;
+ const SymmTensor sT(Zero);
+ Info<< sT << endl;
+ }
+ {
+ Info<< "# Construct given VectorSpace of the same rank:" << nl;
+ const VectorSpace, Type, 6> M(Zero);
+ const SymmTensor sT(M);
+ Info<< sT << endl;
+ }
+ {
+ Info<< "# Construct given SphericalTensor:" << nl;
+ const SphericalTensor Sp(Type(5));
+ const SymmTensor sT(Sp);
+ Info<< sT << endl;
+ }
+ {
+ Info<< "# Construct given the six components:" << nl;
+ const SymmTensor sT
+ (
+ Type(1), Type(2), Type(-3),
+ Type(5), Type(-6),
+ Type(-9)
+ );
+ Info<< sT << endl;
+ }
+ {
+ Info<< "# Copy construct:" << nl;
+ const SymmTensor sT(Zero);
+ const SymmTensor copysT(sT);
+ Info<< sT << tab << copysT << endl;
+ }
+}
+
+
+// Execute each member function of SymmTensor, and print output
+template
+void test_member_funcs(Type)
+{
+ SymmTensor sT
+ (
+ Type(1), Type(2), Type(-3),
+ Type(5), Type(-6),
+ Type(-9)
+ );
+ const SymmTensor csT
+ (
+ Type(1), Type(2), Type(-3),
+ Type(5), Type(-6),
+ Type(-9)
+ );
+
+ Info<< "# Operand: " << nl
+ << " SymmTensor = " << sT << endl;
+
+
+ {
+ Info<< "# Component access:" << nl;
+
+ SymmTensor cpsT
+ (
+ sT.xx(), sT.xy(), sT.xz(),
+ sT.yy(), sT.yz(),
+ sT.zz()
+ );
+ cmp(" 'SymmTensor' access:", sT, cpsT);
+ cmp(" xy()=yx():", sT.xy(), sT.yx());
+ cmp(" xz()=zx():", sT.xz(), sT.zx());
+ cmp(" yz()=zy():", sT.yz(), sT.zy());
+
+ const SymmTensor cpcsT
+ (
+ csT.xx(), csT.xy(), csT.xz(),
+ csT.yy(), csT.yz(),
+ csT.zz()
+ );
+ cmp(" 'const SymmTensor' access:", csT, cpcsT);
+ cmp(" xy()=yx():", sT.xy(), sT.yx());
+ cmp(" xz()=zx():", sT.xz(), sT.zx());
+ cmp(" yz()=zy():", sT.yz(), sT.zy());
+ }
+ {
+ Info<< "# Diagonal access:" << nl;
+
+ cmp
+ (
+ " 'SymmTensor'.diag():",
+ sT.diag(),
+ Vector(Type(1), Type(5), Type(-9))
+ );
+ cmp
+ (
+ " 'const SymmTensor'.diag():",
+ csT.diag(),
+ Vector(Type(1), Type(5), Type(-9))
+ );
+
+
+ Info<< "# Diagonal manipulation:" << nl;
+
+ sT.diag(Vector(Type(-10), Type(-15), Type(-20)));
+ cmp
+ (
+ " 'SymmTensor'.diag('Vector'):",
+ sT.diag(),
+ Vector(Type(-10), Type(-15), Type(-20))
+ );
+ }
+ {
+ Info<< "# Tensor operations:" << nl;
+
+ Info<< " Transpose:" << nl;
+ cmp(" 'SymmTensor'.T():", sT.T(), sT);
+ }
+ {
+ Info<< "# Member operators:" << nl;
+
+ sT = SphericalTensor(Type(5));
+ cmp
+ (
+ " Assign to a SphericalTensor:",
+ sT,
+ SymmTensor
+ (
+ Type(5), Zero, Zero,
+ Type(5), Zero,
+ Type(5)
+ )
+ );
+ }
+}
+
+
+// Execute each global function of SymmTensor, and print output
+template
+void test_global_funcs(Type)
+{
+ const SymmTensor sT
+ (
+ Type(1), Type(2), Type(-3),
+ Type(5), Type(-6),
+ Type(-9)
+ );
+
+ Info<< "# Operand: " << nl
+ << " SymmTensor = " << sT << nl << endl;
+
+
+ cmp(" Trace = ", tr(sT), Type(-3));
+ cmp(" Spherical part = ", sph(sT), SphericalTensor(tr(sT)/Type(3)));
+ cmp(" Symmetric part = ", symm(sT), sT);
+ cmp(" Twice the symmetric part = ", twoSymm(sT), 2*sT);
+ cmp
+ (
+ " Deviatoric part = ",
+ dev(sT),
+ SymmTensor
+ (
+ Type(2), Type(2), Type(-3),
+ Type(6), Type(-6),
+ Type(-8)
+ )
+ );
+ cmp(" Two-third deviatoric part = ", dev2(sT), sT - 2*sph(sT));
+ cmp(" Determinant = ", det(sT), Type(-17.999999999999996));
+ cmp
+ (
+ " Cofactor tensor = ",
+ cof(sT),
+ SymmTensor
+ (
+ Type(-81), Type(36), Type(3),
+ Type(-18), Type(0),
+ Type(1)
+ )
+ );
+ cmp
+ (
+ " Inverse = ",
+ inv(sT, det(sT)),
+ SymmTensor
+ (
+ Type(4.5), Type(-2), Type(-0.16666667),
+ Type(1), Type(0),
+ Type(-0.05555556)
+ ),
+ 1e-8,
+ 1e-8
+ );
+ cmp
+ (
+ " Inverse (another) = ",
+ inv(sT),
+ SymmTensor
+ (
+ Type(4.5), Type(-2), Type(-0.16666667),
+ Type(1), Type(0),
+ Type(-0.05555556)
+ ),
+ 1e-8,
+ 1e-8
+ );
+ cmp(" First invariant = ", invariantI(sT), Type(-3));
+ cmp(" Second invariant = ", invariantII(sT), Type(-98));
+ cmp(" Third invariant = ", invariantIII(sT), Type(-17.999999999999996));
+ cmp
+ (
+ " Inner-product with self = ",
+ innerSqr(sT),
+ SymmTensor
+ (
+ Type(14), Type(30), Type(12),
+ Type(65), Type(18),
+ Type(126)
+ )
+ );
+ cmp(" Square of Frobenius norm = ", magSqr(sT), Type(205));
+}
+
+
+// Execute each global operator of SymmTensor, and print output
+template
+void test_global_opers(Type)
+{
+ const Tensor T
+ (
+ Type(1), Type(2), Type(-3),
+ Type(4), Type(5), Type(-6),
+ Type(7), Type(8), Type(-9)
+ );
+ const SymmTensor sT
+ (
+ Type(1), Type(2), Type(-3),
+ Type(5), Type(-6),
+ Type(-9)
+ );
+ const SphericalTensor spT(Type(1));
+ const Vector v(Type(3), Type(2), Type(1));
+ const Type x(4);
+
+ Info<< "# Operands:" << nl
+ << " Tensor = " << T << nl
+ << " SymmTensor = " << sT << nl
+ << " SphericalTensor = " << spT << nl
+ << " Vector = " << v << nl
+ << " Type = " << x << endl;
+
+
+ cmp
+ (
+ " Sum of SpTensor-SymmTensor = ",
+ (spT + sT),
+ SymmTensor
+ (
+ Type(2), Type(2), Type(-3),
+ Type(6), Type(-6),
+ Type(-8)
+ )
+ );
+ cmp
+ (
+ " Sum of SymmTensor-SpTensor = ",
+ (sT + spT),
+ SymmTensor
+ (
+ Type(2), Type(2), Type(-3),
+ Type(6), Type(-6),
+ Type(-8)
+ )
+ );
+ cmp
+ (
+ " Subtract SymmTensor from SpTensor = ",
+ (spT - sT),
+ SymmTensor
+ (
+ Type(0), Type(-2), Type(3),
+ Type(-4), Type(6),
+ Type(10)
+ )
+ );
+ cmp
+ (
+ " Subtract SpTensor from SymmTensor = ",
+ (sT - spT),
+ SymmTensor
+ (
+ Type(0), Type(2), Type(-3),
+ Type(4), Type(-6),
+ Type(-10)
+ )
+ );
+ cmp
+ (
+ " Hodge dual of a SymmTensor",
+ *sT,
+ Vector(Type(-6), Type(3), Type(2))
+ );
+ cmp
+ (
+ " Division of a SymmTensor by a Type",
+ sT/x,
+ SymmTensor
+ (
+ Type(0.25), Type(0.5), Type(-0.75),
+ Type(1.25), Type(-1.5),
+ Type(-2.25)
+ )
+ );
+ cmp
+ (
+ " Inner-product of SymmTensor-SymmTensor = ",
+ (sT & sT),
+ Tensor
+ (
+ Type(14), Type(30), Type(12),
+ Type(30), Type(65), Type(18),
+ Type(12), Type(18), Type(126)
+ )
+ );
+ cmp
+ (
+ " Inner-product of SpTensor-SymmTensor = ",
+ (spT & sT),
+ SymmTensor
+ (
+ Type(1), Type(2), Type(-3),
+ Type(5), Type(-6),
+ Type(-9)
+ )
+ );
+ cmp
+ (
+ " Inner-product of SymmTensor-SpTensor = ",
+ (sT & spT),
+ SymmTensor
+ (
+ Type(1), Type(2), Type(-3),
+ Type(5), Type(-6),
+ Type(-9)
+ )
+ );
+ cmp
+ (
+ " Inner-product of SymmTensor-Vector = ",
+ (sT & v),
+ Vector(Type(4), Type(10), Type(-30)) // Column-vector
+ );
+ cmp
+ (
+ " Inner-product of Vector-SymmTensor = ",
+ (v & sT),
+ Vector(Type(4), Type(10), Type(-30)) // Row-vector
+ );
+ cmp(" D-inner-product of SymmTensor-SymmTensor = ", (sT && sT), Type(205));
+ cmp(" D-inner-product of SymmTensor-SpTensor = ", (sT && spT), Type(-3));
+ cmp(" D-inner-product of SpTensor-SymmTensor = ", (spT && sT), Type(-3));
+}
+
+
+// Do compile-time recursion over the given types
+template
+inline typename std::enable_if::type
+run_tests(const std::tuple& types, const List& typeID){}
+
+
+template
+inline typename std::enable_if::type
+run_tests(const std::tuple& types, const List& typeID)
+{
+ Info<< nl << " ## Test constructors: "<< typeID[I] <<" ##" << nl;
+ test_constructors(std::get(types));
+
+ Info<< nl << " ## Test member functions: "<< typeID[I] <<" ##" << nl;
+ test_member_funcs(std::get(types));
+
+ Info<< nl << " ## Test global functions: "<< typeID[I] << " ##" << nl;
+ test_global_funcs(std::get(types));
+
+ Info<< nl << " ## Test global operators: "<< typeID[I] <<" ##" << nl;
+ test_global_opers(std::get(types));
+
+ run_tests(types, typeID);
+}
+
+
+// * * * * * * * * * * * * * * * Main Program * * * * * * * * * * * * * * * //
+
+int main()
+{
+ const std::tuple types
+ (
+ std::make_tuple(Zero, Zero, Zero)
+ );
+
+ const List typeID
+ ({
+ "SymmTensor",
+ "SymmTensor",
+ "SymmTensor