diff --git a/applications/test/cubicEqn/Test-cubicEqn.C b/applications/test/cubicEqn/Test-cubicEqn.C
deleted file mode 100644
index aa0579a41940d23aac180a523d7b871a73f0d84f..0000000000000000000000000000000000000000
--- a/applications/test/cubicEqn/Test-cubicEqn.C
+++ /dev/null
@@ -1,118 +0,0 @@
-#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 scalar tol = 5;
-
-    const label nTests = 1000000;
-    for (label 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)
-            ),
-            tol
-        );
-    }
-    Info << nTests << " random cubics tested" << endl;
-
-    const label coeffMin = -9, coeffMax = 10, nCoeff = coeffMax - coeffMin;
-    for (label a = coeffMin; a < coeffMax; ++ a)
-    {
-        for (label b = coeffMin; b < coeffMax; ++ b)
-        {
-            for (label c = coeffMin; c < coeffMax; ++ c)
-            {
-                for (label d = coeffMin; d < coeffMax; ++ d)
-                {
-                    test(cubicEqn(a, b, c, d), tol);
-                }
-            }
-        }
-    }
-    Info<< nCoeff*nCoeff*nCoeff*nCoeff << " integer cubics tested" << endl;
-
-    return 0;
-}
diff --git a/applications/test/cubicEqn/Make/files b/applications/test/polynomialEqns/cubicEqn/Make/files
similarity index 100%
rename from applications/test/cubicEqn/Make/files
rename to applications/test/polynomialEqns/cubicEqn/Make/files
diff --git a/applications/test/cubicEqn/Make/options b/applications/test/polynomialEqns/cubicEqn/Make/options
similarity index 100%
rename from applications/test/cubicEqn/Make/options
rename to applications/test/polynomialEqns/cubicEqn/Make/options
diff --git a/applications/test/polynomialEqns/cubicEqn/Test-cubicEqn.C b/applications/test/polynomialEqns/cubicEqn/Test-cubicEqn.C
new file mode 100644
index 0000000000000000000000000000000000000000..df510df1959c9fd5eae9eb759297e0d1a2db82b7
--- /dev/null
+++ b/applications/test/polynomialEqns/cubicEqn/Test-cubicEqn.C
@@ -0,0 +1,339 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2018 OpenFOAM Foundation
+    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/>.
+
+Application
+    Test-cubicEqn
+
+Description
+    Tests for \c cubicEqn constructors, and member functions.
+
+\*---------------------------------------------------------------------------*/
+
+#include <ctime>
+#include <random>
+
+#include "cubicEqn.H"
+#include "vector.H"
+#include "scalarList.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.
+void cmp
+(
+    const word& msg,
+    const scalar& x,
+    const scalar& y,
+    const scalar relTol = 1e-8,  //<! are values the same within 8 decimals
+    const scalar absTol = 0      //<! useful for cmps near zero
+)
+{
+    Info<< msg << x << endl;
+
+    unsigned nFail = 0;
+
+    if (max(absTol, relTol*max(mag(x), mag(y))) < mag(x - y))
+    {
+        ++nFail;
+    }
+
+    if (nFail)
+    {
+        Info<< nl
+            << "        #### Fail in " << nFail << " comps ####" << nl << endl;
+        ++nFail_;
+    }
+    ++nTest_;
+}
+
+
+// Create each constructor of cubicEqn, and print output
+void test_constructors()
+{
+    #if 0
+    {
+        Info<< "# Construct null:" << nl;
+        const cubicEqn T();
+        Info<< T << endl;
+    }
+    #endif
+
+    {
+        Info<< "# Construct initialized to zero:" << nl;
+        const cubicEqn T(Zero);
+        Info<< T << endl;
+    }
+    {
+        Info<< "# Construct from components:" << nl;
+        const cubicEqn T(1, 2, 3, 4);
+        Info<< T << endl;
+    }
+}
+
+
+// Execute each member function of cubicEqn, and print output
+void test_member_funcs()
+{
+    const cubicEqn T(1, -6, -2, 12);
+    const scalar x = 9.7;
+
+    Info<< "# Operands: " << nl
+        << "  cubicEqn = " << T << nl
+        << "  x = " << x << endl;
+
+
+    {
+        Info<< "# Access:" << nl;
+        cmp("  a = ",  1, T.a());
+        cmp("  b = ", -6, T.b());
+        cmp("  c = ", -2, T.c());
+        cmp("  d = ", 12, T.d());
+    }
+    {
+        Info<< "# Evaluate:" << nl;
+        cmp("  T.value(x) = ", T.value(x), 340.73299999999983);
+        cmp("  T.derivative(x) = ", T.derivative(x), 163.87);
+        cmp
+        (
+            "  T.error(x) = ",
+            T.error(x),
+            2.1854789999999994e-13,
+            1e-8,
+            1e-8
+        );
+
+        const vector rts(6, 1.41421356, -1.41421356);
+        forAll(rts, i)
+        {
+            cmp("  T.roots() = ", T.roots()[i], rts[i]);
+        }
+    }
+
+    {
+        Info<< "# Special cases for the root evaluation: " << nl;
+
+        {
+            Info<< "  Three distinct real roots:" << nl;
+            const cubicEqn Tc(1.0, -4.0, 1.0, 6.0);
+            const vector rts(3, 2, -1);
+            forAll(rts, i)
+            {
+                cmp("    root = ", Tc.roots()[i], rts[i]);
+            }
+        }
+        {
+            Info<< "  One real root and one complex conjugate-pair root:" << nl;
+            const cubicEqn Tc(1.0, 2.0, 3.0, 4.0);
+            const vector rts(-1.65062919, -0.1746854, 1.54686889);
+            forAll(rts, i)
+            {
+                cmp("    root = ", Tc.roots()[i], rts[i], 1e-8, 1e-8);
+            }
+        }
+        {
+            Info<< "  Two identical real roots and a distinct real root:" << nl;
+            const cubicEqn Tc(1.0, -5.0, 8.0, -4.0);
+            const vector rts(2, 2, 1);
+            forAll(rts, i)
+            {
+                cmp("    root = ", Tc.roots()[i], rts[i]);
+            }
+        }
+        {
+            Info<< "  Three identical real roots:" << nl;
+            const cubicEqn Tc(1.0, -6.0, 12.0, -8.0);
+            const vector rts(2, 2, 2);
+            forAll(rts, i)
+            {
+                cmp("    root = ", Tc.roots()[i], rts[i]);
+            }
+        }
+        {
+            Info<< "  Arbitrary roots with non-unity leading term:" << nl;
+            const cubicEqn Tc(-8.59, 6.77, -0.2, 8.0);
+            const vector rts(1.31167947, -0.26177687, 0.80093099);
+            forAll(rts, i)
+            {
+                cmp("    root = ", Tc.roots()[i], rts[i]);
+            }
+        }
+    }
+}
+
+
+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 = 0;
+
+    do
+    {
+        x = scalar(distribution(generator));
+    }
+    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;
+        ++nFail_;
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Main Program  * * * * * * * * * * * * * * * //
+
+int main()
+{
+    Info<< nl << "    ## Test constructors: ##" << nl;
+    test_constructors();
+
+    Info<< nl << "    ## Test member functions: ##" << nl;
+    test_member_funcs();
+
+
+    // Pre-v2006 tests
+    const scalar tol = 5;
+
+    const label nTests = 1000000;
+    for (label 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)
+            ),
+            tol
+        );
+        ++nTest_;
+    }
+    Info << nTests << " scalar cubic equations were tested." << endl;
+
+    const label coeffMin = -9, coeffMax = 10, nCoeff = coeffMax - coeffMin;
+    for (label a = coeffMin; a < coeffMax; ++ a)
+    {
+        for (label b = coeffMin; b < coeffMax; ++ b)
+        {
+            for (label c = coeffMin; c < coeffMax; ++ c)
+            {
+                for (label d = coeffMin; d < coeffMax; ++ d)
+                {
+                    test(cubicEqn(a, b, c, d), tol);
+                    ++nTest_;
+                }
+            }
+        }
+    }
+    Info<< nCoeff*nCoeff*nCoeff*nCoeff
+        << " label cubic equations were tested." << endl;
+
+
+    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/polynomialEqns/linearEqn/Make/files b/applications/test/polynomialEqns/linearEqn/Make/files
new file mode 100644
index 0000000000000000000000000000000000000000..d897b48a2e87aed3750ffe90be5fb0320395c631
--- /dev/null
+++ b/applications/test/polynomialEqns/linearEqn/Make/files
@@ -0,0 +1,3 @@
+Test-linearEqn.C
+
+EXE = $(FOAM_USER_APPBIN)/Test-linearEqn
diff --git a/applications/test/polynomialEqns/linearEqn/Make/options b/applications/test/polynomialEqns/linearEqn/Make/options
new file mode 100644
index 0000000000000000000000000000000000000000..4c3dd783cb4170feefb3f5385510a83257b43b18
--- /dev/null
+++ b/applications/test/polynomialEqns/linearEqn/Make/options
@@ -0,0 +1,3 @@
+EXE_INC =
+
+EXE_LIBS =
diff --git a/applications/test/polynomialEqns/linearEqn/Test-linearEqn.C b/applications/test/polynomialEqns/linearEqn/Test-linearEqn.C
new file mode 100644
index 0000000000000000000000000000000000000000..683a1b161d9c4fee33ed203634c6eb572d23cc97
--- /dev/null
+++ b/applications/test/polynomialEqns/linearEqn/Test-linearEqn.C
@@ -0,0 +1,270 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 <http://www.gnu.org/licenses/>.
+
+Application
+    Test-linearEqn
+
+Description
+    Tests for \c linearEqn constructors, and member functions.
+
+\*---------------------------------------------------------------------------*/
+
+#include <ctime>
+#include <random>
+
+#include "linearEqn.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.
+void cmp
+(
+    const word& msg,
+    const scalar& x,
+    const scalar& y,
+    const scalar relTol = 1e-8,  //<! are values the same within 8 decimals
+    const scalar absTol = 0      //<! useful for cmps near zero
+)
+{
+    Info<< msg << x << endl;
+
+    unsigned nFail = 0;
+
+    if (max(absTol, relTol*max(mag(x), mag(y))) < mag(x - y))
+    {
+        ++nFail;
+    }
+
+    if (nFail)
+    {
+        Info<< nl
+            << "        #### Fail in " << nFail << " comps ####" << nl << endl;
+        ++nFail_;
+    }
+    ++nTest_;
+}
+
+
+// Create each constructor of linearEqn, and print output
+void test_constructors()
+{
+    #if 0
+    {
+        Info<< "# Construct null:" << nl;
+        const linearEqn T();
+        Info<< T << endl;
+    }
+    #endif
+
+    {
+        Info<< "# Construct initialized to zero:" << nl;
+        const linearEqn T(Zero);
+        Info<< T << endl;
+    }
+    {
+        Info<< "# Construct from components:" << nl;
+        const linearEqn T(1, 2);
+        Info<< T << endl;
+    }
+}
+
+
+// Execute each member function of linearEqn, and print output
+void test_member_funcs()
+{
+    const linearEqn T(1, -6);
+    const scalar x = 9.7;
+
+    Info<< "# Operands: " << nl
+        << "  linearEqn = " << T << nl
+        << "  x = " << x << endl;
+
+
+    {
+        Info<< "# Access:" << nl;
+        cmp("  a = ",  1, T.a());
+        cmp("  b = ",  -6, T.b());
+    }
+    {
+        Info<< "# Evaluate:" << nl;
+        cmp("  T.value(x) = ", T.value(x), 3.6999999999999993);
+        cmp("  T.derivative(x) = ", T.derivative(x), 1);
+        cmp
+        (
+            "  T.error(x) = ",
+            T.error(x),
+            1.5699999999999999e-15,
+            1e-8,
+            1e-8
+        );
+        cmp("  T.roots() = ", T.roots()[0], 6);
+    }
+}
+
+
+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 = 0;
+
+    do
+    {
+        x = scalar(distribution(generator));
+    }
+    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;
+        ++nFail_;
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Main Program  * * * * * * * * * * * * * * * //
+
+int main()
+{
+    Info<< nl << "    ## Test constructors: ##" << nl;
+    test_constructors();
+
+    Info<< nl << "    ## Test member functions: ##" << nl;
+    test_member_funcs();
+
+
+    // Sanity checks
+    const scalar tol = 5;
+
+    const label nTests = 1000000;
+    for (label t = 0; t < nTests; ++t)
+    {
+        test
+        (
+            linearEqn
+            (
+                randomScalar(1e-50, 1e+50),
+                randomScalar(1e-50, 1e+50)
+            ),
+            tol
+        );
+        ++nTest_;
+    }
+    Info << nTests << " scalar linear equations were tested." << endl;
+
+    const label coeffMin = -9, coeffMax = 10, nCoeff = coeffMax - coeffMin;
+    for (label a = coeffMin; a < coeffMax; ++a)
+    {
+        for (label b = coeffMin; b < coeffMax; ++b)
+        {
+            test(linearEqn(a, b), tol);
+            ++nTest_;
+        }
+    }
+    Info<< nCoeff*nCoeff << " label linear equations were tested." << endl;
+
+
+    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/polynomialEqns/quadraticEqn/Make/files b/applications/test/polynomialEqns/quadraticEqn/Make/files
new file mode 100644
index 0000000000000000000000000000000000000000..997b2f63cb518a617da86f4a8b3f320ff4da615d
--- /dev/null
+++ b/applications/test/polynomialEqns/quadraticEqn/Make/files
@@ -0,0 +1,3 @@
+Test-quadraticEqn.C
+
+EXE = $(FOAM_USER_APPBIN)/Test-quadraticEqn
diff --git a/applications/test/polynomialEqns/quadraticEqn/Make/options b/applications/test/polynomialEqns/quadraticEqn/Make/options
new file mode 100644
index 0000000000000000000000000000000000000000..4c3dd783cb4170feefb3f5385510a83257b43b18
--- /dev/null
+++ b/applications/test/polynomialEqns/quadraticEqn/Make/options
@@ -0,0 +1,3 @@
+EXE_INC =
+
+EXE_LIBS =
diff --git a/applications/test/polynomialEqns/quadraticEqn/Test-quadraticEqn.C b/applications/test/polynomialEqns/quadraticEqn/Test-quadraticEqn.C
new file mode 100644
index 0000000000000000000000000000000000000000..61e28e394ce49b0f472b07a3ec51f895d7d06134
--- /dev/null
+++ b/applications/test/polynomialEqns/quadraticEqn/Test-quadraticEqn.C
@@ -0,0 +1,321 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  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 <http://www.gnu.org/licenses/>.
+
+Application
+    Test-quadraticEqn
+
+Description
+    Tests for \c quadraticEqn constructors, and member functions.
+
+\*---------------------------------------------------------------------------*/
+
+#include <ctime>
+#include <random>
+
+#include "quadraticEqn.H"
+#include "vector2D.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.
+void cmp
+(
+    const word& msg,
+    const scalar& x,
+    const scalar& y,
+    const scalar relTol = 1e-8,  //<! are values the same within 8 decimals
+    const scalar absTol = 0      //<! useful for cmps near zero
+)
+{
+    Info<< msg << x << endl;
+
+    unsigned nFail = 0;
+
+    if (max(absTol, relTol*max(mag(x), mag(y))) < mag(x - y))
+    {
+        ++nFail;
+    }
+
+    if (nFail)
+    {
+        Info<< nl
+            << "        #### Fail in " << nFail << " comps ####" << nl << endl;
+        ++nFail_;
+    }
+    ++nTest_;
+}
+
+
+// Create each constructor of quadraticEqn, and print output
+void test_constructors()
+{
+    #if 0
+    {
+        Info<< "# Construct null:" << nl;
+        const quadraticEqn T();
+        Info<< T << endl;
+    }
+    #endif
+
+    {
+        Info<< "# Construct initialized to zero:" << nl;
+        const quadraticEqn T(Zero);
+        Info<< T << endl;
+    }
+    {
+        Info<< "# Construct from components:" << nl;
+        const quadraticEqn T(1, 2, 3);
+        Info<< T << endl;
+    }
+}
+
+
+// Execute each member function of quadraticEqn, and print output
+void test_member_funcs()
+{
+    const quadraticEqn T(1, -6, -2);
+    const scalar x = 9.7;
+
+    Info<< "# Operands: " << nl
+        << "  quadraticEqn = " << T << nl
+        << "  x = " << x << endl;
+
+    {
+        Info<< "# Access:" << nl;
+        cmp("  a = ",  1, T.a());
+        cmp("  b = ", -6, T.b());
+        cmp("  c = ", -2, T.c());
+    }
+    {
+        Info<< "# Evaluate:" << nl;
+        cmp("  T.value(x) = ", T.value(x), 33.88999999999999);
+        cmp("  T.derivative(x) = ", T.derivative(x), 13.399999999999999);
+        cmp
+        (
+            "  T.error(x) = ",
+            T.error(x),
+            1.9017999999999998e-14,
+            1e-8,
+            1e-8
+        );
+
+        const vector2D rts(6.31662479, -0.31662479);
+        forAll(rts, i)
+        {
+            cmp("  T.roots() = ", T.roots()[i], rts[i]);
+        }
+    }
+    {
+        Info<< "# Special cases for the root evaluation: " << nl;
+
+        {
+            Info<< "  Two distinct real roots:" << nl;
+            const quadraticEqn Tc(1.0, -11.0, 24.0);
+            const vector2D rts(8, 3);
+            forAll(rts, i)
+            {
+                cmp("    root = ", Tc.roots()[i], rts[i]);
+            }
+        }
+        {
+            Info<< "  Two identical real roots:" << nl;
+            const quadraticEqn Tc(1.0, -8.0, 16.0);
+            const vector2D rts(4, 4);
+            forAll(rts, i)
+            {
+                cmp("    root = ", Tc.roots()[i], rts[i]);
+            }
+        }
+        {
+            Info<< "  One complex conjugate-pair root:" << nl;
+            const quadraticEqn Tc(2.0, -2.0, 1.0);
+            const vector2D rts(0.5, -0.5);
+            forAll(rts, i)
+            {
+                cmp("    root = ", Tc.roots()[i], rts[i], 1e-8, 1e-8);
+            }
+        }
+        {
+            Info<< "  Cancellation problem:" << nl;
+            const quadraticEqn Tc(1.0, 68.5, 0.1);
+            const vector2D rts(-6.84985401e+01, -1.45988513e-03);
+            forAll(rts, i)
+            {
+                cmp("    root = ", Tc.roots()[i], rts[i], 1e-8, 1e-8);
+            }
+        }
+    }
+}
+
+
+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 = 0;
+
+    do
+    {
+        x = scalar(distribution(generator));
+    }
+    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;
+        ++nFail_;
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Main Program  * * * * * * * * * * * * * * * //
+
+int main()
+{
+    Info<< nl << "    ## Test constructors: ##" << nl;
+    test_constructors();
+
+    Info<< nl << "    ## Test member functions: ##" << nl;
+    test_member_funcs();
+
+
+    // Sanity checks
+    const scalar tol = 5;
+
+    const label nTests = 1000000;
+    for (label t = 0; t < nTests; ++t)
+    {
+        test
+        (
+            quadraticEqn
+            (
+                randomScalar(1e-50, 1e+50),
+                randomScalar(1e-50, 1e+50),
+                randomScalar(1e-50, 1e+50)
+            ),
+            tol
+        );
+        ++nTest_;
+    }
+    Info << nTests << " scalar quadratic equations were tested." << endl;
+
+    const label coeffMin = -9, coeffMax = 10, nCoeff = coeffMax - coeffMin;
+    for (label a = coeffMin; a < coeffMax; ++a)
+    {
+        for (label b = coeffMin; b < coeffMax; ++b)
+        {
+            for (label c = coeffMin; c < coeffMax; ++c)
+            {
+                test(quadraticEqn(a, b, c), tol);
+                ++nTest_;
+            }
+        }
+    }
+    Info<< nCoeff*nCoeff*nCoeff
+        << " label quadratic equations were tested." << endl;
+
+
+    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/src/OpenFOAM/primitives/polynomialEqns/cubicEqn/cubicEqn.C b/src/OpenFOAM/primitives/polynomialEqns/cubicEqn/cubicEqn.C
index 35ad8934417d6bb94640853faa772fcc13c82dcc..fc69445e9708b152c56ff12bb530436375008004 100644
--- a/src/OpenFOAM/primitives/polynomialEqns/cubicEqn/cubicEqn.C
+++ b/src/OpenFOAM/primitives/polynomialEqns/cubicEqn/cubicEqn.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2017 OpenFOAM Foundation
+    Copyright (C) 2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -33,132 +34,104 @@ License
 
 Foam::Roots<3> Foam::cubicEqn::roots() const
 {
-    /*
-
-    This function solves a cubic equation of the following form:
-
-        a*x^3 + b*x^2 + c*x + d = 0
-          x^3 + B*x^2 + C*x + D = 0
-
-    The following two substitutions are used:
-
-        x = t - B/3
-        t = w - P/3/w
-
-    This reduces the problem to a quadratic in w^3.
-
-        w^6 + Q*w^3 - P = 0
-
-    Where Q and P are given in the code below.
-
-    The properties of the cubic can be related to the properties of this
-    quadratic in w^3. If it has a repeated root a zero, the cubic has a tripl
-    root. If it has a repeated root not at zero, the cubic has two real roots,
-    one repeated and one not. If it has two complex roots, the cubic has three
-    real roots. If it has two real roots, then the cubic has one real root and
-    two complex roots.
-
-    This is solved for the most numerically accurate value of w^3. See the
-    quadratic function for details on how to pick a value. This single value of
-    w^3 can yield up to three cube roots for w, which relate to the three
-    solutions for x.
-
-    Only a single root, or pair of conjugate roots, is directly evaluated; the
-    one, or ones with the lowest relative numerical error. Root identities are
-    then used to recover the remaining roots, possibly utilising a quadratic
-    and/or linear solution. This seems to be a good way of maintaining the
-    accuracy of roots at very different magnitudes.
-
-    */
-
     const scalar a = this->a();
     const scalar b = this->b();
     const scalar c = this->c();
     const scalar d = this->d();
 
-    if (a == 0)
+    // Check the leading term in the cubic eqn exists
+    if (mag(a) < VSMALL)
     {
         return Roots<3>(quadraticEqn(b, c, d).roots(), roots::nan, 0);
     }
 
-    // This is assumed not to over- or under-flow. If it does, all bets are off.
-    const scalar p = c*a - b*b/3;
+    // (JLM:p. 2246) [p = a*c - b*b/3]
+    const scalar w = a*c;
+    const scalar p = -(fma(-a, c, w) + fma(b, b/3.0, -w));
     const scalar q = b*b*b*scalar(2)/27 - b*c*a/3 + d*a*a;
-    const scalar disc = p*p*p/27 + q*q/4;
+    const scalar numDiscr = p*p*p/27 + q*q/4;
+    const scalar discr = (mag(numDiscr) > SMALL) ? numDiscr : 0;
 
-    // How many roots of what types are available?
-    const bool oneReal = disc == 0 && p == 0;
-    const bool twoReal = disc == 0 && p != 0;
-    const bool threeReal = disc < 0;
-    //const bool oneRealTwoComplex = disc > 0;
+    // Determine the number and types of the roots
+    const bool threeReal = discr < 0;
+    const bool oneRealTwoComplex = discr > 0;
+    const bool twoReal = //p != 0; && discr == 0;
+        (mag(p) > VSMALL) && !(threeReal || oneRealTwoComplex);
+    // const bool oneReal = p == 0 && discr == 0;
 
     static const scalar sqrt3 = sqrt(3.0);
 
-    scalar x;
+    scalar x = 0;
 
-    if (oneReal)
-    {
-        const Roots<1> r = linearEqn(a, b/3).roots();
-        return Roots<3>(r.type(0), r[0]);
-    }
-    else if (twoReal)
-    {
-        if (q*b > 0)
-        {
-            x = - 2*cbrt(q/2) - b/3;
-        }
-        else
-        {
-            x = cbrt(q/2) - b/3;
-            const Roots<1> r = linearEqn(- a, x).roots();
-            return Roots<3>(Roots<2>(r, r), linearEqn(x*x, a*d).roots());
-        }
-    }
-    else if (threeReal)
+    if (threeReal)
     {
-        const scalar wCbRe = - q/2, wCbIm = sqrt(- disc);
+        const scalar wCbRe = -q/2;
+        const scalar wCbIm = sqrt(-discr);
         const scalar wAbs = cbrt(hypot(wCbRe, wCbIm));
         const scalar wArg = atan2(wCbIm, wCbRe)/3;
-        const scalar wRe = wAbs*cos(wArg), wIm = wAbs*sin(wArg);
+        const scalar wRe = wAbs*cos(wArg);
+        const scalar wIm = wAbs*sin(wArg);
+
         if (b > 0)
         {
-            x = - wRe - mag(wIm)*sqrt3 - b/3;
+            x = -wRe - mag(wIm)*sqrt3 - b/3;
         }
         else
         {
             x = 2*wRe - b/3;
         }
     }
-    else // if (oneRealTwoComplex)
+    else if (oneRealTwoComplex)
     {
-        const scalar wCb = - q/2 - sign(q)*sqrt(disc);
+        const scalar wCb = -q/2 - sign(q)*sqrt(discr);
         const scalar w = cbrt(wCb);
         const scalar t = w - p/(3*w);
+
         if (p + t*b < 0)
         {
             x = t - b/3;
         }
         else
         {
-            const scalar xRe = - t/2 - b/3, xIm = sqrt3/2*(w + p/3/w);
-            x = - a*a*d/(xRe*xRe + xIm*xIm);
+            const scalar xRe = -t/2 - b/3;
+            const scalar xIm = sqrt3/2*(w + p/3/w);
 
-            // This form of solving for the remaining roots seems more stable
-            // for this case. This has been determined by trial and error.
             return
                 Roots<3>
                 (
-                    linearEqn(- a, x).roots(),
-                    quadraticEqn(a*x, x*x + b*x, - a*d).roots()
+                    Roots<1>(roots::real, -a*d/(xRe*xRe + xIm*xIm)),
+                    Roots<2>
+                    (
+                        Roots<1>(roots::complex, xRe),
+                        Roots<1>(roots::complex, xIm)
+                    )
                 );
         }
     }
+    else if (twoReal)
+    {
+        if (q*b > 0)
+        {
+            x = -2*cbrt(q/2) - b/3;
+        }
+        else
+        {
+            x = cbrt(q/2) - b/3;
+            const Roots<1> r(linearEqn(-a, x).roots());
+            return Roots<3>(Roots<2>(r, r), linearEqn(x*x, a*d).roots());
+        }
+    }
+    else // (oneReal)
+    {
+        const Roots<1> r(linearEqn(a, b/3).roots());
+        return Roots<3>(r.type(0), r[0]);
+    }
 
     return
         Roots<3>
         (
-            linearEqn(- a, x).roots(),
-            quadraticEqn(- x*x, c*x + a*d, d*x).roots()
+            linearEqn(-a, x).roots(),
+            quadraticEqn(-x*x, c*x + a*d, d*x).roots()
         );
 }
 
diff --git a/src/OpenFOAM/primitives/polynomialEqns/cubicEqn/cubicEqn.H b/src/OpenFOAM/primitives/polynomialEqns/cubicEqn/cubicEqn.H
index f8ba53e750e920b1a2c4f1f704b94e17d67fb093..3e90378981678023713421bbd4cbeb5124372971 100644
--- a/src/OpenFOAM/primitives/polynomialEqns/cubicEqn/cubicEqn.H
+++ b/src/OpenFOAM/primitives/polynomialEqns/cubicEqn/cubicEqn.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2017 OpenFOAM Foundation
+    Copyright (C) 2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,7 +28,67 @@ Class
     Foam::cubicEqn
 
 Description
-    Cubic equation of the form a*x^3 + b*x^2 + c*x + d = 0
+    Container to encapsulate various operations for
+    cubic equation of the forms with real coefficients:
+
+    \f[
+        a*x^3 + b*x^2 + c*x + d = 0
+          x^3 + B*x^2 + C*x + D = 0
+    \f]
+
+    The following two substitutions into the above forms are used:
+
+    \f[
+        x = t - B/3
+        t = w - P/3/w
+    \f]
+
+    This reduces the problem to a quadratic in \c w^3:
+
+    \f[
+        w^6 + Q*w^3 - P = 0
+    \f]
+
+    where \c Q and \c P are given in the code.
+
+    The properties of the cubic can be related to the properties of this
+    quadratic in \c w^3.
+
+    If the quadratic eqn has two identical real roots at zero, three identical
+    real roots exist in the cubic eqn.
+
+    If the quadratic eqn has two identical real roots at non-zero, two identical
+    and one distinct real roots exist in the cubic eqn.
+
+    If the quadratic eqn has two complex roots (a complex conjugate pair),
+    three distinct real roots exist in the cubic eqn.
+
+    If the quadratic eqn has two distinct real roots, one real root and two
+    complex roots (a complex conjugate pair) exist in the cubic eqn.
+
+    The quadratic eqn is solved for the most numerically accurate value
+    of \c w^3. See the \link quadraticEqn.H \endlink for details on how to
+    pick a value. This single value of \c w^3 can yield up to three cube
+    roots for \c w, which relate to the three solutions for \c x.
+
+    Only a single root, or pair of conjugate roots, is directly evaluated; the
+    one, or ones with the lowest relative numerical error. Root identities are
+    then used to recover the remaining roots, possibly utilising a quadratic
+    and/or linear solution. This seems to be a good way of maintaining the
+    accuracy of roots at very different magnitudes.
+
+    Reference:
+    \verbatim
+        Kahan's algo. to compute 'p' using fused multiply-adds (tag:JML):
+            Jeannerod, C. P., Louvet, N., & Muller, J. M. (2013).
+            Further analysis of Kahan's algorithm for the accurate
+            computation of 2× 2 determinants.
+            Mathematics of Computation, 82(284), 2245-2264.
+            DOI:10.1090/S0025-5718-2013-02679-8
+    \endverbatim
+
+See also
+    Test-cubicEqn.C
 
 SourceFiles
     cubicEqnI.H
@@ -79,28 +140,37 @@ public:
 
     // Member Functions
 
-        // Access
+    // Access
+
+        inline scalar a() const;
+        inline scalar b() const;
+        inline scalar c() const;
+        inline scalar d() const;
 
-            inline scalar a() const;
-            inline scalar b() const;
-            inline scalar c() const;
-            inline scalar d() const;
+        inline scalar& a();
+        inline scalar& b();
+        inline scalar& c();
+        inline scalar& d();
 
-            inline scalar& a();
-            inline scalar& b();
-            inline scalar& c();
-            inline scalar& d();
+    // Evaluate
 
-        //- Evaluate at x
+        //- Evaluate the cubic equation at x
         inline scalar value(const scalar x) const;
 
-        //- Evaluate the derivative at x
+        //- Evaluate the derivative of the cubic equation at x
         inline scalar derivative(const scalar x) const;
 
-        //- Estimate the error of evaluation at x
+        //- Estimate the error of evaluation of the cubic equation at x
         inline scalar error(const scalar x) const;
 
-        //- Get the roots
+        //- Return the roots of the cubic equation with no particular order
+        //  if discriminant > 0: return three distinct real roots
+        //  if discriminant < 0: return one real root and one complex root
+        //                       (one member of the complex conjugate pair)
+        //  if discriminant = 0: return two identical roots,
+        //                       and one distinct real root
+        //  if identical zero Hessian: return three identical real roots
+        //  where the discriminant = - 4p^3 - 27q^2.
         Roots<3> roots() const;
 };
 
diff --git a/src/OpenFOAM/primitives/polynomialEqns/linearEqn/linearEqn.H b/src/OpenFOAM/primitives/polynomialEqns/linearEqn/linearEqn.H
index adb6f39591a564a0982b0a9632d748aefa6908c6..c898aef1ab1566ed2ccfa3ee678f33f5b3a62d68 100644
--- a/src/OpenFOAM/primitives/polynomialEqns/linearEqn/linearEqn.H
+++ b/src/OpenFOAM/primitives/polynomialEqns/linearEqn/linearEqn.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2017 OpenFOAM Foundation
+    Copyright (C) 2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,7 +28,16 @@ Class
     Foam::linearEqn
 
 Description
-    Linear equation of the form a*x + b = 0
+    Container to encapsulate various operations for
+    linear equation of the forms with real coefficients:
+
+    \f[
+        a*x + b = 0
+          x + B = 0
+    \f]
+
+See also
+    Test-linearEqn.C
 
 SourceFiles
     linearEqnI.H
@@ -72,24 +82,26 @@ public:
 
     // Member Functions
 
-        // Access
+    // Access
+
+        inline scalar a() const;
+        inline scalar b() const;
 
-            inline scalar a() const;
-            inline scalar b() const;
+        inline scalar& a();
+        inline scalar& b();
 
-            inline scalar& a();
-            inline scalar& b();
+    // Evaluate
 
-        //- Evaluate at x
+        //- Evaluate the linear equation at x
         inline scalar value(const scalar x) const;
 
-        //- Evaluate the derivative at x
+        //- Evaluate the derivative of the linear equation at x
         inline scalar derivative(const scalar x) const;
 
-        //- Estimate the error of evaluation at x
+        //- Estimate the error of evaluation of the linear equation at x
         inline scalar error(const scalar x) const;
 
-        //- Get the roots
+        //- Return the real root of the linear equation
         inline Roots<1> roots() const;
 };
 
