diff --git a/tests/test_PolynomialReconstruction_degree_1.cpp b/tests/test_PolynomialReconstruction_degree_1.cpp
index 6c333913e85fa811abe34af119176d3d7209f156..927a5f4c3f965e53c1d940f012dc1bbddf2fbbe1 100644
--- a/tests/test_PolynomialReconstruction_degree_1.cpp
+++ b/tests/test_PolynomialReconstruction_degree_1.cpp
@@ -13,6 +13,9 @@
 #include <analysis/QuadratureFormula.hpp>
 #include <analysis/QuadratureManager.hpp>
 #include <geometry/LineTransformation.hpp>
+#include <geometry/SquareTransformation.hpp>
+#include <geometry/TetrahedronTransformation.hpp>
+#include <geometry/TriangleTransformation.hpp>
 #include <mesh/Mesh.hpp>
 #include <mesh/MeshDataManager.hpp>
 #include <mesh/NamedBoundaryDescriptor.hpp>
@@ -23,8 +26,195 @@
 
 #include <MeshDataBaseForTests.hpp>
 
+#include <type_traits>
+
 // clazy:excludeall=non-pod-global-static
 
+namespace test_only
+{
+
+template <typename DataType>
+PUGS_INLINE double
+get_max_error(const DataType& x, const DataType& y)
+{
+  if constexpr (is_tiny_matrix_v<DataType>) {
+    return frobeniusNorm(x - y);
+  } else if constexpr (is_tiny_vector_v<DataType>) {
+    return l2Norm(x - y);
+  } else {
+    static_assert(std::is_arithmetic_v<DataType>, "expecting arithmetic type");
+    return std::abs(x - y);
+  }
+}
+
+template <MeshConcept MeshType, typename DataType>
+double
+max_reconstruction_error(const MeshType& mesh,
+                         DiscreteFunctionDPk<MeshType::Dimension, const DataType> dpk_f,
+                         std::function<DataType(const TinyVector<MeshType::Dimension>&)> exact)
+{
+  auto xr                  = mesh.xr();
+  auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+  double max_error         = 0;
+  auto cell_type           = mesh.connectivity().cellType();
+
+  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
+    auto cell_nodes = cell_to_node_matrix[cell_id];
+    if constexpr (MeshType::Dimension == 1) {
+      Assert(cell_type[cell_id] == CellType::Line);
+      LineTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]]};
+      auto qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1});
+      for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+        auto x    = T(qf.point(i_quadrarture));
+        max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x)));
+      }
+    } else if constexpr (MeshType::Dimension == 2) {
+      switch (cell_type[cell_id]) {
+      case CellType::Triangle: {
+        TriangleTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]]};
+        auto qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1});
+        for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+          auto x    = T(qf.point(i_quadrarture));
+          max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x)));
+        }
+        break;
+      }
+      case CellType::Quadrangle: {
+        SquareTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]],
+                                                    xr[cell_nodes[3]]};
+        auto qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1});
+        for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+          auto x    = T(qf.point(i_quadrarture));
+          max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x)));
+        }
+        break;
+      }
+      default: {
+        throw UnexpectedError("unexpected cell type");
+      }
+      }
+    } else if constexpr (MeshType::Dimension == 3) {
+      switch (cell_type[cell_id]) {
+      case CellType::Tetrahedron: {
+        TetrahedronTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]]};
+        auto qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1});
+        for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+          auto x    = T(qf.point(i_quadrarture));
+          max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x)));
+        }
+        break;
+      }
+      // case CellType::Pyramid: {
+      // SquareTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]],
+      //                                             xr[cell_nodes[3]]};
+      // auto qf = QuadratureManager::instance().getSquareFormula(GaussLobattoQuadratureDescriptor{dpk_f.degree() + 1});
+      // for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+      //   auto x    = T(qf.point(i_quadrarture));
+      //   max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x)));
+      // }
+      // break;
+      // }
+      default: {
+        throw UnexpectedError("unexpected cell type");
+      }
+      }
+    }
+  }
+  return max_error;
+}
+
+template <MeshConcept MeshType, typename DataType, size_t NbComponents>
+double
+max_reconstruction_error(
+  const MeshType& mesh,
+  DiscreteFunctionDPkVector<MeshType::Dimension, const DataType> dpk_v,
+  const std::array<std::function<DataType(const TinyVector<MeshType::Dimension>&)>, NbComponents>& vector_exact)
+{
+  auto xr                  = mesh.xr();
+  auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+  double max_error         = 0;
+  auto cell_type           = mesh.connectivity().cellType();
+
+  REQUIRE(NbComponents == dpk_v.numberOfComponents());
+
+  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
+    auto cell_nodes = cell_to_node_matrix[cell_id];
+    if constexpr (MeshType::Dimension == 1) {
+      Assert(cell_type[cell_id] == CellType::Line);
+      LineTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]]};
+      auto qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1});
+      for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+        auto x = T(qf.point(i_quadrarture));
+        for (size_t i_component = 0; i_component < NbComponents; ++i_component) {
+          max_error = std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x)));
+        }
+      }
+    } else if constexpr (MeshType::Dimension == 2) {
+      switch (cell_type[cell_id]) {
+      case CellType::Triangle: {
+        TriangleTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]]};
+        auto qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1});
+        for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+          auto x = T(qf.point(i_quadrarture));
+          for (size_t i_component = 0; i_component < NbComponents; ++i_component) {
+            max_error =
+              std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x)));
+          }
+        }
+        break;
+      }
+      case CellType::Quadrangle: {
+        SquareTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]],
+                                                    xr[cell_nodes[3]]};
+        auto qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1});
+        for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+          auto x = T(qf.point(i_quadrarture));
+          for (size_t i_component = 0; i_component < NbComponents; ++i_component) {
+            max_error =
+              std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x)));
+          }
+        }
+        break;
+      }
+      default: {
+        throw UnexpectedError("unexpected cell type");
+      }
+      }
+    } else if constexpr (MeshType::Dimension == 3) {
+      switch (cell_type[cell_id]) {
+      case CellType::Tetrahedron: {
+        TetrahedronTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]]};
+        auto qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor{dpk_v.degree() + 1});
+        for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+          auto x = T(qf.point(i_quadrarture));
+          for (size_t i_component = 0; i_component < NbComponents; ++i_component) {
+            max_error =
+              std::max(max_error, get_max_error(dpk_v(cell_id, i_component)(x), vector_exact[i_component](x)));
+          }
+        }
+        break;
+      }
+      // case CellType::Pyramid: {
+      // SquareTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]],
+      //                                             xr[cell_nodes[3]]};
+      // auto qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor{dpk_f.degree() + 1});
+      // for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+      //   auto x    = T(qf.point(i_quadrarture));
+      //   max_error = std::max(max_error, get_max_error(dpk_f[cell_id](x), exact(x)));
+      // }
+      // break;
+      // }
+      default: {
+        throw UnexpectedError("unexpected cell type");
+      }
+      }
+    }
+  }
+  return max_error;
+}
+
+}   // namespace test_only
+
 TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
 {
   SECTION("without symmetries")
@@ -49,55 +239,20 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto p_mesh = named_mesh.mesh()->get<Mesh<1>>();
                 auto& mesh  = *p_mesh;
 
-                auto R_affine = [](const R1& x) { return 2.3 + 1.7 * x[0]; };
-                auto xj       = MeshDataManager::instance().getMeshData(mesh).xj();
-                auto xr       = mesh.xr();
-
-                auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+                auto R_exact = [](const R1& x) { return 2.3 + 1.7 * x[0]; };
+                auto xj      = MeshDataManager::instance().getMeshData(mesh).xj();
 
                 DiscreteFunctionP0<double> fh{p_mesh};
 
                 parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R_affine(xj[cell_id]); });
