From 1b387c723f9d58186cbac38686f59ceab07f1a5d Mon Sep 17 00:00:00 2001
From: Christoph Lehmann <christoph.lehmann@ufz.de>
Date: Tue, 19 Apr 2022 10:39:22 +0200
Subject: [PATCH] [T] Extended quadrature tests to all schemes and orders

---
 .../MathLib/TestGaussLegendreIntegration.cpp  | 637 +++++++-----------
 1 file changed, 241 insertions(+), 396 deletions(-)

diff --git a/Tests/MathLib/TestGaussLegendreIntegration.cpp b/Tests/MathLib/TestGaussLegendreIntegration.cpp
index 8b8898264d4..653ac0bd376 100644
--- a/Tests/MathLib/TestGaussLegendreIntegration.cpp
+++ b/Tests/MathLib/TestGaussLegendreIntegration.cpp
@@ -9,6 +9,7 @@
 
 #include <gtest/gtest.h>
 
+#include <algorithm>
 #include <cmath>
 #include <limits>
 
@@ -17,10 +18,17 @@
 #include "MeshLib/Elements/Element.h"
 #include "MeshLib/IO/VtkIO/VtuInterface.h"
 #include "MeshLib/MeshSubset.h"
+
+#ifdef USE_PETSC
+#include "MeshLib/NodePartitionedMesh.h"
+#endif
+
 #include "NumLib/DOF/LocalToGlobalIndexMap.h"
 #include "NumLib/Fem/InitShapeMatrices.h"
 #include "NumLib/Fem/ShapeMatrixPolicy.h"
+#include "Polynomials.h"
 #include "ProcessLib/Utils/CreateLocalAssemblers.h"
+#include "Tests/MeshLib/UnitCube.h"
 #include "Tests/VectorUtils.h"
 
 namespace GaussLegendreTest
@@ -47,7 +55,7 @@ static std::array<double, 3> interpolateNodeCoordinates(
     return res;
 }
 
-class LocalAssemblerDataInterface
+class LocalAssemblerInterface
 {
 public:
     using Function = std::function<double(std::array<double, 3> const&)>;
@@ -55,20 +63,20 @@ public:
     virtual double integrate(Function const& f,
                              unsigned const integration_order) const = 0;
 
-    virtual ~LocalAssemblerDataInterface() = default;
+    virtual ~LocalAssemblerInterface() = default;
 };
 
 template <typename ShapeFunction, typename IntegrationMethod, int GlobalDim>