diff --git a/src/OpenFOAM/primitives/polynomialEqns/linearEqn/linearEqnI.H b/src/OpenFOAM/primitives/polynomialEqns/linearEqn/linearEqnI.H
index 29b2013341f4157e83a74caa9323c8983df778fd..0f2fda08144847011443ae12f950778f8831919b 100644
--- a/src/OpenFOAM/primitives/polynomialEqns/linearEqn/linearEqnI.H
+++ b/src/OpenFOAM/primitives/polynomialEqns/linearEqn/linearEqnI.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2017 OpenFOAM Foundation
+    Copyright (C) 2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -90,29 +91,19 @@ inline Foam::scalar Foam::linearEqn::error(const scalar x) const
 
 inline Foam::Roots<1> Foam::linearEqn::roots() const
 {
-    /*
-
-    This function solves a linear equation of the following form:
-
-        a*x + b = 0
-          x + B = 0
-
-    */
-
     const scalar a = this->a();
     const scalar b = this->b();
 
-    if (a == 0)
+    if (mag(a) < VSMALL)
     {
         return Roots<1>(roots::nan, 0);
     }
-
-    if (mag(b/VGREAT) >= mag(a))
+    else if (mag(b/VGREAT) >= mag(a))
     {
         return Roots<1>(sign(a) == sign(b) ? roots::negInf : roots::posInf, 0);
     }
 
-    return Roots<1>(roots::real, - b/a);
+    return Roots<1>(roots::real, -b/a);
 }
 
 
