diff --git a/tests/DiscreteFunctionDPkForTests.hpp b/tests/DiscreteFunctionDPkForTests.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5b51861d811b2d8a35c91375bc9e8dd368e2b281
--- /dev/null
+++ b/tests/DiscreteFunctionDPkForTests.hpp
@@ -0,0 +1,365 @@
+#ifndef DISCRETE_FUNCTION_DPK_FOR_TESTS_HPP
+#define DISCRETE_FUNCTION_DPK_FOR_TESTS_HPP
+
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureFormula.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/CubeTransformation.hpp>
+#include <geometry/LineTransformation.hpp>
+#include <geometry/PrismTransformation.hpp>
+#include <geometry/PyramidTransformation.hpp>
+#include <geometry/SquareTransformation.hpp>
+#include <geometry/TetrahedronTransformation.hpp>
+#include <geometry/TriangleTransformation.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <type_traits>
+
+namespace test_only
+{
+
+template <MeshConcept MeshType, typename DataType>
+DiscreteFunctionP0<std::remove_const_t<DataType>>
+exact_projection(const MeshType& mesh,
+                 size_t degree,
+                 std::function<DataType(const TinyVector<MeshType::Dimension>&)> exact_function)
+{
+  DiscreteFunctionP0<std::remove_const_t<DataType>> P0_function{mesh.meshVariant()};
+
+  auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj();
+
+  auto xr                  = mesh.xr();
+  auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+  auto cell_type           = mesh.connectivity().cellType();
+
+  auto sum = [&exact_function, &Vj](const CellId cell_id, const auto& T,
+                                    const auto& qf) -> std::remove_const_t<DataType> {
+    std::remove_const_t<DataType> integral =
+      (qf.weight(0) * T.jacobianDeterminant(qf.point(0))) * exact_function(T(qf.point(0)));
+    for (size_t i_quadrarture = 1; i_quadrarture < qf.numberOfPoints(); ++i_quadrarture) {
+      integral += (qf.weight(i_quadrarture) * T.jacobianDeterminant(qf.point(i_quadrarture))) *
+                  exact_function(T(qf.point(i_quadrarture)));
+    }
+    return 1. / Vj[cell_id] * integral;
+  };
+
+  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) {
+      LineTransformation<MeshType::Dimension> T{xr[cell_nodes[0]], xr[cell_nodes[1]]};
+      auto qf              = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor{degree + 1});
+      P0_function[cell_id] = sum(cell_id, T, qf);
+    } 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{degree + 2});
+        P0_function[cell_id] = sum(cell_id, T, qf);
+        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{degree + 2});
+        P0_function[cell_id] = sum(cell_id, T, qf);
+        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{degree + 3});
+        P0_function[cell_id] = sum(cell_id, T, qf);
+        break;
+      }
+      case CellType::Pyramid: {
+        PyramidTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]],
+                                xr[cell_nodes[4]]};
+        auto qf              = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor{degree + 3});
+        P0_function[cell_id] = sum(cell_id, T, qf);
+        break;
+      }
+      case CellType::Prism: {
+        PrismTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]],
+                              xr[cell_nodes[3]], xr[cell_nodes[4]], xr[cell_nodes[5]]};
+        auto qf              = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor{degree + 3});
+        P0_function[cell_id] = sum(cell_id, T, qf);
+        break;
+      }
+      case CellType::Hexahedron: {
+        CubeTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]],
+                             xr[cell_nodes[4]], xr[cell_nodes[5]], xr[cell_nodes[6]], xr[cell_nodes[7]]};
+        auto qf              = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor{degree + 3});
+        P0_function[cell_id] = sum(cell_id, T, qf);
+        break;
+      }
+      default: {
+        throw UnexpectedError("unexpected cell type");
+      }
+      }
+    } else {
+      throw UnexpectedError("invalid mesh dimension");
+    }
+  }
+
+  return P0_function;
+}
+
+template <MeshConcept MeshType, typename DataType, size_t NbComponents>
+DiscreteFunctionP0Vector<std::remove_const_t<DataType>>
+exact_projection(
+  const MeshType& mesh,
+  size_t degree,
+  const std::array<std::function<DataType(const TinyVector<MeshType::Dimension>&)>, NbComponents>& vector_exact)
+{
+  DiscreteFunctionP0Vector<std::remove_const_t<DataType>> P0_function_vector{mesh.meshVariant(), vector_exact.size()};
+
+  for (size_t i_component = 0; i_component < vector_exact.size(); ++i_component) {
+    auto exact_function = vector_exact[i_component];
+
+    DiscreteFunctionP0 P0_function = exact_projection(mesh, degree, vector_exact[i_component]);
+
+    parallel_for(
+      mesh.numberOfCells(),
+      PUGS_LAMBDA(const CellId cell_id) { P0_function_vector[cell_id][i_component] = P0_function[cell_id]; });
+  }
+
+  return P0_function_vector;
+}
+
+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();
+  auto cell_type           = mesh.connectivity().cellType();
+
+  double max_error = 0;
+  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: {
+        PyramidTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]],
+                                xr[cell_nodes[4]]};
+        auto qf = QuadratureManager::instance().getPyramidFormula(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::Prism: {
+        PrismTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]],
+                              xr[cell_nodes[3]], xr[cell_nodes[4]], xr[cell_nodes[5]]};
+        auto qf = QuadratureManager::instance().getPrismFormula(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::Hexahedron: {
+        CubeTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]],
+                             xr[cell_nodes[4]], xr[cell_nodes[5]], xr[cell_nodes[6]], xr[cell_nodes[7]]};
+        auto qf = QuadratureManager::instance().getCubeFormula(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;
+}
+
+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: {
+        PyramidTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]],
+                                xr[cell_nodes[4]]};
+        auto qf = QuadratureManager::instance().getPyramidFormula(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::Prism: {
+        PrismTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]],
+                              xr[cell_nodes[3]], xr[cell_nodes[4]], xr[cell_nodes[5]]};
+        auto qf = QuadratureManager::instance().getPrismFormula(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::Hexahedron: {
+        CubeTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]],
+                             xr[cell_nodes[4]], xr[cell_nodes[5]], xr[cell_nodes[6]], xr[cell_nodes[7]]};
+        auto qf = QuadratureManager::instance().getCubeFormula(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");
+      }
+      }
+    }
+  }
+  return max_error;
+}
+
+}   // namespace test_only
+
+#endif   // DISCRETE_FUNCTION_DPK_FOR_TESTS_HPP
diff --git a/tests/test_PolynomialReconstruction_degree_1.cpp b/tests/test_PolynomialReconstruction_degree_1.cpp
index 4e8b8ca57d4266acb26481e91c68c83d6139c951..fb2c7b2ec4a56239e37314704b2cc30122182e8c 100644
--- a/tests/test_PolynomialReconstruction_degree_1.cpp
+++ b/tests/test_PolynomialReconstruction_degree_1.cpp
@@ -2,279 +2,30 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_all.hpp>
 
