diff --git a/src/geometry/LineTransformation.hpp b/src/geometry/LineTransformation.hpp
index c9f7def71f9d342cca580debf09aa8e77316c556..4576c0b64904ecb489caeb456b10271be2a076cc 100644
--- a/src/geometry/LineTransformation.hpp
+++ b/src/geometry/LineTransformation.hpp
@@ -24,12 +24,20 @@ class LineTransformation<1>
     return m_jacobian * x + m_shift;
   }
 
+  PUGS_INLINE
   double
   jacobianDeterminant() const
   {
     return m_jacobian;
   }
 
+  PUGS_INLINE
+  double
+  jacobianDeterminant(const TinyVector<1>&) const
+  {
+    return m_jacobian;
+  }
+
   PUGS_INLINE
   LineTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b)
   {
diff --git a/src/geometry/TetrahedronTransformation.hpp b/src/geometry/TetrahedronTransformation.hpp
index 64244266957a350e8be8f78c2eab3d5fead629e1..a81a344bfbabd0999e45b1bcab0b52519f9db4ed 100644
--- a/src/geometry/TetrahedronTransformation.hpp
+++ b/src/geometry/TetrahedronTransformation.hpp
@@ -24,12 +24,20 @@ class TetrahedronTransformation
     return m_jacobian * x + m_shift;
   }
 
+  PUGS_INLINE
   double
   jacobianDeterminant() const
   {
     return m_jacobian_determinant;
   }
 
+  PUGS_INLINE
+  double
+  jacobianDeterminant(const TinyVector<3>&) const
+  {
+    return m_jacobian_determinant;
+  }
+
   PUGS_INLINE
   TetrahedronTransformation(const TinyVector<Dimension>& a,
                             const TinyVector<Dimension>& b,
diff --git a/src/geometry/TriangleTransformation.hpp b/src/geometry/TriangleTransformation.hpp
index 9210e0bda55bb19dfdfd877e509801fa87f0a4f0..df8444d894a7ed56c0fe3825ffbf1cc14a742531 100644
--- a/src/geometry/TriangleTransformation.hpp
+++ b/src/geometry/TriangleTransformation.hpp
@@ -26,12 +26,20 @@ class TriangleTransformation<2>
     return m_jacobian * x + m_shift;
   }
 
+  PUGS_INLINE
   double
   jacobianDeterminant() const
   {
     return m_jacobian_determinant;
   }
 