+                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R_exact(xj[cell_id]); });
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
 
                 auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>();
 
-                auto qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor{2});
-                {
-                  double max_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    auto cell_nodes = cell_to_node_matrix[cell_id];
-                    LineTransformation<1> T{xr[cell_nodes[0]], xr[cell_nodes[1]]};
-                    for (size_t i_quadrarture = 0; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
-                      max_error = std::max(max_error, std::abs(dpk_fh[cell_id](xj[cell_id]) - R_affine(xj[cell_id])));
-                    }
-                  }
-
-                  REQUIRE(parallel::allReduceMax(max_error) == 0   // Catch::Approx(0).margin(0 * 1E-14)
-                  );
-                }
-
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    max_mean_error =
-                      std::max(max_mean_error, std::abs(dpk_fh[cell_id](xj[cell_id]) - R_affine(xj[cell_id])));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const double reconstructed_slope =
-                      (dpk_fh[cell_id](R1{0.1} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R1{0.1})) / 0.2;
-
-                    max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - 1.7));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-14));
-                }
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact));
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
               }
             }
           }
@@ -112,7 +267,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto p_mesh = named_mesh.mesh()->get<Mesh<1>>();
                 auto& mesh  = *p_mesh;
 
-                auto R3_affine = [](const R1& x) -> R3 {
+                auto R3_exact = [](const R1& x) -> R3 {
                   return R3{+2.3 + 1.7 * x[0],   //
                             +1.4 - 0.6 * x[0],   //
                             -0.2 + 3.1 * x[0]};
@@ -122,31 +277,14 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 DiscreteFunctionP0<R3> uh{p_mesh};
 
                 parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_affine(xj[cell_id]); });