-#include <Kokkos_Core.hpp>
-
 #include <utils/PugsAssert.hpp>
-#include <utils/Types.hpp>
-
-#include <algebra/SmallMatrix.hpp>
-#include <algebra/SmallVector.hpp>
-#include <analysis/GaussQuadratureDescriptor.hpp>
-#include <analysis/QuadratureFormula.hpp>
-#include <analysis/QuadratureManager.hpp>
-#include <geometry/CubeTransformation.hpp>
-#include <geometry/LineTransformation.hpp>
-#include <geometry/PrismTransformation.hpp>
-#include <geometry/PyramidTransformation.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>
 #include <scheme/DiscreteFunctionDPkVariant.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
 #include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/PolynomialReconstruction.hpp>
 
+#include <DiscreteFunctionDPkForTests.hpp>
 #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: {
-        PyramidTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]],
-                                xr[cell_nodes[4]]};
-        auto qf = QuadratureManager::instance().getPyramidFormula(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::Prism: {
-        PrismTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]],
-                              xr[cell_nodes[3]], xr[cell_nodes[4]], xr[cell_nodes[5]]};
-        auto qf = QuadratureManager::instance().getPrismFormula(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::Hexahedron: {
-        CubeTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]],
-                             xr[cell_nodes[4]], xr[cell_nodes[5]], xr[cell_nodes[6]], xr[cell_nodes[7]]};
-        auto qf = QuadratureManager::instance().getCubeFormula(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;
-}
-
-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: {
-        PyramidTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]],
-                                xr[cell_nodes[4]]};
-        auto qf = QuadratureManager::instance().getPyramidFormula(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::Prism: {
-        PrismTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]],
-                              xr[cell_nodes[3]], xr[cell_nodes[4]], xr[cell_nodes[5]]};
-        auto qf = QuadratureManager::instance().getPrismFormula(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::Hexahedron: {
-        CubeTransformation T{xr[cell_nodes[0]], xr[cell_nodes[1]], xr[cell_nodes[2]], xr[cell_nodes[3]],
-                             xr[cell_nodes[4]], xr[cell_nodes[5]], xr[cell_nodes[6]], xr[cell_nodes[7]]};
-        auto qf = QuadratureManager::instance().getCubeFormula(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");
-      }
-      }
-    }
-  }
-  return max_error;
-}
-
-}   // namespace test_only
-
 TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
 {
+  constexpr size_t degree = 1;
+
   SECTION("without symmetries")
   {
     std::vector<PolynomialReconstructionDescriptor> descriptor_list =
-      {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, 1},
-       PolynomialReconstructionDescriptor{IntegrationMethodType::element, 1},
-       PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, 1}};
+      {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, degree},
+       PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree},
+       PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree}};
 
     for (auto descriptor : descriptor_list) {
       SECTION(name(descriptor.integrationMethodType()))
@@ -292,12 +43,8 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto& mesh  = *p_mesh;
 
                 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_exact(xj[cell_id]); });