+  PUGS_INLINE
+  double
+  jacobianDeterminant(const TinyVector<2>&) const
+  {
+    return m_jacobian_determinant;
+  }
+
   PUGS_INLINE
   TriangleTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b, const TinyVector<Dimension>& c)
   {
diff --git a/src/mesh/StencilBuilder.cpp b/src/mesh/StencilBuilder.cpp
index 20fc3bf61dd6a59ef82ff7bdb25dc5e32c7eef24..de7501d44b5b0aef6e50f830460e9be110456331 100644
--- a/src/mesh/StencilBuilder.cpp
+++ b/src/mesh/StencilBuilder.cpp
@@ -447,8 +447,7 @@ StencilBuilder::_build_for_same_source_and_target(const ConnectivityType& connec
   }
 
   Array<uint32_t> column_indices{column_indices_vector.size()};
-  parallel_for(
-    column_indices_vector.size(), PUGS_LAMBDA(size_t i) { column_indices[i] = column_indices_vector[i]; });
+  parallel_for(column_indices_vector.size(), PUGS_LAMBDA(size_t i) { column_indices[i] = column_indices_vector[i]; });
 
   ConnectivityMatrix primal_stencil{row_map, column_indices};
 
@@ -743,8 +742,7 @@ StencilBuilder::_build_for_different_source_and_target(
   }
 
   Array<uint32_t> column_indices{column_indices_vector.size()};
-  parallel_for(
-    column_indices_vector.size(), PUGS_LAMBDA(size_t i) { column_indices[i] = column_indices_vector[i]; });
+  parallel_for(column_indices_vector.size(), PUGS_LAMBDA(size_t i) { column_indices[i] = column_indices_vector[i]; });
 
   ConnectivityMatrix primal_stencil{row_map, column_indices};
 
@@ -836,7 +834,7 @@ StencilBuilder::buildC2C(const IConnectivity& connectivity,
   if ((parallel::size() > 1) and
       (stencil_descriptor.numberOfLayers() > GlobalVariableManager::instance().getNumberOfGhostLayers())) {
     std::ostringstream error_msg;
-    error_msg << "Stencil builder requires" << rang::fgB::yellow << stencil_descriptor.numberOfLayers()
+    error_msg << "Stencil builder requires " << rang::fgB::yellow << stencil_descriptor.numberOfLayers()
               << rang::fg::reset << " layers while parallel number of ghost layer is "
               << GlobalVariableManager::instance().getNumberOfGhostLayers() << ".\n";
     error_msg << "Increase the number of ghost layers (using the '--number-of-ghost-layers' option).";
@@ -870,7 +868,7 @@ StencilBuilder::buildN2C(const IConnectivity& connectivity,
   if ((parallel::size() > 1) and
       (stencil_descriptor.numberOfLayers() > GlobalVariableManager::instance().getNumberOfGhostLayers())) {
     std::ostringstream error_msg;
-    error_msg << "Stencil builder requires" << rang::fgB::yellow << stencil_descriptor.numberOfLayers()
+    error_msg << "Stencil builder requires " << rang::fgB::yellow << stencil_descriptor.numberOfLayers()
               << rang::fg::reset << " layers while parallel number of ghost layer is "
               << GlobalVariableManager::instance().getNumberOfGhostLayers() << ".\n";
     error_msg << "Increase the number of ghost layers (using the '--number-of-ghost-layers' option).";
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index d7ba065480be4aac1949b813df03fb34d94d3f56..e3739abe7afd3a625b6510d9be792bdb3b3a76dd 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -280,6 +280,7 @@ add_executable (mpi_unit_tests
   test_ParallelChecker_read.cpp
   test_Partitioner.cpp
   test_PolynomialReconstruction_degree_1.cpp
+  test_PolynomialReconstruction_degree_2.cpp
   test_PolynomialReconstructionDescriptor.cpp
   test_RandomEngine.cpp
   test_StencilBuilder_cell2cell.cpp
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/MeshDataBaseForTests.cpp b/tests/MeshDataBaseForTests.cpp
index 1f2b485edcb7f7b3fadf35d845b59f23c1a03274..1615673d0ba28d7328ab5faef7830a774d5c75f4 100644
--- a/tests/MeshDataBaseForTests.cpp
+++ b/tests/MeshDataBaseForTests.cpp
@@ -1,11 +1,15 @@
 #include <MeshDataBaseForTests.hpp>
+
 #include <mesh/CartesianMeshBuilder.hpp>
 #include <mesh/Connectivity.hpp>
 #include <mesh/GmshReader.hpp>
 #include <mesh/MeshVariant.hpp>
+#include <utils/GlobalVariableManager.hpp>
 #include <utils/Messenger.hpp>
 #include <utils/PugsAssert.hpp>
 
+#include <NbGhostLayersTester.hpp>
+
 #include <filesystem>
 #include <fstream>
 
@@ -13,17 +17,22 @@ const MeshDataBaseForTests* MeshDataBaseForTests::m_instance = nullptr;
 
 MeshDataBaseForTests::MeshDataBaseForTests()
 {
-  m_cartesian_1d_mesh = CartesianMeshBuilder{TinyVector<1>{-1}, TinyVector<1>{3}, TinyVector<1, size_t>{23}}.mesh();
+  for (size_t nb_ghost_layers = 1; nb_ghost_layers <= m_max_nb_ghost_layers; ++nb_ghost_layers) {
+    NbGhostLayersTester t{nb_ghost_layers};
 
-  m_cartesian_2d_mesh =
-    CartesianMeshBuilder{TinyVector<2>{0, -1}, TinyVector<2>{3, 2}, TinyVector<2, size_t>{6, 7}}.mesh();
+    m_cartesian_1d_mesh.push_back(
+      CartesianMeshBuilder{TinyVector<1>{-1}, TinyVector<1>{3}, TinyVector<1, size_t>{23}}.mesh());
 
-  m_cartesian_3d_mesh =
-    CartesianMeshBuilder{TinyVector<3>{0, 1, 0}, TinyVector<3>{2, -1, 3}, TinyVector<3, size_t>{6, 7, 4}}.mesh();
+    m_cartesian_2d_mesh.push_back(
+      CartesianMeshBuilder{TinyVector<2>{0, -1}, TinyVector<2>{3, 2}, TinyVector<2, size_t>{6, 7}}.mesh());
 
-  m_unordered_1d_mesh = _buildUnordered1dMesh();
-  m_hybrid_2d_mesh    = _buildHybrid2dMesh();
-  m_hybrid_3d_mesh    = _buildHybrid3dMesh();
+    m_cartesian_3d_mesh.push_back(
+      CartesianMeshBuilder{TinyVector<3>{0, 1, 0}, TinyVector<3>{2, -1, 3}, TinyVector<3, size_t>{6, 7, 4}}.mesh());
+
+    m_unordered_1d_mesh.push_back(_buildUnordered1dMesh());
+    m_hybrid_2d_mesh.push_back(_buildHybrid2dMesh());
+    m_hybrid_3d_mesh.push_back(_buildHybrid3dMesh());
+  }
 }
 
 const MeshDataBaseForTests&
@@ -50,37 +59,49 @@ MeshDataBaseForTests::destroy()
 std::shared_ptr<const MeshVariant>
 MeshDataBaseForTests::cartesian1DMesh() const
 {
-  return m_cartesian_1d_mesh;
+  const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers();
+  Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers));
+  return m_cartesian_1d_mesh[nb_ghost_layers - 1];
 }
 
 std::shared_ptr<const MeshVariant>
 MeshDataBaseForTests::cartesian2DMesh() const
 {
-  return m_cartesian_2d_mesh;
+  const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers();
+  Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers));
+  return m_cartesian_2d_mesh[nb_ghost_layers - 1];
 }
 
 std::shared_ptr<const MeshVariant>
 MeshDataBaseForTests::cartesian3DMesh() const
 {
-  return m_cartesian_3d_mesh;
+  const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers();
+  Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers));
+  return m_cartesian_3d_mesh[nb_ghost_layers - 1];
 }
 
 std::shared_ptr<const MeshVariant>
 MeshDataBaseForTests::unordered1DMesh() const
 {
-  return m_unordered_1d_mesh;
+  const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers();
+  Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers));
+  return m_unordered_1d_mesh[nb_ghost_layers - 1];
 }
 
 std::shared_ptr<const MeshVariant>
 MeshDataBaseForTests::hybrid2DMesh() const
 {
-  return m_hybrid_2d_mesh;
+  const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers();
+  Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers));
+  return m_hybrid_2d_mesh[nb_ghost_layers - 1];
 }
 
 std::shared_ptr<const MeshVariant>
 MeshDataBaseForTests::hybrid3DMesh() const
 {
-  return m_hybrid_3d_mesh;
+  const size_t nb_ghost_layers = GlobalVariableManager::instance().getNumberOfGhostLayers();
+  Assert((nb_ghost_layers >= 1) and (nb_ghost_layers <= m_max_nb_ghost_layers));
+  return m_hybrid_3d_mesh[nb_ghost_layers - 1];
 }
 
 std::shared_ptr<const MeshVariant>