+                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_exact(xj[cell_id]); });
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
 
                 auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3>>();
 
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    max_mean_error =
-                      std::max(max_mean_error, l2Norm(dpk_uh[cell_id](xj[cell_id]) - R3_affine(xj[cell_id])));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const R3 reconstructed_slope =
-                      (1 / 0.2) * (dpk_uh[cell_id](R1{0.1} + xj[cell_id]) - dpk_uh[cell_id](xj[cell_id] - R1{0.1}));
-
-                    max_slope_error = std::max(max_slope_error, l2Norm(reconstructed_slope - R3{1.7, -0.6, 3.1}));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-14));
-                }
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact));
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
               }
             }
           }
@@ -161,7 +299,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto p_mesh = named_mesh.mesh()->get<Mesh<1>>();
                 auto& mesh  = *p_mesh;
 
-                auto R3x3_affine = [](const R1& x) -> R3x3 {
+                auto R3x3_exact = [](const R1& x) -> R3x3 {
                   return R3x3{
                     +2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0],   //
                     +2.4 - 2.3 * x[0], -0.2 + 3.1 * x[0], -3.2 - 3.6 * x[0],
@@ -173,94 +311,46 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 DiscreteFunctionP0<R3x3> Ah{p_mesh};
 
                 parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R3x3_affine(xj[cell_id]); });