+                DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact));
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
 
@@ -324,12 +71,8 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                             +1.4 - 0.6 * x[0],   //
                             -0.2 + 3.1 * x[0]};
                 };
-                auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
-
-                DiscreteFunctionP0<R3> uh{p_mesh};
 
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_exact(xj[cell_id]); });
+                DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact));
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
 
@@ -358,12 +101,8 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                     -4.1 + 3.1 * x[0], +0.8 + 2.9 * x[0], -1.6 + 2.3 * x[0],
                   };
                 };
-                auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
 
-                DiscreteFunctionP0<R3x3> Ah{p_mesh};
-
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R3x3_exact(xj[cell_id]); });
+                DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R3x3_exact));
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah);
 
@@ -387,16 +126,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                    [](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();
-
-                DiscreteFunctionP0Vector<double> Vh{p_mesh, 3};
-
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                    for (size_t i = 0; i < vector_exact.size(); ++i) {
-                      Vh[cell_id][i] = vector_exact[i](xj[cell_id]);
-                    }
-                  });
+                DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
                 auto dpk_Vh          = reconstructions[0]->get<DiscreteFunctionDPkVector<1, const double>>();
@@ -418,18 +148,14 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto& mesh  = *p_mesh;
 
                 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]}; }};
+                  {[](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();
-
-                DiscreteFunctionP0Vector<R3> Vh{p_mesh, 2};
-
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                    Vh[cell_id][0] = vector_exact[0](xj[cell_id]);
-                    Vh[cell_id][1] = vector_exact[1](xj[cell_id]);
-                  });
+                DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
 
@@ -473,27 +199,10 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                    [](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_exact(xj[cell_id]); });
-
-                DiscreteFunctionP0<R3> uh{p_mesh};
-                parallel_for(
-                  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_exact(xj[cell_id]); });
-
-                DiscreteFunctionP0Vector<double> Vh{p_mesh, 3};
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                    for (size_t i = 0; i < vector_exact.size(); ++i) {
-                      Vh[cell_id][i] = vector_exact[i](xj[cell_id]);
-                    }
-                  });
+                DiscreteFunctionP0 fh       = test_only::exact_projection(mesh, degree, std::function(R_exact));
+                DiscreteFunctionP0 uh       = test_only::exact_projection(mesh, degree, std::function(R3_exact));
+                DiscreteFunctionP0 Ah       = test_only::exact_projection(mesh, degree, std::function(R3x3_exact));
+                DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
 
                 auto reconstructions =
                   PolynomialReconstruction{descriptor}.build(std::make_shared<DiscreteFunctionVariant>(fh), uh,
@@ -541,12 +250,8 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto& mesh  = *p_mesh;
 
                 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_exact(xj[cell_id]); });