-class LocalAssemblerData : public LocalAssemblerDataInterface
+class LocalAssembler : public LocalAssemblerInterface
 {
     using ShapeMatricesType = ShapeMatrixPolicyType<ShapeFunction, GlobalDim>;
     using ShapeMatrices = typename ShapeMatricesType::ShapeMatrices;
 
 public:
-    LocalAssemblerData(MeshLib::Element const& e,
-                       std::size_t const /*local_matrix_size*/,
-                       bool is_axially_symmetric,
-                       unsigned const /*integration_order*/)
+    LocalAssembler(MeshLib::Element const& e,
+                   std::size_t const /*local_matrix_size*/,
+                   bool is_axially_symmetric,
+                   unsigned const /*integration_order*/)
         : _e(e)
     {
         if (is_axially_symmetric)
@@ -91,16 +99,12 @@ public:
 
         for (unsigned ip = 0; ip < sms.size(); ++ip)
         {
-            // We need to assert that detJ is constant in each element in order
-            // to be sure that in every element that we are integrating over the
-            // effective polynomial degree is the one we expect.
-            EXPECT_NEAR(sms[0].detJ, sms[ip].detJ,
-                        std::numeric_limits<double>::epsilon());
             auto const& N = sms[ip].N;
+            auto const weight =
+                integration_method.getWeightedPoint(ip).getWeight();
             auto const coords = interpolateNodeCoordinates(_e, N);
             auto const function_value = f(coords);
-            integral += function_value * sms[ip].detJ *
-                        integration_method.getWeightedPoint(ip).getWeight();
+            integral += function_value * sms[ip].detJ * weight;
         }
 
         return integral;
@@ -113,8 +117,6 @@ private:
 class IntegrationTestProcess
 {
 public:
-    using LocalAssembler = LocalAssemblerDataInterface;
-
     IntegrationTestProcess(MeshLib::Mesh const& mesh,
                            unsigned const integration_order)
         : _integration_order(integration_order),
@@ -124,15 +126,14 @@ public:
             _mesh_subset_all_nodes};
 
         _dof_table = std::make_unique<NumLib::LocalToGlobalIndexMap>(
-            std::move(all_mesh_subsets), NumLib::ComponentOrder::BY_COMPONENT);
+            std::move(all_mesh_subsets), NumLib::ComponentOrder::BY_LOCATION);
 
-        // createAssemblers(mesh);
-        ProcessLib::createLocalAssemblers<LocalAssemblerData>(
+        ProcessLib::createLocalAssemblers<LocalAssembler>(
             mesh.getDimension(), mesh.getElements(), *_dof_table,
             _local_assemblers, mesh.isAxiallySymmetric(), _integration_order);
     }
 
-    double integrate(LocalAssembler::Function const& f) const
+    double integrate(LocalAssemblerInterface::Function const& f) const
     {
         double integral = 0;
         for (auto const& la : _local_assemblers)
@@ -149,473 +150,317 @@ private:
     MeshLib::MeshSubset _mesh_subset_all_nodes;
     std::unique_ptr<NumLib::LocalToGlobalIndexMap> _dof_table;
 
-    std::vector<std::unique_ptr<LocalAssembler>> _local_assemblers;
+    std::vector<std::unique_ptr<LocalAssemblerInterface>> _local_assemblers;
 };
 
-struct FBase
-{
-private:
-    static std::vector<double> initCoeffs(std::size_t const num_coeffs)
-    {
-        std::vector<double> cs(num_coeffs);
-        fillVectorRandomly(cs);
-        return cs;
-    }
-
-public:
-    explicit FBase(std::size_t const num_coeffs)
-        : coeffs(initCoeffs(num_coeffs))
-    {
-    }
+}  // namespace GaussLegendreTest
 
-    virtual double operator()(
-        std::array<double, 3> const& /*coords*/) const = 0;
-    virtual double getAnalyticalIntegralOverUnitCube() const = 0;
+/* *****************************************************************************
+ *
+ * The idea behind the tests in this file is to integrate polynomials of
+ * different degree over the unit cube.
+ *
+ * Gauss-Legendre integration should be able to exactly integrate those up to a
+ * certain degree.
+ *
+ * The coefficients of the tested polynomials are chosen randomly.
+ *
+ **************************************************************************** */
 
-    LocalAssemblerDataInterface::Function getClosure() const
+// Test fixture.
+class MathLibIntegrationGaussLegendreTestBase : public ::testing::Test
+{
+protected:
+    // Up to the polynomial order returned by this function the numerical
+    // integration should be exact in the base case test.
+    static unsigned maxExactPolynomialOrderBase(
+        unsigned const integration_order)
     {
-        return [this](std::array<double, 3> const& coords)
-        { return this->operator()(coords); };
+        // Base case is defined only for polynomials up to order 2.
+        return std::min(2u, 2 * integration_order - 1);
     }
 
-    virtual ~FBase() = default;
-
-    std::vector<double> const coeffs;
-};
-
-struct FConst final : FBase
-{
-    FConst() : FBase(1) {}
-
-    double operator()(std::array<double, 3> const& /*unused*/) const override
+    // Up to the polynomial order returned by this function the numerical
+    // integration should be exact in the separable polynomial test.
+    static unsigned maxExactPolynomialOrderSeparable(
+        unsigned const integration_order)
     {
-        return coeffs[0];
+        return 2 * integration_order - 1;
     }
 
-    double getAnalyticalIntegralOverUnitCube() const override
+    // Up to the polynomial order returned by this function the numerical
+    // integration should be exact in the nonseparable polynomial test.
+    static unsigned maxExactPolynomialOrderNonseparable(
+        unsigned const integration_order)
     {
-        return coeffs[0];
+        return 2 * integration_order - 1;
     }
 };
 
-struct FLin final : FBase
+template <typename MeshElementType>
+class MathLibIntegrationGaussLegendreTest
+    : public MathLibIntegrationGaussLegendreTestBase
 {
-    FLin() : FBase(4) {}
-
-    double operator()(std::array<double, 3> const& coords) const override
-    {
-        auto const x = coords[0];
-        auto const y = coords[1];
-        auto const z = coords[2];
-        return coeffs[0] + coeffs[1] * x + coeffs[2] * y + coeffs[3] * z;
-    }
-
-    double getAnalyticalIntegralOverUnitCube() const override
-    {
-        return coeffs[0];
-    }
 };
 
-struct FQuad final : FBase
+// Specialization for triangles.
+template <>
+class MathLibIntegrationGaussLegendreTest<MeshLib::Tri>
+    : public MathLibIntegrationGaussLegendreTestBase
 {
-    FQuad() : FBase(10) {}
-
-    double operator()(std::array<double, 3> const& coords) const override
+protected:
+    static unsigned maxExactPolynomialOrderSeparable(
+        unsigned const integration_order)
     {
-        auto const x = coords[0];
-        auto const y = coords[1];
-        auto const z = coords[2];
-        return coeffs[0] + coeffs[1] * x + coeffs[2] * y + coeffs[3] * z +
-               coeffs[4] * x * y + coeffs[5] * y * z + coeffs[6] * z * x +
-               coeffs[7] * x * x + coeffs[8] * y * y + coeffs[9] * z * z;
+        switch (integration_order)
+        {
+            case 1:
+                return 0;  // constants
+            case 2:
+                return 1;  // most complicated monomial is x*y*z
+            case 3:
+                return 1;  // most complicated monomial is x*y*z
+            case 4:
+                return 2;  // most complicated monomial is x^2 * y^2 * z^2
+        }
+
+        OGS_FATAL("Unsupported integration order {}", integration_order);
     }
 
-    double getAnalyticalIntegralOverUnitCube() const override
+    static unsigned maxExactPolynomialOrderNonseparable(
+        unsigned const integration_order)
     {
-        double const a = -.5;
-        double const b = .5;
-
-        double const a3 = a * a * a;
-        double const b3 = b * b * b;
+        switch (integration_order)
+        {
+            case 1:
+                return 1;  // at most linear affine functions
+            case 2:
+                return 3;  // most complicated monomial is x^3 or x*y*z
+            case 3:
+                return 3;  // most complicated monomial is x^3 or x*y*z
+            case 4:
+                return 5;  // most complicated monomial is x^5 or x^2 * y^2 * z
+        }
 
-        return coeffs[0] + (coeffs[7] + coeffs[8] + coeffs[9]) * (b3 - a3) / 3.;
+        OGS_FATAL("Unsupported integration order {}", integration_order);
     }
 };
 
-struct F3DSeparablePolynomial final : FBase
+// Specialization for tetrahedra.
+template <>
+class MathLibIntegrationGaussLegendreTest<MeshLib::Tet>
+    : public MathLibIntegrationGaussLegendreTestBase
 {
-    explicit F3DSeparablePolynomial(unsigned polynomial_degree)
-        : FBase(3 * polynomial_degree + 3), _degree(polynomial_degree)
-    {
-    }
-
-    // f(x, y, z) = g(x) * h(y) * i(z)
-    double operator()(std::array<double, 3> const& coords) const override
+protected:
+    static unsigned maxExactPolynomialOrderSeparable(
+        unsigned const integration_order)
     {
-        double res = 1.0;
-        for (unsigned d : {0, 1, 2})
+        switch (integration_order)
         {
-            auto const x = coords[d];
-
-            double poly = 0.0;
-            for (unsigned n = 0; n <= _degree; ++n)
-            {
-                poly += coeffs[n + d * (_degree + 1)] * std::pow(x, n);
-            }
-
-            res *= poly;
+            case 1:
+                return 0;  // constants
+            case 2:
+                return 1;  // most complicated monomial is x*y*z
+            case 3:
+                return 1;  // most complicated monomial is x*y*z
+            case 4:
+                return 1;  // most complicated monomial is x*y*z
         }
 
-        return res;
+        OGS_FATAL("Unsupported integration order {}", integration_order);
     }
 
-    // [ F(x, y, z) ]_a^b = [ G(x) ]_a^b * [ H(y) ]_a^b * [ I(z) ]_a^b
-    double getAnalyticalIntegralOverUnitCube() const override
+    static unsigned maxExactPolynomialOrderNonseparable(
+        unsigned const integration_order)
     {
-        double const a = -.5;
-        double const b = .5;
-
-        double res = 1.0;
-        for (unsigned d : {0, 1, 2})
+        switch (integration_order)
         {
-            double poly = 0.0;
-            for (unsigned n = 0; n <= _degree; ++n)
-            {
-                poly += coeffs[n + d * (_degree + 1)] *
-                        (std::pow(b, n + 1) - std::pow(a, n + 1)) / (n + 1);
-            }
-
-            res *= poly;
+            case 1:
+                return 1;  // at most linear affine functions
+            case 2:
+                return 3;  // most complicated monomial is x^3 or x*y*z
+            case 3:
+                return 5;  // most complicated monomial is x^5 or x^2 * y^2 * z
+            case 4:
+                return 5;  // most complicated monomial is x^5 or x^2 * y^2 * z
         }
 
-        return res;
+        OGS_FATAL("Unsupported integration order {}", integration_order);
     }
-
-private:
-    unsigned const _degree;
 };
 
-unsigned binomial_coefficient(unsigned n, unsigned k)
+// Specialization for prisms.
+template <>
+class MathLibIntegrationGaussLegendreTest<MeshLib::Prism>
+    : public MathLibIntegrationGaussLegendreTestBase
 {
-    EXPECT_GE(n, k);
-    unsigned res = 1;
-
-    for (unsigned i = n; i > k; --i)
+protected:
+    static unsigned maxExactPolynomialOrderSeparable(
+        unsigned const integration_order)
     {
-        res *= i;
-    }
+        switch (integration_order)
+        {
+            case 1:
+                return 0;  // constants
+            case 2:
+                return 1;  // most complicated monomial is x*y*z
+            case 3:
+                return 2;  // most complicated monomial is x^2 * y^2 * z^2
+            case 4:
+                return 2;  // most complicated monomial is x^2 * y^2 * z^2
+        }
 
-    for (unsigned i = n - k; i > 0; --i)
-    {
-        res /= i;
+        OGS_FATAL("Unsupported integration order {}", integration_order);
     }
 
-    return res;
-}
-
-/* This function is a polynomial where for each monomial a_ijk x^i y^j z^k
- * holds: i + j + k <= n, where n is the overall polynomial degree
- */
-struct F3DNonseparablePolynomial final : FBase
-{
-    // The number of coefficients/monomials are obtained as follows: Compute the
-    // number of combinations with repititions when drawing
-    // polynomial_degree times from the set { x, y, z, 1 }
-    explicit F3DNonseparablePolynomial(unsigned polynomial_degree)
-        : FBase(binomial_coefficient(4 + polynomial_degree - 1, 4 - 1)),
-          _degree(polynomial_degree)
+    static unsigned maxExactPolynomialOrderNonseparable(
+        unsigned const integration_order)
     {
+        switch (integration_order)
+        {
+            case 1:
+                return 1;  // at most linear affine functions
+            case 2:
+                return 3;  // most complicated monomial is x^3 or x*y*z
+            case 3:
+                return 5;  // most complicated monomial is x^5 or x^2 * y^2 * z
+            case 4:
+                return 5;  // most complicated monomial is x^5 or x^2 * y^2 * z
+        }
+
+        OGS_FATAL("Unsupported integration order {}", integration_order);
     }
+};
 
-    double operator()(std::array<double, 3> const& coords) const override
+// Specialization for prisms.
+template <>
+class MathLibIntegrationGaussLegendreTest<MeshLib::Pyramid>
+    : public MathLibIntegrationGaussLegendreTestBase
+{
+protected:
+    static unsigned maxExactPolynomialOrderSeparable(
+        unsigned const integration_order)
     {
-        auto const x = coords[0];
-        auto const y = coords[1];
-        auto const z = coords[2];
-
-        double res = 0.0;
-        unsigned index = 0;
-
-        for (unsigned x_deg = 0; x_deg <= _degree; ++x_deg)
+        // Note: In pyramids det J changes over the element. So effectively we
+        // are integrating a polynomial of higher degree than indicated here.
+        switch (integration_order)
         {
-            for (unsigned y_deg = 0; x_deg + y_deg <= _degree; ++y_deg)
-            {
-                for (unsigned z_deg = 0; x_deg + y_deg + z_deg <= _degree;
-                     ++z_deg)
-                {
-                    EXPECT_GT(coeffs.size(), index);
-
-                    res += coeffs[index] * std::pow(x, x_deg) *
-                           std::pow(y, y_deg) * std::pow(z, z_deg);
-
-                    ++index;
-                }
-            }
+            case 1:
+                return 1;  // most complicated monomial is x*y*z
+            case 2:
+                return 1;  // most complicated monomial is x*y*z
+            case 3:
+                return 3;  // most complicated monomial is x^3 * y^3 * z^3
+            case 4:
+                return 3;  // most complicated monomial is x^3 * y^3 * z^3
         }
 
-        EXPECT_EQ(coeffs.size(), index);
-
-        return res;
+        OGS_FATAL("Unsupported integration order {}", integration_order);
     }
 
-    double getAnalyticalIntegralOverUnitCube() const override
+    static unsigned maxExactPolynomialOrderNonseparable(
+        unsigned const integration_order)
     {
-        double const a = -.5;
-        double const b = .5;
-
-        double res = 0.0;
-        unsigned index = 0;
-
-        for (unsigned x_deg = 0; x_deg <= _degree; ++x_deg)
+        // Note: In pyramids det J changes over the element. So effectively we
+        // are integrating a polynomial of higher degree than indicated here.
+        switch (integration_order)
         {
-            for (unsigned y_deg = 0; x_deg + y_deg <= _degree; ++y_deg)
-            {
-                for (unsigned z_deg = 0; x_deg + y_deg + z_deg <= _degree;
-                     ++z_deg)
-                {
-                    EXPECT_GT(coeffs.size(), index);
-
-                    res += coeffs[index] *
-                           (std::pow(b, x_deg + 1) - std::pow(a, x_deg + 1)) /
-                           (x_deg + 1) *
-                           (std::pow(b, y_deg + 1) - std::pow(a, y_deg + 1)) /
-                           (y_deg + 1) *
-                           (std::pow(b, z_deg + 1) - std::pow(a, z_deg + 1)) /
-                           (z_deg + 1);
-
-                    ++index;
-                }
-            }
+            case 1:
+                return 1;  // at most linear affine functions
+            case 2:
+                return 3;  // most complicated monomial is x^3 or x*y*z
+            case 3:
+                return 3;  // most complicated monomial is x^3 or x*y*z
+            case 4:
+                return 3;  // most complicated monomial is x^3 or x*y*z
         }
 
-        EXPECT_EQ(coeffs.size(), index);
-
-        return res;
+        OGS_FATAL("Unsupported integration order {}", integration_order);
     }
-
-private:
-    unsigned const _degree;
 };
 
-std::unique_ptr<FBase> getF(unsigned polynomial_order)
-{
-    std::vector<double> coeffs;
-
-    switch (polynomial_order)
-    {
-        case 0:
-            return std::make_unique<FConst>();
-        case 1:
-            return std::make_unique<FLin>();
-        case 2:
-            return std::make_unique<FQuad>();
-    }
-
-    OGS_FATAL("unsupported polynomial order: {:d}.", polynomial_order);
-}
-
-}  // namespace GaussLegendreTest
+using MeshElementTypes =
+    ::testing::Types<MeshLib::Line, MeshLib::Quad, MeshLib::Hex, MeshLib::Tri,
+                     MeshLib::Tet, MeshLib::Prism, MeshLib::Pyramid>;
 