+                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R3x3_exact(xj[cell_id]); });
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah);
 
                 auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<1, const R3x3>>();
 
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    max_mean_error =
-                      std::max(max_mean_error, frobeniusNorm(dpk_Ah[cell_id](xj[cell_id]) - R3x3_affine(xj[cell_id])));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const R3x3 reconstructed_slope =
-                      (1 / 0.2) * (dpk_Ah[cell_id](R1{0.1} + xj[cell_id]) - dpk_Ah[cell_id](xj[cell_id] - R1{0.1}));
-
-                    R3x3 slops = R3x3{+1.7, +2.1, -0.6,   //
-                                      -2.3, +3.1, -3.6,   //
-                                      +3.1, +2.9, +2.3};
-
-                    max_slope_error = std::max(max_slope_error,   //
-                                               frobeniusNorm(reconstructed_slope - slops));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-13));
-                }
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R3x3_exact));
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
               }
             }
           }
 
           SECTION("R vector data")
           {
-            using R3 = TinyVector<3>;
-
             for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) {
               SECTION(named_mesh.name())
               {
                 auto p_mesh = named_mesh.mesh()->get<Mesh<1>>();
                 auto& mesh  = *p_mesh;
+                std::array<std::function<double(const R1&)>, 3> vector_exact =
+                  {[](const R1& x) -> double { return +2.3 + 1.7 * x[0]; },
+                   [](const R1& x) -> double { return -1.7 + 2.1 * x[0]; },
+                   [](const R1& x) -> double { return +1.4 - 0.6 * x[0]; }};
 
-                auto vector_affine = [](const R1& x) -> R3 {
-                  return R3{+2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0]};
-                };
                 auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
 
                 DiscreteFunctionP0Vector<double> Vh{p_mesh, 3};
 
                 parallel_for(
                   mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                    auto vector = vector_affine(xj[cell_id]);
-                    for (size_t i = 0; i < vector.dimension(); ++i) {
-                      Vh[cell_id][i] = vector[i];
+                    for (size_t i = 0; i < vector_exact.size(); ++i) {
+                      Vh[cell_id][i] = vector_exact[i](xj[cell_id]);
                     }
                   });
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
+                auto dpk_Vh          = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const double>>();
 
-                auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const double>>();
-
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    auto vector = vector_affine(xj[cell_id]);
-                    for (size_t i = 0; i < vector.dimension(); ++i) {
-                      max_mean_error = std::max(max_mean_error, std::abs(dpk_Vh(cell_id, i)(xj[cell_id]) - vector[i]));
-                    }
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_slope_error = 0;
-                  const TinyVector<3> slope{+1.7, +2.1, -0.6};
-
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    for (size_t i = 0; i < slope.dimension(); ++i) {
-                      const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R1{0.1} + xj[cell_id]) -
-                                                                      dpk_Vh(cell_id, i)(xj[cell_id] - R1{0.1}));
-
-                      max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - slope[i]));
-                    }
-                  }
-                  REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-14));
-                }
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
               }
             }
           }
@@ -275,13 +365,13 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto p_mesh = named_mesh.mesh()->get<Mesh<1>>();
                 auto& mesh  = *p_mesh;
 
-                auto vector_affine0 = [](const R1& x) -> R3 {
-                  return R3{+2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0]};
-                };
-
-                auto vector_affine1 = [](const R1& x) -> R3 {
-                  return R3{+1.6 + 0.7 * x[0], -2.1 + 1.2 * x[0], +1.1 - 0.3 * x[0]};
-                };
+                std::array<std::function<R3(const R1&)>, 2> vector_exact =
+                  {[](const R1& x) -> R3 {
+                     return R3{+2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0]};
+                   },
+                   [](const R1& x) -> R3 {
+                     return R3{+1.6 + 0.7 * x[0], -2.1 + 1.2 * x[0], +1.1 - 0.3 * x[0]};
+                   }};
 
                 auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
 
@@ -289,54 +379,16 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
 
                 parallel_for(
                   mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                    Vh[cell_id][0] = vector_affine0(xj[cell_id]);
-                    Vh[cell_id][1] = vector_affine1(xj[cell_id]);
+                    Vh[cell_id][0] = vector_exact[0](xj[cell_id]);
+                    Vh[cell_id][1] = vector_exact[1](xj[cell_id]);
                   });
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
 
                 auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const R3>>();
 
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    max_mean_error =
-                      std::max(max_mean_error, l2Norm(dpk_Vh(cell_id, 0)(xj[cell_id]) - vector_affine0(xj[cell_id])));
-                    max_mean_error =
-                      std::max(max_mean_error, l2Norm(dpk_Vh(cell_id, 1)(xj[cell_id]) - vector_affine1(xj[cell_id])));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_slope_error = 0;
-                  {
-                    const TinyVector<3> slope0{+1.7, +2.1, -0.6};
-
-                    for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                      for (size_t i = 0; i < R3::Dimension; ++i) {
-                        const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, 0)(R1{0.1} + xj[cell_id])[i] -
-                                                                        dpk_Vh(cell_id, 0)(xj[cell_id] - R1{0.1})[i]);
-
-                        max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - slope0[i]));
-                      }
-                    }
-                  }
-
-                  {
-                    const TinyVector<3> slope1{+0.7, +1.2, -0.3};
-
-                    for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                      for (size_t i = 0; i < R3::Dimension; ++i) {
-                        const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, 1)(R1{0.1} + xj[cell_id])[i] -
-                                                                        dpk_Vh(cell_id, 1)(xj[cell_id] - R1{0.1})[i]);
-
-                        max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - slope1[i]));
-                      }
-                    }
-                  }
-                  REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-14));
-                }
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
               }
             }
           }