diff --git a/tests/MeshDataBaseForTests.hpp b/tests/MeshDataBaseForTests.hpp
index 2b092d544271fbd1b1d0488ed73c9c5a401d548b..f0ab29fa9a927c01f16f94925fba7eb750ffd591 100644
--- a/tests/MeshDataBaseForTests.hpp
+++ b/tests/MeshDataBaseForTests.hpp
@@ -4,6 +4,7 @@
 #include <array>
 #include <memory>
 #include <string>
+#include <vector>
 
 class MeshVariant;
 
@@ -35,15 +36,17 @@ class MeshDataBaseForTests
  private:
   explicit MeshDataBaseForTests();
 
+  constexpr static size_t m_max_nb_ghost_layers = 3;
+
   static const MeshDataBaseForTests* m_instance;
 
-  std::shared_ptr<const MeshVariant> m_cartesian_1d_mesh;
-  std::shared_ptr<const MeshVariant> m_cartesian_2d_mesh;
-  std::shared_ptr<const MeshVariant> m_cartesian_3d_mesh;
+  std::vector<std::shared_ptr<const MeshVariant>> m_cartesian_1d_mesh;
+  std::vector<std::shared_ptr<const MeshVariant>> m_cartesian_2d_mesh;
+  std::vector<std::shared_ptr<const MeshVariant>> m_cartesian_3d_mesh;
 