diff --git a/src/OpenFOAM/primitives/polynomialEqns/quadraticEqn/quadraticEqn.C b/src/OpenFOAM/primitives/polynomialEqns/quadraticEqn/quadraticEqn.C
index ca6c3cfd3b377d8777c0b3e984d679a0f7af243c..910f9375ce6899989e141a76e9cff27d35355480 100644
--- a/src/OpenFOAM/primitives/polynomialEqns/quadraticEqn/quadraticEqn.C
+++ b/src/OpenFOAM/primitives/polynomialEqns/quadraticEqn/quadraticEqn.C
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2017 OpenFOAM Foundation
+    Copyright (C) 2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -32,58 +33,42 @@ License
 
 Foam::Roots<2> Foam::quadraticEqn::roots() const
 {
-    /*
-
-    This function solves a quadraticEqn equation of the following form:
-
-        a*x^2 + b*x + c = 0
-          x^2 + B*x + C = 0
-
-    The quadraticEqn formula is as follows:
-
-        x = - B/2 +- sqrt(B*B - 4*C)/2
-
-    If the sqrt generates a complex number, this provides the result. If not
-    then the real root with the smallest floating point error is calculated.
-
-        x0 = - B/2 - sign(B)*sqrt(B*B - 4*C)/2
-
-    The other root is the obtained using an identity.
-
-        x1 = C/x0
-
-    */
-
     const scalar a = this->a();
     const scalar b = this->b();
     const scalar c = this->c();
 
-    if (a == 0)
+    // Check the leading term in the quadratic eqn exists
+    if (mag(a) < VSMALL)
     {
         return Roots<2>(linearEqn(b, c).roots(), roots::nan, 0);
     }
 
-    // This is assumed not to over- or under-flow. If it does, all bets are off.
-    const scalar disc = b*b/4 - a*c;
+    // (JLM:p. 2246) [discriminant = b*b/4 - a*c]
+    const scalar w = a*c;
+    const scalar numDiscr = fma(-a, c, w) + fma(b, b/4, -w);
+    const scalar discr = (mag(numDiscr) > SMALL) ? numDiscr : 0;
 
-    // How many roots of what types are available?
-    const bool oneReal = disc == 0;
-    const bool twoReal = disc > 0;
-    //const bool twoComplex = disc < 0;
+    // Find how many roots of what types are available
+    const bool twoReal = discr > 0;
+    const bool twoComplex = discr < 0;
+    //const bool oneReal = discr == 0;
 
-    if (oneReal)
+    if (twoReal)
     {
-        const Roots<1> r = linearEqn(a, b/2).roots();
-        return Roots<2>(r, r);
+        // (F:Exp. 8.9)
+        const scalar x = -b/2 - sign(b)*sqrt(discr);
+        return Roots<2>(linearEqn(-a, x).roots(), linearEqn(-x, c).roots());
     }
-    else if (twoReal)
+    else if (twoComplex)
     {
-        const scalar x = - b/2 - sign(b)*sqrt(disc);
-        return Roots<2>(linearEqn(- a, x).roots(), linearEqn(- x, c).roots());
+        const Roots<1> xRe(roots::type::complex, -b/2/a);
+        const Roots<1> xIm(roots::type::complex, sign(b)*sqrt(mag(discr))/a);
+        return Roots<2>(xRe, xIm);
     }
-    else // if (twoComplex)
+    else // (oneReal)
     {
-        return Roots<2>(roots::complex, 0);
+        const Roots<1> r(linearEqn(a, b/2).roots());
+        return Roots<2>(r, r);
     }
 }
 