@@ -352,15 +404,15 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto p_mesh = named_mesh.mesh()->get<Mesh<1>>();
                 auto& mesh  = *p_mesh;
 
-                auto R_affine = [](const R1& x) { return 2.3 + 1.7 * x[0]; };
+                auto R_exact = [](const R1& x) { return 2.3 + 1.7 * x[0]; };
 
-                auto R3_affine = [](const R1& x) -> R3 {
+                auto R3_exact = [](const R1& x) -> R3 {
                   return R3{+2.3 + 1.7 * x[0],   //
                             +1.4 - 0.6 * x[0],   //
                             -0.2 + 3.1 * x[0]};
                 };
 
-                auto R3x3_affine = [](const R1& x) -> R3x3 {
+                auto R3x3_exact = [](const R1& x) -> R3x3 {
                   return R3x3{
                     +2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0],   //
                     +2.4 - 2.3 * x[0], -0.2 + 3.1 * x[0], -3.2 - 3.6 * x[0],
@@ -368,30 +420,30 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                   };
                 };
 
-                auto vector_affine = [](const R1& x) -> R3 {
-                  return R3{+2.3 + 1.7 * x[0], -1.7 + 2.1 * x[0], +1.4 - 0.6 * x[0]};
-                };
+                std::array<std::function<double(const R1&)>, 3> vector_exact =
+                  {[](const R1& x) -> double { return +2.3 + 1.7 * x[0]; },
+                   [](const R1& x) -> double { return -1.7 + 2.1 * x[0]; },
+                   [](const R1& x) -> double { return +1.4 - 0.6 * x[0]; }};
 
                 auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
 
                 DiscreteFunctionP0<double> fh{p_mesh};
                 parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R_affine(xj[cell_id]); });
+                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R_exact(xj[cell_id]); });
 
                 DiscreteFunctionP0<R3> uh{p_mesh};
                 parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_affine(xj[cell_id]); });
+                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_exact(xj[cell_id]); });
 
                 DiscreteFunctionP0<R3x3> Ah{p_mesh};
                 parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R3x3_affine(xj[cell_id]); });
+                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R3x3_exact(xj[cell_id]); });
 
                 DiscreteFunctionP0Vector<double> Vh{p_mesh, 3};
                 parallel_for(
                   mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                    auto vector = vector_affine(xj[cell_id]);
-                    for (size_t i = 0; i < vector.dimension(); ++i) {
-                      Vh[cell_id][i] = vector[i];
+                    for (size_t i = 0; i < vector_exact.size(); ++i) {
+                      Vh[cell_id][i] = vector_exact[i](xj[cell_id]);
                     }
                   });
 
@@ -400,103 +452,28 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                                                              std::make_shared<DiscreteFunctionP0<R3x3>>(Ah),
                                                              DiscreteFunctionVariant(Vh));
 
-                auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>();
-
                 {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    max_mean_error =
-                      std::max(max_mean_error, std::abs(dpk_fh[cell_id](xj[cell_id]) - R_affine(xj[cell_id])));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const double reconstructed_slope =
-                      (dpk_fh[cell_id](R1{0.1} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R1{0.1})) / 0.2;
-
-                    max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - 1.7));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                auto dpk_uh = reconstructions[1]->get<DiscreteFunctionDPk<1, const R3>>();
-
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    max_mean_error =
-                      std::max(max_mean_error, l2Norm(dpk_uh[cell_id](xj[cell_id]) - R3_affine(xj[cell_id])));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const R3 reconstructed_slope =
-                      (1 / 0.2) * (dpk_uh[cell_id](R1{0.1} + xj[cell_id]) - dpk_uh[cell_id](xj[cell_id] - R1{0.1}));
-
-                    max_slope_error = std::max(max_slope_error, l2Norm(reconstructed_slope - R3{1.7, -0.6, 3.1}));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-14));
+                  auto dpk_fh      = reconstructions[0]->get<DiscreteFunctionDPk<1, const double>>();
+                  double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact));
+                  REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
                 }
 