+                DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact));
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
 
@@ -573,12 +278,8 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                             +1.4 - 0.6 * x[0] + 1.3 * x[1],   //
                             -0.2 + 3.1 * x[0] - 1.1 * x[1]};
                 };
-                auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
-
-                DiscreteFunctionP0<R3> uh{p_mesh};
 
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_exact(xj[cell_id]); });
+                DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact));
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
 
@@ -604,12 +305,8 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                   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]};
                 };
-                auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
-
-                DiscreteFunctionP0<R2x2> Ah{p_mesh};
 
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R2x2_exact(xj[cell_id]); });
+                DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact));
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah);
 
@@ -634,16 +331,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                    [](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) {
-                    for (size_t i = 0; i < vector_exact.size(); ++i) {
-                      Vh[cell_id][i] = vector_exact[i](xj[cell_id]);
-                    }
-                  });
+                DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
 
@@ -668,19 +356,15 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                 auto& mesh  = *p_mesh;
 
                 auto R_exact = [](const R3& x) { return 2.3 + 1.7 * x[0] - 1.3 * x[1] + 2.1 * x[2]; };
-                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_exact(xj[cell_id]); });
+                DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R_exact));
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
 
                 auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<3, 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));
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
               }
             }
           }
@@ -698,12 +382,8 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                             +1.4 - 0.6 * x[0] + 1.3 * x[1] - 3.7 * x[2],   //
                             -0.2 + 3.1 * x[0] - 1.1 * x[1] + 1.9 * x[2]};
                 };
-                auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
-
-                DiscreteFunctionP0<R3> uh{p_mesh};
 
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { uh[cell_id] = R3_exact(xj[cell_id]); });
+                DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact));
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
 
@@ -730,12 +410,8 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                               //
                               +2.4 - 2.3 * x[0] + 1.3 * x[1] + 1.4 * x[2], -0.2 + 3.1 * x[0] + 0.8 * x[1] - 1.8 * x[2]};
                 };
-                auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
 
-                DiscreteFunctionP0<R2x2> Ah{p_mesh};
-
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { Ah[cell_id] = R2x2_exact(xj[cell_id]); });
+                DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact));
 
                 descriptor.setRowWeighting(false);
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah);
@@ -761,16 +437,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                    [](const R3& x) -> double { return +2.4 - 2.3 * x[0] + 1.3 * x[1] + 1.4 * x[2]; },
                    [](const R3& x) -> double { return -0.2 + 3.1 * x[0] + 0.8 * x[1] - 1.8 * x[2]; }};
 
-                auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
-
-                DiscreteFunctionP0Vector<double> Vh{p_mesh, 4};
-
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                    for (size_t i = 0; i < vector_exact.size(); ++i) {
-                      Vh[cell_id][i] = vector_exact[i](xj[cell_id]);
-                    }
-                  });
+                DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
 
                 descriptor.setPreconditioning(false);
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
@@ -807,7 +474,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
       {
         auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
 
-        PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, 1,
+        PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, degree,
                                                       std::vector<std::shared_ptr<const IBoundaryDescriptor>>{
                                                         std::make_shared<NamedBoundaryDescriptor>("XMIN")}};
 
@@ -844,7 +511,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
       {
         auto p_mesh = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
 
-        PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, 1,
+        PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, degree,
                                                       std::vector<std::shared_ptr<const IBoundaryDescriptor>>{
                                                         std::make_shared<NamedBoundaryDescriptor>("XMIN")}};
 