-  std::shared_ptr<const MeshVariant> m_unordered_1d_mesh;
-  std::shared_ptr<const MeshVariant> m_hybrid_2d_mesh;
-  std::shared_ptr<const MeshVariant> m_hybrid_3d_mesh;
+  std::vector<std::shared_ptr<const MeshVariant>> m_unordered_1d_mesh;
+  std::vector<std::shared_ptr<const MeshVariant>> m_hybrid_2d_mesh;
+  std::vector<std::shared_ptr<const MeshVariant>> m_hybrid_3d_mesh;
 
   std::shared_ptr<const MeshVariant> _buildUnordered1dMesh();
   std::shared_ptr<const MeshVariant> _buildHybrid2dMesh();
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);
 
diff --git a/tests/test_PolynomialReconstruction_degree_2.cpp b/tests/test_PolynomialReconstruction_degree_2.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dce627cabab012e398a271b88aafc5131e4338d5
--- /dev/null
+++ b/tests/test_PolynomialReconstruction_degree_2.cpp
@@ -0,0 +1,716 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/PugsAssert.hpp>
+
+#include <mesh/Mesh.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 <NbGhostLayersTester.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("PolynomialReconstruction_degree_2", "[scheme]")
+{
+  constexpr size_t degree = 2;
+
+  constexpr size_t nb_ghost_layers = 2;
+  NbGhostLayersTester t{nb_ghost_layers};
+
+  SECTION("without symmetries")
+  {
+    std::vector<PolynomialReconstructionDescriptor> descriptor_list =
+      {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree},
+       PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree}};
+
+    for (auto descriptor : descriptor_list) {
+      SECTION(name(descriptor.integrationMethodType()))
+      {
+        SECTION("1D")
+        {
+          using R1 = TinyVector<1>;
+
+          SECTION("R data")
+          {
+            for (auto named_mesh : MeshDataBaseForTests::get().all1DMeshes()) {
+              SECTION(named_mesh.name())
+              {
+                auto p_mesh = named_mesh.mesh()->get<Mesh<1>>();
+                auto& mesh  = *p_mesh;
+
+                auto R_exact = [](const R1& x) { return 2.3 + 1.7 * x[0] - 1.4 * x[0] * x[0]; };
+
+                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<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));
+              }
+            }
+          }
+
+          SECTION("R^3 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;
+
+                auto R3_exact = [](const R1& x) -> R3 {
+                  return R3{+2.3 + 1.7 * x[0] - x[0] * x[0],       //
+                            +1.4 - 0.6 * x[0] + 2 * x[0] * x[0],   //
+                            -0.2 + 3.1 * x[0] + 1.4 * x[0] * x[0]};
+                };
+
+                DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact));
+
+                auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
+
+                auto dpk_uh = reconstructions[0]->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));
+              }
+            }
+          }
+
+          SECTION("R^3x3 data")
+          {
+            using R3x3 = TinyMatrix<3, 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;
+
+                auto R3x3_exact = [](const R1& x) -> R3x3 {
+                  return R3x3{+2.3 + 1.7 * x[0] - 2.3 * x[0] * x[0],   //
+                              -1.7 + 2.1 * x[0] + 1.2 * x[0] * x[0],   //
+                              +1.4 - 0.6 * x[0] - 2.0 * x[0] * x[0],   //
+                                                                       //
+                              +2.4 - 2.3 * x[0] + 1.1 * x[0] * x[0],   //
+                              -0.2 + 3.1 * x[0] - 0.7 * x[0] * x[0],   //
+                              -3.2 - 3.6 * x[0] + 0.1 * x[0] * x[0],   //
+                              //
+                              -4.1 + 3.1 * x[0] - 0.2 * x[0] * x[0],   //
+                              +0.8 + 2.9 * x[0] + 4.1 * x[0] * x[0],   //
+                              -1.6 + 2.3 * x[0] - 1.7 * x[0] * x[0]};
+                };
+
+                DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R3x3_exact));
+
+                auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah);
+
+                auto dpk_Ah = reconstructions[0]->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));
+              }
+            }
+          }
+
+          SECTION("R vector data")
+          {
+            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] + 1.2 * x[0] * x[0]; },
+                   [](const R1& x) -> double { return -1.7 + 2.1 * x[0] + 2.1 * x[0] * x[0]; },
+                   [](const R1& x) -> double { return +1.4 - 0.6 * x[0] - 1.3 * x[0] * x[0]; }};
+
+                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>>();
+
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
+              }
+            }
+          }
+
+          SECTION("R3 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<R3(const R1&)>, 2> vector_exact =
+                  {[](const R1& x) -> R3 {
+                     return R3{+2.3 + 1.7 * x[0] + 0.8 * x[0] * x[0],   //
+                               -1.7 + 2.1 * x[0] - 0.7 * x[0] * x[0],   //
+                               +1.4 - 0.6 * x[0] + 1.9 * x[0] * 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]}; }};
+
+                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 R3>>();
+
+                double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+                REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-14));
+              }
+            }
+          }
+
+          SECTION("list of various types")
+          {
+            using R3x3 = TinyMatrix<3>;
+            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;
+
+                auto R_exact = [](const R1& x) { return 2.3 + 1.7 * x[0]; };
+
+                auto R3_exact = [](const R1& x) -> R3 {
+                  return R3{+2.3 + 1.7 * x[0] + 2.3 * x[0] * x[0],   //
+                            +1.4 - 0.6 * x[0] - 1.5 * x[0] * x[0],   //
+                            -0.2 + 3.1 * x[0] + 2.9 * x[0] * x[0]};
+                };
+
+                auto R3x3_exact = [](const R1& x) -> R3x3 {
+                  return R3x3{
+                    +2.3 + 1.7 * x[0] - 0.3 * x[0] * x[0],
+                    -1.7 + 2.1 * x[0] + 1.4 * x[0] * x[0],
+                    +1.4 - 0.6 * x[0] - 2.3 * x[0] * x[0],
+                    //
+                    +2.4 - 2.3 * x[0] + 1.8 * x[0] * x[0],
+                    -0.2 + 3.1 * x[0] - 1.7 * x[0] * x[0],
+                    -3.2 - 3.6 * x[0] + 0.7 * x[0] * x[0],
+                    //
+                    -4.1 + 3.1 * x[0] - 1.9 * x[0] * x[0],
+                    +0.8 + 2.9 * x[0] + 2.2 * x[0] * x[0],
+                    -1.6 + 2.3 * x[0] - 1.3 * x[0] * x[0],
+                  };
+                };
+
+                std::array<std::function<double(const R1&)>, 3> vector_exact =
+                  {[](const R1& x) -> double { return +2.3 + 1.7 * x[0] + 2.2 * x[0] * x[0]; },
+                   [](const R1& x) -> double { return -1.7 + 2.1 * x[0] - 1.9 * x[0] * x[0]; },
+                   [](const R1& x) -> double { return +1.4 - 0.6 * x[0] + 3.1 * x[0] * x[0]; }};
+
+                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,
+                                                             std::make_shared<DiscreteFunctionP0<R3x3>>(Ah),
+                                                             DiscreteFunctionVariant(Vh));
+
+                {
+                  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_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));
+                }
+
+                {
+                  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));
+                }
+
+                {
+                  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));
+                }
+              }
+            }
+          }
+        }
+
+        SECTION("2D")
+        {
+          using R2 = TinyVector<2>;
+
+          SECTION("R data")
+          {
+            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 R_exact = [](const R2& x) {
+                  return 2.3 + 1.7 * x[0] - 1.3 * x[1]   //
+                         + 1.2 * x[0] * x[0] + 1.3 * x[0] * x[1] - 3.2 * x[1] * x[1];
+                };
+
+                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<2, 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-12));
+              }
+            }
+          }
+
+          SECTION("R^3 data")
+          {
+            using R3 = TinyVector<3>;
+
+            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 R3_exact = [](const R2& x) -> R3 {
+                  return R3{+2.3 + 1.7 * x[0] - 2.2 * x[1]                                   //
+                              - 2.1 * x[0] * x[0] - 2.3 * x[0] * x[1] - 3.2 * x[1] * x[1],   //
+                            +1.4 - 0.6 * x[0] + 1.3 * x[1]                                   //
+                              + 2.3 * x[0] * x[0] - 1.3 * x[0] * x[1] + 1.2 * x[1] * x[1],   //
+                            -0.2 + 3.1 * x[0] - 1.1 * x[1]                                   //
+                              - 2.1 * x[0] * x[0] + 1.3 * x[0] * x[1] - 1.1 * x[1] * x[1]};
+                };
+
+                DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact));
+
+                auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
+
+                auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, 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-12));
+              }
+            }
+          }
+
+          SECTION("R^2x2 data")
+          {
+            using R2x2 = TinyMatrix<2, 2>;
+
+            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 R2x2_exact = [](const R2& x) -> R2x2 {
+                  return R2x2{+2.3 + 1.7 * x[0] + 1.2 * x[1]                                   //
+                                - 2.1 * x[0] * x[0] + 1.3 * x[0] * x[1] + 1.2 * x[1] * x[1],   //
+                              -1.7 + 2.1 * x[0] - 2.2 * x[1]                                   //
+                                - 1.2 * x[0] * x[0] + 2.1 * x[0] * x[1] - 1.3 * x[1] * x[1],   //
+                              +1.4 - 0.6 * x[0] - 2.1 * x[1]                                   //
+                                - 1.1 * x[0] * x[0] - 2.3 * x[0] * x[1] + 2.1 * x[1] * x[1],
+                              +2.4 - 2.3 * x[0] + 1.3 * x[1]   //
+                                + 2.7 * x[0] * x[0] + 2.1 * x[0] * x[1] - 2.7 * x[1] * x[1]};
+                };
+
+                DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact));
+
+                auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah);
+
+                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-12));
+              }
+            }
+          }
+
+          SECTION("vector data")
+          {
+            for (auto named_mesh : MeshDataBaseForTests::get().all2DMeshes()) {
+              SECTION(named_mesh.name())
+              {
+                auto p_mesh = named_mesh.mesh()->get<Mesh<2>>();
+                auto& mesh  = *p_mesh;
+
+                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] + 1.2 * x[0] * x[0] - 2.1 * x[0] * x[1] + 3.1 * x[1] * x[1];
+                   },
+                   [](const R2& x) -> double {
+                     return -1.7 + 2.1 * x[0] - 2.2 * x[1] - 0.7 * x[0] * x[0] + 2.2 * x[0] * x[1] - 1.6 * x[1] * x[1];
+                   },
+                   [](const R2& x) -> double {
+                     return +1.4 - 0.6 * x[0] - 2.1 * x[1] + 2.3 * x[0] * x[0] + 2.3 * x[0] * x[1] - 2.9 * x[1] * x[1];
+                   },
+                   [](const R2& x) -> double {
+                     return +2.4 - 2.3 * x[0] + 1.3 * x[1] - 2.7 * x[0] * x[0] - 1.2 * x[0] * x[1] - 0.7 * x[1] * x[1];
+                   }};
+
+                DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
+
+                auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
+
+                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-12));
+              }
+            }
+          }
+        }
+
+        SECTION("3D")
+        {
+          using R3 = TinyVector<3>;
+
+          SECTION("R data")
+          {
+            for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) {
+              SECTION(named_mesh.name())
+              {
+                auto p_mesh = named_mesh.mesh()->get<Mesh<3>>();
+                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]                    //
+                         + 1.7 * x[0] * x[0] + 1.4 * x[1] * x[1] + 1.7 * x[2] * x[2]   //
+                         - 2.3 * x[0] * x[1] + 1.6 * x[0] * x[2] - 1.9 * x[1] * x[2];
+                };
+
+                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-12));
+              }
+            }
+          }
+
+          SECTION("R^3 data")
+          {
+            for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) {
+              SECTION(named_mesh.name())
+              {
+                auto p_mesh = named_mesh.mesh()->get<Mesh<3>>();
+                auto& mesh  = *p_mesh;
+
+                auto R3_exact = [](const R3& x) -> R3 {
+                  return R3{+2.3 + 1.7 * x[0] - 2.2 * x[1] + 1.8 * x[2]                      //
+                              + 1.7 * x[0] * x[0] - 2.4 * x[1] * x[1] - 2.3 * x[2] * x[2]    //
+                              - 2.1 * x[0] * x[1] + 2.6 * x[0] * x[2] + 1.6 * x[1] * x[2],   //
+                            +1.4 - 0.6 * x[0] + 1.3 * x[1] - 3.7 * x[2]                      //
+                              + 3.1 * x[0] * x[0] - 1.1 * x[1] * x[1] + 1.7 * x[2] * x[2]    //
+                              - 2.3 * x[0] * x[1] - 2.6 * x[0] * x[2] - 1.9 * x[1] * x[2],   //
+                            -0.2 + 3.1 * x[0] - 1.1 * x[1] + 1.9 * x[2]                      //
+                              - 1.5 * x[0] * x[0] + 1.4 * x[1] * x[1] - 1.2 * x[2] * x[2]    //
+                              - 1.7 * x[0] * x[1] - 1.3 * x[0] * x[2] + 2.1 * x[1] * x[2]};
+                };
+
+                DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact));
+
+                auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
+
+                auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, 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-12));
+              }
+            }
+          }
+
+          SECTION("R^2x2 data")
+          {
+            using R2x2 = TinyMatrix<2, 2>;
+
+            for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) {
+              SECTION(named_mesh.name())
+              {
+                auto p_mesh = named_mesh.mesh()->get<Mesh<3>>();
+                auto& mesh  = *p_mesh;
+
+                auto R2x2_exact = [](const R3& x) -> R2x2 {
+                  return R2x2{
+                    +2.3 + 1.7 * x[0] + 1.2 * x[1] - 1.3 * x[2]                      //
+                      - 1.7 * x[0] * x[0] + 1.4 * x[1] * x[1] + 1.7 * x[2] * x[2]    //
+                      - 1.3 * x[0] * x[1] + 1.6 * x[0] * x[2] - 1.9 * x[1] * x[2],   //
+                    -1.7 + 2.1 * x[0] - 2.2 * x[1] - 2.4 * x[2]                      //
+                      + 3.7 * x[0] * x[0] + 1.3 * x[1] * x[1] + 1.6 * x[2] * x[2]    //
+                      - 2.1 * x[0] * x[1] - 1.5 * x[0] * x[2] - 1.7 * x[1] * x[2],   //
+                    //
+                    +2.4 - 2.3 * x[0] + 1.3 * x[1] + 1.4 * x[2]                      //
+                      - 2.1 * x[0] * x[0] + 1.7 * x[1] * x[1] + 1.8 * x[2] * x[2]    //
+                      - 1.4 * x[0] * x[1] + 1.3 * x[0] * x[2] - 2.9 * x[1] * x[2],   //
+                    -0.2 + 3.1 * x[0] + 0.8 * x[1] - 1.8 * x[2]                      //
+                      + 1.6 * x[0] * x[0] + 2.1 * x[1] * x[1] - 2.1 * x[2] * x[2]    //
+                      - 1.1 * x[0] * x[1] - 1.3 * x[0] * x[2] + 1.6 * x[1] * x[2],   //
+                  };
+                };
+
+                DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact));
+
+                descriptor.setRowWeighting(false);
+                auto reconstructions = PolynomialReconstruction{descriptor}.build(Ah);
+
+                auto dpk_Ah      = reconstructions[0]->get<DiscreteFunctionDPk<3, 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-12));
+              }
+            }
+          }
+
+          SECTION("vector data")
+          {
+            for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) {
+              SECTION(named_mesh.name())
+              {
+                auto p_mesh = named_mesh.mesh()->get<Mesh<3>>();
+                auto& mesh  = *p_mesh;
+#warning continue here
+                std::array<std::function<double(const R3&)>, 4> vector_exact =
+                  {[](const R3& x) -> double { return +2.3 + 1.7 * x[0] + 1.2 * x[1] - 1.3 * x[2]; },
+                   [](const R3& x) -> double { return -1.7 + 2.1 * x[0] - 2.2 * x[1] - 2.4 * x[2]; },
+                   [](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]; }};
+
+                DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
+
+                descriptor.setPreconditioning(false);
+                auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
+
+                auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, 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-12));
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  SECTION("with symmetries")
+  {
+    // SECTION("1D")
+    // {
+    //   std::vector<PolynomialReconstructionDescriptor> descriptor_list =
+    //     {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree,
+    //                                         std::vector<std::shared_ptr<const IBoundaryDescriptor>>{
+    //                                           std::make_shared<NamedBoundaryDescriptor>("XMIN")}},
+    //      PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree,
+    //                                         std::vector<std::shared_ptr<const IBoundaryDescriptor>>{
+    //                                           std::make_shared<NamedBoundaryDescriptor>("XMIN")}}};
+
+    //   using R1 = TinyVector<1>;
+
+    //   for (auto descriptor : descriptor_list) {
+    //     SECTION(name(descriptor.integrationMethodType()))
+    //     {
+    //       SECTION("R^1 data")
+    //       {
+    //         auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+    //         auto& mesh = *p_mesh;
+
+    //         auto R1_exact = [](const R1& x) { return R1{1.7 * (x[0] + 1)}; };
+
+    //         DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R1_exact));
+
+    //         auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
+
+    //         auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R1>>();
+
+    //         double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R1_exact));
+    //         REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+    //       }
+
+    //       SECTION("R1 vector data")
+    //       {
+    //         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<R1(const R1&)>, 2> vector_exact   //
+    //               = {[](const R1& x) -> R1 { return R1{+1.7 * (x[0] + 1)}; },
+    //                  [](const R1& x) -> R1 { return R1{-0.3 * (x[0] + 1)}; }};
+
+    //             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 R1>>();
+
+    //             double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+    //             REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+    //           }
+    //         }
+    //       }
+    //     }
+    //   }
+    // }
+
+    // SECTION("2D")
+    // {
+    //   std::vector<PolynomialReconstructionDescriptor> descriptor_list =
+    //     {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree,
+    //                                         std::vector<std::shared_ptr<
+    //                                           const IBoundaryDescriptor>>{std::
+    //                                                                         make_shared<NamedBoundaryDescriptor>(
+    //                                                                           "XMAX"),
+    //                                                                       std::make_shared<NamedBoundaryDescriptor>(
+    //                                                                         "YMAX")}},
+    //      PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree,
+    //                                         std::vector<std::shared_ptr<
+    //                                           const IBoundaryDescriptor>>{std::
+    //                                                                         make_shared<NamedBoundaryDescriptor>(
+    //                                                                           "XMAX"),
+    //                                                                       std::make_shared<NamedBoundaryDescriptor>(
+    //                                                                         "YMAX")}}};
+
+    //   using R2 = TinyVector<2>;
+
+    //   for (auto descriptor : descriptor_list) {
+    //     SECTION(name(descriptor.integrationMethodType()))
+    //     {
+    //       SECTION("R^2 data")
+    //       {
+    //         auto p_mesh = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+    //         auto& mesh  = *p_mesh;
+
+    //         auto R2_exact = [](const R2& x) -> R2 { return R2{2.3 * (x[0] - 2), -1.3 * (x[1] - 1)}; };
+
+    //         DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R2_exact));
+
+    //         auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
+
+    //         auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2>>();
+
+    //         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));
+    //       }
+
+    //       SECTION("vector of R2")
+    //       {
+    //         auto p_mesh = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+    //         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)};
+    //              }};
+
+    //         DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
+
+    //         auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
+
+    //         auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const R2>>();
+
+    //         double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+    //         REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+    //       }
+    //     }
+    //   }
+    // }
+
+    // SECTION("3D")
+    // {
+    //   std::vector<PolynomialReconstructionDescriptor> descriptor_list =
+    //     {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree,
+    //                                         std::vector<std::shared_ptr<
+    //                                           const IBoundaryDescriptor>>{std::
+    //                                                                         make_shared<NamedBoundaryDescriptor>(
+    //                                                                           "XMAX"),
+    //                                                                       std::make_shared<NamedBoundaryDescriptor>(
+    //                                                                         "YMAX"),
+    //                                                                       std::make_shared<NamedBoundaryDescriptor>(
+    //                                                                         "ZMAX")}},
+    //      PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree,
+    //                                         std::vector<std::shared_ptr<
+    //                                           const IBoundaryDescriptor>>{std::
+    //                                                                         make_shared<NamedBoundaryDescriptor>(
+    //                                                                           "XMAX"),
+    //                                                                       std::make_shared<NamedBoundaryDescriptor>(
+    //                                                                         "YMAX"),
+    //                                                                       std::make_shared<NamedBoundaryDescriptor>(
+    //                                                                         "ZMAX")}}};
+
+    //   using R3 = TinyVector<3>;
+
+    //   for (auto descriptor : descriptor_list) {
+    //     SECTION(name(descriptor.integrationMethodType()))
+    //     {
+    //       SECTION("R^3 data")
+    //       {
+    //         auto p_mesh = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+    //         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)};
+    //         };
+
+    //         DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R3_exact));
+
+    //         auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
+
+    //         auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, 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-12));
+    //       }
+
+    //       SECTION("vector of R3")
+    //       {
+    //         auto p_mesh = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+    //         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)};
+    //              }};
+
+    //         DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
+
+    //         auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
+
+    //         auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, const R3>>();
+
+    //         double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+    //         REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+    //       }
+    //     }
+    //   }
+    // }
+  }
+}