-                auto dpk_Ah = reconstructions[2]->get<DiscreteFunctionDPk<1, const R3x3>>();
-
                 {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    max_mean_error =
-                      std::max(max_mean_error, frobeniusNorm(dpk_Ah[cell_id](xj[cell_id]) - R3x3_affine(xj[cell_id])));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
+                  auto dpk_uh      = reconstructions[1]->get<DiscreteFunctionDPk<1, const R3>>();
+                  double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact));
+                  REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
                 }
 
                 {
-                  double max_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const R3x3 reconstructed_slope =
-                      (1 / 0.2) * (dpk_Ah[cell_id](R1{0.1} + xj[cell_id]) - dpk_Ah[cell_id](xj[cell_id] - R1{0.1}));
-
-                    R3x3 slops = R3x3{+1.7, +2.1, -0.6,   //
-                                      -2.3, +3.1, -3.6,   //
-                                      +3.1, +2.9, +2.3};
-
-                    max_slope_error = std::max(max_slope_error,   //
-                                               frobeniusNorm(reconstructed_slope - slops));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-13));
-                }
-
-                auto dpk_Vh = reconstructions[3]->get<DiscreteFunctionDPkVector<1, const double>>();
-
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    auto vector = vector_affine(xj[cell_id]);
-                    for (size_t i = 0; i < vector.dimension(); ++i) {
-                      max_mean_error = std::max(max_mean_error, std::abs(dpk_Vh(cell_id, i)(xj[cell_id]) - vector[i]));
-                    }
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
+                  auto dpk_Ah      = reconstructions[2]->get<DiscreteFunctionDPk<1, const R3x3>>();
+                  double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R3x3_exact));
+                  REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
                 }
 
                 {
-                  double max_slope_error = 0;
-                  const TinyVector<3> slope{+1.7, +2.1, -0.6};
-
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    for (size_t i = 0; i < slope.dimension(); ++i) {
-                      const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R1{0.1} + xj[cell_id]) -
-                                                                      dpk_Vh(cell_id, i)(xj[cell_id] - R1{0.1}));
-
-                      max_slope_error = std::max(max_slope_error, std::abs(reconstructed_slope - slope[i]));
-                    }
-                  }
-                  REQUIRE(parallel::allReduceMax(max_slope_error) == Catch::Approx(0).margin(1E-14));
+                  auto dpk_Vh      = reconstructions[3]->get<DiscreteFunctionDPkVector<1, const double>>();
+                  double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+                  REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
                 }
               }
             }
@@ -515,48 +492,20 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto p_mesh = named_mesh.mesh()->get<Mesh<2>>();
                 auto& mesh  = *p_mesh;
 
-                auto R_affine = [](const R2& x) { return 2.3 + 1.7 * x[0] - 1.3 * x[1]; };
-                auto xj       = MeshDataManager::instance().getMeshData(mesh).xj();
+                auto R_exact = [](const R2& x) { return 2.3 + 1.7 * x[0] - 1.3 * x[1]; };
+                auto xj      = MeshDataManager::instance().getMeshData(mesh).xj();
 
                 DiscreteFunctionP0<double> fh{p_mesh};
 
                 parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R_affine(xj[cell_id]); });
+                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R_exact(xj[cell_id]); });
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
 
                 auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<2, const double>>();
 
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    max_mean_error =
-                      std::max(max_mean_error, std::abs(dpk_fh[cell_id](xj[cell_id]) - R_affine(xj[cell_id])));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_x_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const double reconstructed_slope =
-                      (dpk_fh[cell_id](R2{0.1, 0} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R2{0.1, 0})) / 0.2;
-
-                    max_x_slope_error = std::max(max_x_slope_error, std::abs(reconstructed_slope - 1.7));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13));
-                }
-
-                {
-                  double max_y_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const double reconstructed_slope =
-                      (dpk_fh[cell_id](R2{0, 0.1} + xj[cell_id]) - dpk_fh[cell_id](xj[cell_id] - R2{0, 0.1})) / 0.2;
-
-                    max_y_slope_error = std::max(max_y_slope_error, std::abs(reconstructed_slope - (-1.3)));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_y_slope_error) == Catch::Approx(0).margin(1E-14));
-                }
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R_exact));
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
               }
             }
           }