-/* *****************************************************************************
- *
- * The idea behind the tests in this file is to integrate polynomials of
- * different degree over the unit cube.
- *
- * Gauss-Legendre integration should be able to exactly integrate those up to a
- * certain degree.
- *
- * The coefficients of the tested polynomials are chosen randomly.
- *
- **************************************************************************** */
+TYPED_TEST_SUITE(MathLibIntegrationGaussLegendreTest, MeshElementTypes);
 
-static double const eps = 10 * std::numeric_limits<double>::epsilon();
+template <typename MeshElementType, typename MaxExactPolynomialOrderFct,
+          typename PolynomialFactory>
+static void mathLibIntegrationGaussLegendreTestImpl(
+    MaxExactPolynomialOrderFct const& max_exact_polynomial_order_fct,
+    PolynomialFactory const& polynomial_factory)
+{
+    auto const eps = 10 * std::numeric_limits<double>::epsilon();
+    auto const mesh_ptr = createUnitCube<MeshElementType>();
 
-/* The tests in this file fundamentally rely on a mesh being read from a vtu
- * file, and a DOF table being computed for that mesh. Since our PETSc build
- * only works with node partitioned meshes, it cannot digest the read meshes.
- */
 #ifndef USE_PETSC
-#define OGS_DONT_TEST_THIS_IF_PETSC(group, test_case) TEST(group, test_case)
+    auto const& mesh = *mesh_ptr;
 #else
-#define OGS_DONT_TEST_THIS_IF_PETSC(group, test_case) \
-    TEST(group, DISABLED_##test_case)
+    MeshLib::NodePartitionedMesh const mesh{*mesh_ptr};
 #endif
 
-OGS_DONT_TEST_THIS_IF_PETSC(MathLib, IntegrationGaussLegendreTet)
-{
-    std::unique_ptr<MeshLib::Mesh> mesh_tet(
-        MeshLib::IO::VtuInterface::readVTUFile(
-            TestInfoLib::TestInfo::data_path + "/MathLib/unit_cube_tet.vtu"));
-
-    for (unsigned integration_order : {1, 2, 3, 4})
-    {
-        DBUG("\n==== integration order: {:d}.\n", integration_order);
-        GaussLegendreTest::IntegrationTestProcess pcs_tet(*mesh_tet,
-                                                          integration_order);
-
-        for (unsigned polynomial_order : {0, 1, 2})
-        {
-            if (polynomial_order > 2 * integration_order - 1)
-            {
-                break;
-            }
-
-            DBUG("  == polynomial order: {:d}.", polynomial_order);
-            auto f = GaussLegendreTest::getF(polynomial_order);
-
-            auto const integral_tet = pcs_tet.integrate(f->getClosure());
-            EXPECT_NEAR(f->getAnalyticalIntegralOverUnitCube(), integral_tet,
-                        eps);
-        }
-    }
-}
-
-OGS_DONT_TEST_THIS_IF_PETSC(MathLib, IntegrationGaussLegendreHex)
-{
-    std::unique_ptr<MeshLib::Mesh> mesh_hex(
-        MeshLib::IO::VtuInterface::readVTUFile(
-            TestInfoLib::TestInfo::data_path + "/MathLib/unit_cube_hex.vtu"));
-
-    for (unsigned integration_order : {1, 2, 3})
-    {
-        DBUG("\n==== integration order: {:d}.\n", integration_order);
-        GaussLegendreTest::IntegrationTestProcess pcs_hex(*mesh_hex,
-                                                          integration_order);
-
-        for (unsigned polynomial_order : {0, 1, 2})
-        {
-            if (polynomial_order > 2 * integration_order - 1)
-            {
-                break;
-            }
-
-            DBUG("  == polynomial order: {:d}.", polynomial_order);
-            auto f = GaussLegendreTest::getF(polynomial_order);
-
-            auto const integral_hex = pcs_hex.integrate(f->getClosure());
-            EXPECT_NEAR(f->getAnalyticalIntegralOverUnitCube(), integral_hex,
-                        eps);
-        }
-    }
-}
-
-// This test is disabled, because the polynomials involved are too complicated
-// to be exactly integrated over tetrahedra using Gauss-Legendre quadrature
-OGS_DONT_TEST_THIS_IF_PETSC(
-    MathLib, DISABLED_IntegrationGaussLegendreTetSeparablePolynomial)
-{
-    std::unique_ptr<MeshLib::Mesh> mesh_tet(
-        MeshLib::IO::VtuInterface::readVTUFile(
-            TestInfoLib::TestInfo::data_path + "/MathLib/unit_cube_tet.vtu"));
-
     for (unsigned integration_order : {1, 2, 3, 4})
     {
-        DBUG("\n==== integration order: {:d}.\n", integration_order);
-        GaussLegendreTest::IntegrationTestProcess pcs_tet(*mesh_tet,
-                                                          integration_order);
+        GaussLegendreTest::IntegrationTestProcess pcs(mesh, integration_order);
 
         for (unsigned polynomial_order = 0;
-             // Gauss-Legendre integration is exact up to this order!
-             polynomial_order < 2 * integration_order;
+             polynomial_order <=
+             max_exact_polynomial_order_fct(integration_order);
              ++polynomial_order)
         {
-            DBUG("  == polynomial order: {:d}.", polynomial_order);
-            GaussLegendreTest::F3DSeparablePolynomial f(polynomial_order);
-
-            auto const integral_tet = pcs_tet.integrate(f.getClosure());
-            EXPECT_NEAR(f.getAnalyticalIntegralOverUnitCube(), integral_tet,
-                        eps);
+            auto const f = polynomial_factory(polynomial_order);
+
+            auto const analytical_integral =
+                f->getAnalyticalIntegralOverUnitCube();
+            auto const numerical_integral = pcs.integrate(*f);
+            EXPECT_NEAR(analytical_integral, numerical_integral, eps)
+                << "integration order: " << integration_order
+                << "\npolynomial order:  " << polynomial_order  //
+                << "\nf:                 " << *f;
         }
     }
 }
 
-OGS_DONT_TEST_THIS_IF_PETSC(MathLib,
-                            IntegrationGaussLegendreHexSeparablePolynomial)
+TYPED_TEST(MathLibIntegrationGaussLegendreTest, Base)
 {
-    std::unique_ptr<MeshLib::Mesh> mesh_hex(
-        MeshLib::IO::VtuInterface::readVTUFile(
-            TestInfoLib::TestInfo::data_path + "/MathLib/unit_cube_hex.vtu"));
-
-    for (unsigned integration_order : {1, 2, 3, 4})
+    using MeshElementType = TypeParam;
+    auto const polynomial_factory = [](unsigned const polynomial_order)
     {
-        DBUG("\n==== integration order: {:d}.\n", integration_order);
-        GaussLegendreTest::IntegrationTestProcess pcs_hex(*mesh_hex,
-                                                          integration_order);
-
-        for (unsigned polynomial_order = 0;
-             // Gauss-Legendre integration is exact up to this order!
-             polynomial_order < 2 * integration_order;
-             ++polynomial_order)
-        {
-            DBUG("  == polynomial order: {:d}.", polynomial_order);
-            GaussLegendreTest::F3DSeparablePolynomial f(polynomial_order);
+        auto constexpr dim = MeshElementType::dimension;
+        return TestPolynomials::getF<dim>(polynomial_order);
+    };
 
-            auto const integral_hex = pcs_hex.integrate(f.getClosure());
-            EXPECT_NEAR(f.getAnalyticalIntegralOverUnitCube(), integral_hex,
-                        eps);
-        }
-    }
+    mathLibIntegrationGaussLegendreTestImpl<MeshElementType>(
+        this->maxExactPolynomialOrderBase, polynomial_factory);
 }
 
-OGS_DONT_TEST_THIS_IF_PETSC(MathLib,
-                            IntegrationGaussLegendreTetNonSeparablePolynomial)
+TYPED_TEST(MathLibIntegrationGaussLegendreTest, SeparablePolynomial)
 {
-    std::unique_ptr<MeshLib::Mesh> mesh_tet(
-        MeshLib::IO::VtuInterface::readVTUFile(
-            TestInfoLib::TestInfo::data_path + "/MathLib/unit_cube_tet.vtu"));
-
-    // quadrature must be exact up to the given polynomial degrees
-    // for integration order n = 1, 2, 3 it is 2*n-1 = 1, 3, 5
-    // for integration order 4 it is 5, i.e. the same as for 3rd integration
-    // order
-    const std::vector<unsigned> maximum_polynomial_degrees{0, 1, 3, 5, 5};
+    using MeshElementType = TypeParam;
 
-    for (unsigned integration_order : {1, 2, 3, 4})
+    auto const polynomial_factory = [](unsigned const polynomial_order)
     {
-        DBUG("\n==== integration order: {:d}.\n", integration_order);
-        GaussLegendreTest::IntegrationTestProcess pcs_tet(*mesh_tet,
-                                                          integration_order);
+        auto constexpr dim = MeshElementType::dimension;
+        return std::make_unique<TestPolynomials::FSeparablePolynomial<dim>>(
+            polynomial_order);
+    };
 
-        for (unsigned polynomial_order = 0;
-             polynomial_order <= maximum_polynomial_degrees[integration_order];
-             ++polynomial_order)
-        {
-            DBUG("  == polynomial order: {:d}.", polynomial_order);
-            GaussLegendreTest::F3DNonseparablePolynomial f(polynomial_order);
-
-            auto const integral_tet = pcs_tet.integrate(f.getClosure());
-            EXPECT_NEAR(f.getAnalyticalIntegralOverUnitCube(), integral_tet,
-                        eps);
-        }
-    }
+    mathLibIntegrationGaussLegendreTestImpl<MeshElementType>(
+        this->maxExactPolynomialOrderSeparable, polynomial_factory);
 }
 