@@ -881,7 +548,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
       {
         auto p_mesh = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
 
-        PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, 1,
+        PolynomialReconstructionDescriptor descriptor{IntegrationMethodType::element, degree,
                                                       std::vector<std::shared_ptr<const IBoundaryDescriptor>>{
                                                         std::make_shared<NamedBoundaryDescriptor>("XMIN")}};
 
@@ -918,13 +585,13 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
     SECTION("1D")
     {
       std::vector<PolynomialReconstructionDescriptor> descriptor_list =
-        {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, 1,
+        {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, degree,
                                             std::vector<std::shared_ptr<const IBoundaryDescriptor>>{
                                               std::make_shared<NamedBoundaryDescriptor>("XMIN")}},
-         PolynomialReconstructionDescriptor{IntegrationMethodType::element, 1,
+         PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree,
                                             std::vector<std::shared_ptr<const IBoundaryDescriptor>>{
                                               std::make_shared<NamedBoundaryDescriptor>("XMIN")}},
-         PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, 1,
+         PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree,
                                             std::vector<std::shared_ptr<const IBoundaryDescriptor>>{
                                               std::make_shared<NamedBoundaryDescriptor>("XMIN")}}};
 
@@ -940,12 +607,8 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
             auto& mesh = *p_mesh;
 
             auto R1_exact = [](const R1& x) { return R1{1.7 * (x[0] + 1)}; };
-            auto xj       = MeshDataManager::instance().getMeshData(mesh).xj();
 
-            DiscreteFunctionP0<R1> fh{p_mesh};
-
-            parallel_for(
-              mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R1_exact(xj[cell_id]); });
+            DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R1_exact));
 
             auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
 
@@ -967,15 +630,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                   = {[](const R1& x) -> R1 { return R1{+1.7 * (x[0] + 1)}; },
                      [](const R1& x) -> R1 { return R1{-0.3 * (x[0] + 1)}; }};
 
-                auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
-
-                DiscreteFunctionP0Vector<R1> Vh{p_mesh, 2};
-
-                parallel_for(
-                  mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                    Vh[cell_id][0] = vector_exact[0](xj[cell_id]);
-                    Vh[cell_id][1] = vector_exact[1](xj[cell_id]);
-                  });
+                DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
 
                 auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
 
@@ -993,21 +648,21 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
     SECTION("2D")
     {
       std::vector<PolynomialReconstructionDescriptor> descriptor_list =
-        {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, 1,
+        {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, degree,
                                             std::vector<std::shared_ptr<
                                               const IBoundaryDescriptor>>{std::
                                                                             make_shared<NamedBoundaryDescriptor>(
                                                                               "XMAX"),
                                                                           std::make_shared<NamedBoundaryDescriptor>(
                                                                             "YMAX")}},
-         PolynomialReconstructionDescriptor{IntegrationMethodType::element, 1,
+         PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree,
                                             std::vector<std::shared_ptr<
                                               const IBoundaryDescriptor>>{std::
                                                                             make_shared<NamedBoundaryDescriptor>(
                                                                               "XMAX"),
                                                                           std::make_shared<NamedBoundaryDescriptor>(
                                                                             "YMAX")}},
-         PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, 1,
+         PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree,
                                             std::vector<std::shared_ptr<
                                               const IBoundaryDescriptor>>{std::
                                                                             make_shared<NamedBoundaryDescriptor>(
@@ -1026,18 +681,14 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
             auto& mesh  = *p_mesh;
 
             auto R2_exact = [](const R2& x) -> R2 { return R2{2.3 * (x[0] - 2), -1.3 * (x[1] - 1)}; };
-            auto xj       = MeshDataManager::instance().getMeshData(mesh).xj();
 
-            DiscreteFunctionP0<R2> fh{p_mesh};
+            DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R2_exact));
 
-            parallel_for(
-              mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R2_exact(xj[cell_id]); });
+            auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
 
-            auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
-
-            auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2>>();
+            auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2>>();
 
-            double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R2_exact));
+            double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R2_exact));
             REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
           }
 
@@ -1047,18 +698,14 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
             auto& mesh  = *p_mesh;
 
             std::array<std::function<R2(const R2&)>, 2> vector_exact   //