@@ -571,7 +520,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto p_mesh = named_mesh.mesh()->get<Mesh<2>>();
                 auto& mesh  = *p_mesh;
 
-                auto R3_affine = [](const R2& x) -> R3 {
+                auto R3_exact = [](const R2& x) -> R3 {
                   return R3{+2.3 + 1.7 * x[0] - 2.2 * x[1],   //
                             +1.4 - 0.6 * x[0] + 1.3 * x[1],   //
                             -0.2 + 3.1 * x[0] - 1.1 * x[1]};
@@ -581,42 +530,14 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 DiscreteFunctionP0<R3> uh{p_mesh};
 
                 parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_affine(xj[cell_id]); });
+                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_exact(xj[cell_id]); });
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
 
                 auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R3>>();
 
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    max_mean_error =
-                      std::max(max_mean_error, l2Norm(dpk_uh[cell_id](xj[cell_id]) - R3_affine(xj[cell_id])));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_x_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const R3 reconstructed_slope = (1 / 0.2) * (dpk_uh[cell_id](R2{0.1, 0} + xj[cell_id]) -
-                                                                dpk_uh[cell_id](xj[cell_id] - R2{0.1, 0}));
-
-                    max_x_slope_error = std::max(max_x_slope_error, l2Norm(reconstructed_slope - R3{1.7, -0.6, 3.1}));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13));
-                }
-
-                {
-                  double max_y_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const R3 reconstructed_slope = (1 / 0.2) * (dpk_uh[cell_id](R2{0, 0.1} + xj[cell_id]) -
-                                                                dpk_uh[cell_id](xj[cell_id] - R2{0, 0.1}));
-
-                    max_y_slope_error = std::max(max_y_slope_error, l2Norm(reconstructed_slope - R3{-2.2, 1.3, -1.1}));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_y_slope_error) == Catch::Approx(0).margin(1E-13));
-                }
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R3_exact));
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
               }
             }
           }
@@ -631,7 +552,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto p_mesh = named_mesh.mesh()->get<Mesh<2>>();
                 auto& mesh  = *p_mesh;
 
-                auto R2x2_affine = [](const R2& x) -> R2x2 {
+                auto R2x2_exact = [](const R2& x) -> R2x2 {
                   return R2x2{+2.3 + 1.7 * x[0] + 1.2 * x[1], -1.7 + 2.1 * x[0] - 2.2 * x[1],   //
                               +1.4 - 0.6 * x[0] - 2.1 * x[1], +2.4 - 2.3 * x[0] + 1.3 * x[1]};
                 };
@@ -640,118 +561,47 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 DiscreteFunctionP0<R2x2> Ah{p_mesh};
 
                 parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R2x2_affine(xj[cell_id]); });
+                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R2x2_exact(xj[cell_id]); });
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah);
 