-OGS_DONT_TEST_THIS_IF_PETSC(MathLib,
-                            IntegrationGaussLegendreHexNonSeparablePolynomial)
+TYPED_TEST(MathLibIntegrationGaussLegendreTest, NonseparablePolynomial)
 {
-    std::unique_ptr<MeshLib::Mesh> mesh_hex(
-        MeshLib::IO::VtuInterface::readVTUFile(
-            TestInfoLib::TestInfo::data_path + "/MathLib/unit_cube_hex.vtu"));
+    using MeshElementType = TypeParam;
 
-    for (unsigned integration_order : {1, 2, 3, 4})
+    auto const polynomial_factory = [](unsigned const polynomial_order)
     {
-        DBUG("\n==== integration order: {:d}.\n", integration_order);
-        GaussLegendreTest::IntegrationTestProcess pcs_hex(*mesh_hex,
-                                                          integration_order);
+        auto constexpr dim = MeshElementType::dimension;
+        return std::make_unique<TestPolynomials::FNonseparablePolynomial<dim>>(
+            polynomial_order);
+    };
 
-        for (unsigned polynomial_order = 0;
-             // Gauss-Legendre integration is exact up to this order!
-             polynomial_order < 2 * integration_order;
-             ++polynomial_order)
-        {
-            DBUG("  == polynomial order: {:d}.", polynomial_order);
-            GaussLegendreTest::F3DNonseparablePolynomial f(polynomial_order);
-
-            auto const integral_hex = pcs_hex.integrate(f.getClosure());
-            EXPECT_NEAR(f.getAnalyticalIntegralOverUnitCube(), integral_hex,
-                        eps);
-        }
-    }
+    mathLibIntegrationGaussLegendreTestImpl<MeshElementType>(
+        this->maxExactPolynomialOrderNonseparable, polynomial_factory);
 }
-
-#undef OGS_DONT_TEST_THIS_IF_PETSC
-- 
GitLab