-              = {[](const R2& x) -> R2 { return R2{+1.7 * (x[0] - 2), -0.6 * (x[1] - 1)}; },
-                 [](const R2& x) -> R2 { return R2{-2.3 * (x[0] - 2), +1.1 * (x[1] - 1)}; }};
-
-            auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
+              = {[](const R2& x) -> R2 {
+                   return R2{+1.7 * (x[0] - 2), -0.6 * (x[1] - 1)};
+                 },
+                 [](const R2& x) -> R2 {
+                   return R2{-2.3 * (x[0] - 2), +1.1 * (x[1] - 1)};
+                 }};
 
-            DiscreteFunctionP0Vector<R2> Vh{p_mesh, 2};
-
-            parallel_for(
-              mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                Vh[cell_id][0] = vector_exact[0](xj[cell_id]);
-                Vh[cell_id][1] = vector_exact[1](xj[cell_id]);
-              });
+            DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
 
             auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
 
@@ -1074,7 +721,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
     SECTION("3D")
     {
       std::vector<PolynomialReconstructionDescriptor> descriptor_list =
-        {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, 1,
+        {PolynomialReconstructionDescriptor{IntegrationMethodType::cell_center, degree,
                                             std::vector<std::shared_ptr<
                                               const IBoundaryDescriptor>>{std::
                                                                             make_shared<NamedBoundaryDescriptor>(
@@ -1083,7 +730,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                                                                             "YMAX"),
                                                                           std::make_shared<NamedBoundaryDescriptor>(
                                                                             "ZMAX")}},
-         PolynomialReconstructionDescriptor{IntegrationMethodType::element, 1,
+         PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree,
                                             std::vector<std::shared_ptr<
                                               const IBoundaryDescriptor>>{std::
                                                                             make_shared<NamedBoundaryDescriptor>(
@@ -1092,7 +739,7 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
                                                                             "YMAX"),
                                                                           std::make_shared<NamedBoundaryDescriptor>(
                                                                             "ZMAX")}},
-         PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, 1,
+         PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree,
                                             std::vector<std::shared_ptr<
                                               const IBoundaryDescriptor>>{std::
                                                                             make_shared<NamedBoundaryDescriptor>(
@@ -1113,18 +760,14 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
             auto& mesh  = *p_mesh;
 
             auto R3_exact = [](const R3& x) -> R3 { return R3{2.3 * (x[0] - 2), -1.3 * (x[1] - 1), 1.4 * (x[2] - 1)}; };
-            auto xj       = MeshDataManager::instance().getMeshData(mesh).xj();
-
-            DiscreteFunctionP0<R3> fh{p_mesh};
 
-            parallel_for(
-              mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { fh[cell_id] = R3_exact(xj[cell_id]); });
+            DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact));
 
-            auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
+            auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
 
-            auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<3, const R3>>();
+            auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const R3>>();
 
-            double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R3_exact));
+            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-12));
           }
 
@@ -1134,18 +777,14 @@ TEST_CASE("PolynomialReconstruction_degree_1", "[scheme]")
             auto& mesh  = *p_mesh;
 
             std::array<std::function<R3(const R3&)>, 2> vector_exact   //
-              = {[](const R3& x) -> R3 { return R3{+1.7 * (x[0] - 2), -0.6 * (x[1] - 1), +1.2 * (x[2] - 1)}; },
-                 [](const R3& x) -> R3 { return R3{-2.3 * (x[0] - 2), +1.1 * (x[1] - 1), -0.3 * (x[2] - 1)}; }};
-
-            auto xj = MeshDataManager::instance().getMeshData(mesh).xj();
-
-            DiscreteFunctionP0Vector<R3> Vh{p_mesh, 2};
-
-            parallel_for(
-              mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-                Vh[cell_id][0] = vector_exact[0](xj[cell_id]);
-                Vh[cell_id][1] = vector_exact[1](xj[cell_id]);
-              });
+              = {[](const R3& x) -> R3 {
+                   return R3{+1.7 * (x[0] - 2), -0.6 * (x[1] - 1), +1.2 * (x[2] - 1)};
+                 },
+                 [](const R3& x) -> R3 {
+                   return R3{-2.3 * (x[0] - 2), +1.1 * (x[1] - 1), -0.3 * (x[2] - 1)};
+                 }};
+
+            DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
 
             auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);