-                auto dpk_Ah = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2x2>>();
-
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    max_mean_error =
-                      std::max(max_mean_error, frobeniusNorm(dpk_Ah[cell_id](xj[cell_id]) - R2x2_affine(xj[cell_id])));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_x_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const R2x2 reconstructed_slope = (1 / 0.2) * (dpk_Ah[cell_id](R2{0.1, 0} + xj[cell_id]) -
-                                                                  dpk_Ah[cell_id](xj[cell_id] - R2{0.1, 0}));
-
-                    max_x_slope_error =
-                      std::max(max_x_slope_error, frobeniusNorm(reconstructed_slope - R2x2{+1.7, +2.1,   //
-                                                                                           -0.6, -2.3}));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13));
-                }
-
-                {
-                  double max_y_slope_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    const R2x2 reconstructed_slope = (1 / 0.2) * (dpk_Ah[cell_id](R2{0, 0.1} + xj[cell_id]) -
-                                                                  dpk_Ah[cell_id](xj[cell_id] - R2{0, 0.1}));
-
-                    max_y_slope_error =
-                      std::max(max_y_slope_error, frobeniusNorm(reconstructed_slope - R2x2{+1.2, -2.2,   //
-                                                                                           -2.1, +1.3}));
-                  }
-                  REQUIRE(parallel::allReduceMax(max_y_slope_error) == Catch::Approx(0).margin(1E-13));
-                }
+                auto dpk_Ah      = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2x2>>();
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_Ah, std::function(R2x2_exact));
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
               }
             }
           }
 
           SECTION("vector data")
           {
-            using R4 = TinyVector<4>;
-
             for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) {
               SECTION(named_mesh.name())
               {
                 auto p_mesh = named_mesh.mesh()->get<Mesh<2>>();
                 auto& mesh  = *p_mesh;
 
-                auto vector_affine = [](const R2& x) -> R4 {
-                  return R4{+2.3 + 1.7 * x[0] + 1.2 * x[1], -1.7 + 2.1 * x[0] - 2.2 * x[1],   //
-                            +1.4 - 0.6 * x[0] - 2.1 * x[1], +2.4 - 2.3 * x[0] + 1.3 * x[1]};
-                };
+                std::array<std::function<double(const R2&)>, 4> vector_exact =
+                  {[](const R2& x) -> double { return +2.3 + 1.7 * x[0] + 1.2 * x[1]; },
+                   [](const R2& x) -> double { return -1.7 + 2.1 * x[0] - 2.2 * x[1]; },
+                   [](const R2& x) -> double { return +1.4 - 0.6 * x[0] - 2.1 * x[1]; },
+                   [](const R2& x) -> double { return +2.4 - 2.3 * x[0] + 1.3 * x[1]; }};
+
                 auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
 
                 DiscreteFunctionP0Vector<double> Vh{p_mesh, 4};
 
                 parallel_for(
                   mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                    auto vector = vector_affine(xj[cell_id]);
-                    for (size_t i = 0; i < vector.dimension(); ++i) {
-                      Vh[cell_id][i] = vector[i];
+                    for (size_t i = 0; i < vector_exact.size(); ++i) {
+                      Vh[cell_id][i] = vector_exact[i](xj[cell_id]);
                     }
                   });
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
 
-                auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const double>>();
-
-                {
-                  double max_mean_error = 0;
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    auto vector = vector_affine(xj[cell_id]);
-                    for (size_t i = 0; i < vector.dimension(); ++i) {
-                      max_mean_error = std::max(max_mean_error, std::abs(dpk_Vh(cell_id, i)(xj[cell_id]) - vector[i]));
-                    }
-                  }
-                  REQUIRE(parallel::allReduceMax(max_mean_error) == Catch::Approx(0).margin(1E-14));
-                }
-
-                {
-                  double max_x_slope_error = 0;
-                  const R4 slope{+1.7, +2.1, -0.6, -2.3};
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    for (size_t i = 0; i < slope.dimension(); ++i) {
-                      const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R2{0.1, 0} + xj[cell_id]) -
-                                                                      dpk_Vh(cell_id, i)(xj[cell_id] - R2{0.1, 0}));
-
-                      max_x_slope_error = std::max(max_x_slope_error, std::abs(reconstructed_slope - slope[i]));
-                    }
-                  }
-                  REQUIRE(parallel::allReduceMax(max_x_slope_error) == Catch::Approx(0).margin(1E-13));
-                }
-
-                {
-                  double max_y_slope_error = 0;
-                  const R4 slope{+1.2, -2.2, -2.1, +1.3};
-                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-                    for (size_t i = 0; i < slope.dimension(); ++i) {
-                      const double reconstructed_slope = (1 / 0.2) * (dpk_Vh(cell_id, i)(R2{0, 0.1} + xj[cell_id]) -
-                                                                      dpk_Vh(cell_id, i)(xj[cell_id] - R2{0, 0.1}));
-
-                      max_y_slope_error = std::max(max_y_slope_error, std::abs(reconstructed_slope - slope[i]));
-                    }
-                  }
-                  REQUIRE(parallel::allReduceMax(max_y_slope_error) == Catch::Approx(0).margin(1E-13));
-                }
+                auto dpk_Vh      = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const double>>();
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
               }
             }
           }