diff --git a/src/OpenFOAM/primitives/polynomialEqns/quadraticEqn/quadraticEqn.H b/src/OpenFOAM/primitives/polynomialEqns/quadraticEqn/quadraticEqn.H
index 8e6d6dd9fc0ef6360dbf01a7953432985b614832..c68da9b9ec4d9014699e535f0692b02a95092a10 100644
--- a/src/OpenFOAM/primitives/polynomialEqns/quadraticEqn/quadraticEqn.H
+++ b/src/OpenFOAM/primitives/polynomialEqns/quadraticEqn/quadraticEqn.H
@@ -6,6 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2017 OpenFOAM Foundation
+    Copyright (C) 2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,7 +28,43 @@ Class
     Foam::quadraticEqn
 
 Description
-    Quadratic equation of the form a*x^2 + b*x + c = 0
+    Container to encapsulate various operations for
+    quadratic equation of the forms with real coefficients:
+
+    \f[
+        a*x^2 + b*x + c = 0
+          x^2 + B*x + C = 0
+    \f]
+
+    The expressions for the roots of \c quadraticEqn are as follows:
+
+    \f[
+        x1 = - (b + sign(b) sqrt(b^2 - 4ac)/(2*a))
+
+        x2 = c/(a*x1)
+    \f]
+
+    where \c (b^2 - 4ac) is evaluated by fused multiply-adds to avoid
+    detrimental cancellation.
+
+    Reference:
+    \verbatim
+        Cancellation-avoiding quadratic formula (tag:F):
+            Ford, W. (2014).
+            Numerical linear algebra with applications: Using MATLAB.
+            London: Elsevier/Academic Press.
+            DOI:10.1016/C2011-0-07533-6
+
+        Kahan's algo. to compute 'b^2-a*c' using fused multiply-adds (tag:JML):
+            Jeannerod, C. P., Louvet, N., & Muller, J. M. (2013).
+            Further analysis of Kahan's algorithm for the accurate
+            computation of 2× 2 determinants.
+            Mathematics of Computation, 82(284), 2245-2264.
+            DOI:10.1090/S0025-5718-2013-02679-8
+    \endverbatim
+
+See also
+    Test-quadraticEqn.C
 
 SourceFiles
     quadraticEqnI.H
@@ -73,26 +110,31 @@ public:
 
     // Member Functions
 
-        // Access
+    // Access
+
+        inline scalar a() const;
+        inline scalar b() const;
+        inline scalar c() const;
 
-            inline scalar a() const;
-            inline scalar b() const;
-            inline scalar c() const;
+        inline scalar& a();
+        inline scalar& b();
+        inline scalar& c();
 
-            inline scalar& a();
-            inline scalar& b();
-            inline scalar& c();
+    // Evaluate
 
-        //- Evaluate at x
+        //- Evaluate the quadratic equation at x
         inline scalar value(const scalar x) const;
 
-        //- Evaluate the derivative at x
+        //- Evaluate the derivative of the quadratic equation at x
         inline scalar derivative(const scalar x) const;
 
-        //- Estimate the error of evaluation at x
+        //- Estimate the error of evaluation of the quadratic equation at x
         inline scalar error(const scalar x) const;
 
-        //- Get the roots
+        //- Return the roots of the quadratic equation with no particular order
+        //  if discriminant > 0: return two distinct real roots
+        //  if discriminant < 0: return one of the complex conjugate-pair roots
+        //  otherwise          : return two identical real roots
         Roots<2> roots() const;
 };