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/test_PolynomialReconstruction_degree_2.cpp b/tests/test_PolynomialReconstruction_degree_2.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..350a57426f4c70629e39edd3c2edb3754e350cec
--- /dev/null
+++ b/tests/test_PolynomialReconstruction_degree_2.cpp
@@ -0,0 +1,713 @@
+#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>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("PolynomialReconstruction_degree_2", "[scheme]")
+{
+  constexpr size_t degree = 2;
+
+  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));
+    //       }
+    //     }
+    //   }
+    // }
+  }
+}