From 8c69f47fd21aa7cb2a8ef589b96b85c65a1c3175 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Mon, 14 Apr 2025 19:33:59 +0200
Subject: [PATCH] WIP tests for polynomial reconstruction of degree 3 [ci-skip]

Seems buggy in 3d for degree 3+
---
 src/scheme/PolynomialReconstruction.cpp       |   16 +
 tests/CMakeLists.txt                          |    1 +
 ...test_PolynomialReconstruction_degree_3.cpp | 1230 +++++++++++++++++
 3 files changed, 1247 insertions(+)
 create mode 100644 tests/test_PolynomialReconstruction_degree_3.cpp

diff --git a/src/scheme/PolynomialReconstruction.cpp b/src/scheme/PolynomialReconstruction.cpp
index 77fed2565..386d741d6 100644
--- a/src/scheme/PolynomialReconstruction.cpp
+++ b/src/scheme/PolynomialReconstruction.cpp
@@ -1384,6 +1384,22 @@ PolynomialReconstruction::_build(
           }
         } else {
           Givens::solveCollectionInPlace(A, X, B);
+#warning REMOVE
+          if (cell_j_id == 0) {
+            for (size_t l = 0; l < B.numberOfRows(); ++l) {
+              for (size_t i = 0; i < B.numberOfColumns(); ++i) {
+                std::clog << "B(" << l << ", " << i << ") =" << B(l, i) << '\n';
+                ;
+              }
+            }
+
+            for (size_t l = 0; l < X.numberOfRows(); ++l) {
+              for (size_t i = 0; i < X.numberOfColumns(); ++i) {
+                std::clog << "X(" << l << ", " << i << ") =" << X(l, i) << '\n';
+                ;
+              }
+            }
+          }
         }
 
         column_begin = 0;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e3739abe7..fd8915b83 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -281,6 +281,7 @@ add_executable (mpi_unit_tests
   test_Partitioner.cpp
   test_PolynomialReconstruction_degree_1.cpp
   test_PolynomialReconstruction_degree_2.cpp
+  test_PolynomialReconstruction_degree_3.cpp
   test_PolynomialReconstructionDescriptor.cpp
   test_RandomEngine.cpp
   test_StencilBuilder_cell2cell.cpp
diff --git a/tests/test_PolynomialReconstruction_degree_3.cpp b/tests/test_PolynomialReconstruction_degree_3.cpp
new file mode 100644
index 000000000..d04ac14b7
--- /dev/null
+++ b/tests/test_PolynomialReconstruction_degree_3.cpp
@@ -0,0 +1,1230 @@
+#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 <mesh/CartesianMeshBuilder.hpp>
+
+#include <DiscreteFunctionDPkForTests.hpp>
+#include <MeshDataBaseForTests.hpp>
+
+#include <NbGhostLayersTester.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("PolynomialReconstruction_degree_3", "[scheme]")
+{
+  constexpr size_t degree = 3;
+
+  constexpr size_t nb_ghost_layers = 3;
+  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) {
+#warning remove
+      descriptor.setPreconditioning(false);
+      descriptor.setRowWeighting(false);
+
+      SECTION(name(descriptor.integrationMethodType()))
+      {
+        SECTION("1D")
+        {
+          using R1 = TinyVector<1>;
+
+          auto p0 = [](const R1& X) {
+            const double x = X[0];
+            return +2.3 + (1.4 + (1.7 - 2.3 * x) * x) * x;
+          };
+          auto p1 = [](const R1& X) {
+            const double x = X[0];
+            return -1.2 - (2.3 - (1.1 + 2.1 * x) * x) * x;
+          };
+          auto p2 = [](const R1& X) {
+            const double x = X[0];
+            return +2.1 + (2.1 + (3.1 - 1.7 * x) * x) * x;
+          };
+          auto p3 = [](const R1& X) {
+            const double x = X[0];
+            return -1.7 + (1.4 + (1.6 - 3.1 * x) * x) * x;
+          };
+          auto p4 = [](const R1& X) {
+            const double x = X[0];
+            return +1.1 - (1.3 - (2.1 + 1.5 * x) * x) * x;
+          };
+          auto p5 = [](const R1& X) {
+            const double x = X[0];
+            return +1.9 - (2.1 + (1.6 - 2.7 * x) * x) * x;
+          };
+          auto p6 = [](const R1& X) {
+            const double x = X[0];
+            return -0.7 + (1.4 + (2.1 + 1.1 * x) * x) * x;
+          };
+          auto p7 = [](const R1& X) {
+            const double x = X[0];
+            return -1.4 - (1.2 + (1.5 - 2.1 * x) * x) * x;
+          };
+          auto p8 = [](const R1& X) {
+            const double x = X[0];
+            return -2.1 + (1.1 - (1.7 + 1.2 * x) * x) * x;
+          };
+          auto p9 = [](const R1& X) {
+            const double x = X[0];
+            return +1.8 - (3.1 + (2.1 - 2.4 * x) * x) * x;
+          };
+
+          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 = p0;
+
+                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-12));
+              }
+            }
+          }
+
+          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{p2(x), p4(x), p1(x)}; };
+
+                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-12));
+              }
+            }
+          }
+
+          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{p1(x), p2(x), p3(x),   //
+                              p4(x), p5(x), p6(x),   //
+                              p7(x), p8(x), p9(x)};
+                };
+
+                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-12));
+              }
+            }
+          }
+
+          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 = {p1, p7, p9};
+
+                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-12));
+              }
+            }
+          }
+
+          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&)>, 3> vector_exact   //
+                  = {[&](const R1& x) -> R3 {
+                       return R3{p1(x), p2(x), p3(x)};
+                     },
+                     [&](const R1& x) -> R3 {
+                       return R3{p5(x), p7(x), p0(x)};
+                     },
+                     [&](const R1& x) -> R3 {
+                       return R3{p9(x), p8(x), p4(x)};
+                     }};
+
+                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-12));
+              }
+            }
+          }
+
+          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 = p0;
+
+                auto R3_exact = [&](const R1& x) -> R3 { return R3{p9(x), p4(x), p7(x)}; };
+
+                auto R3x3_exact = [&](const R1& x) -> R3x3 {
+                  return R3x3{p2(x), p1(x), p0(x),   //
+                              p3(x), p2(x), p4(x),   //
+                              p6(x), p5(x), p9(x)};
+                };
+
+                std::array<std::function<double(const R1&)>, 3> vector_exact = {p1, p8, p7};
+
+                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-12));
+                }
+
+                {
+                  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-12));
+                }
+
+                {
+                  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-12));
+                }
+
+                {
+                  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-12));
+                }
+              }
+            }
+          }
+        }
+
+        SECTION("2D")
+        {
+          using R2 = TinyVector<2>;
+          auto p0  = [](const R2& X) {
+            const double x   = X[0];
+            const double y   = X[1];
+            const double x2  = x * x;
+            const double xy  = x * y;
+            const double y2  = y * y;
+            const double x3  = x2 * x;
+            const double x2y = x2 * y;
+            const double xy2 = x * y2;
+            const double y3  = y * y2;
+
+            return +2.3                               //
+                   + 1.7 * x - 1.3 * y                //
+                   + 1.2 * x2 + 1.3 * xy - 3.2 * y2   //
+                   - 1.3 * x3 + 2.1 * x2y - 1.6 * xy2 + 2.1 * y3;
+          };
+
+          auto p1 = [](const R2& X) {
+            const double x   = X[0];
+            const double y   = X[1];
+            const double x2  = x * x;
+            const double xy  = x * y;
+            const double y2  = y * y;
+            const double x3  = x2 * x;
+            const double x2y = x2 * y;
+            const double xy2 = x * y2;
+            const double y3  = y * y2;
+
+            return +1.4                               //
+                   + 1.6 * x - 2.1 * y                //
+                   + 1.3 * x2 + 2.6 * xy - 1.4 * y2   //
+                   - 1.2 * x3 - 1.7 * x2y + 2.1 * xy2 - 2.2 * y3;
+          };
+
+          auto p2 = [](const R2& X) {
+            const double x   = X[0];
+            const double y   = X[1];
+            const double x2  = x * x;
+            const double xy  = x * y;
+            const double y2  = y * y;
+            const double x3  = x2 * x;
+            const double x2y = x2 * y;
+            const double xy2 = x * y2;
+            const double y3  = y * y2;
+
+            return -1.2                               //
+                   + 2.3 * x - 1.6 * y                //
+                   - 1.2 * x2 + 2.4 * xy - 1.9 * y2   //
+                   + 0.9 * x3 + 1.5 * x2y - 2.3 * xy2 - 1.6 * y3;
+          };
+
+          auto p3 = [](const R2& X) {
+            const double x   = X[0];
+            const double y   = X[1];
+            const double x2  = x * x;
+            const double xy  = x * y;
+            const double y2  = y * y;
+            const double x3  = x2 * x;
+            const double x2y = x2 * y;
+            const double xy2 = x * y2;
+            const double y3  = y * y2;
+
+            return +2.4                               //
+                   + 2.5 * x + 1.4 * y                //
+                   - 2.7 * x2 + 1.9 * xy - 2.2 * y2   //
+                   - 1.3 * x3 + 2.3 * x2y - 1.4 * xy2 + 2.2 * y3;
+          };
+
+          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 = p0;
+
+                DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree + 1, 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{p1(x), p2(x), p3(x)}; };
+
+                DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree + 1, 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{p0(x), p1(x),   //
+                              p2(x), p3(x)};
+                };
+
+                DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree + 1, 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 = {p0, p1, p2, p3};
+
+                DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree + 1, 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>;
+
+          auto p0 = [](const R3& X) -> double {
+            const double x   = X[0];
+            const double y   = X[1];
+            const double z   = X[2];
+            const double xy  = x * y;
+            const double xz  = x * z;
+            const double yz  = y * z;
+            const double x2  = x * x;
+            const double y2  = y * y;
+            const double z2  = z * z;
+            const double xyz = x * y * z;
+            const double x2y = x2 * y;
+            const double x2z = x2 * z;
+            const double xy2 = x * y2;
+            const double y2z = y2 * z;
+            const double xz2 = x * z2;
+            const double yz2 = y * z2;
+            const double x3  = x2 * x;
+            const double y3  = y2 * y;
+            const double z3  = z2 * z;
+
+            return (y - 0.5) * (z - 0.5) * (z - 0.5);
+
+            // return 2.3 + 1.7 * x - 1.3 * y + 2.1 * z                             //
+            //        + 1.7 * x2 + 1.4 * y2 + 1.7 * z2                              //
+            //        - 2.3 * xy + 1.6 * xz - 1.9 * yz                              //
+            //        + 1.2 * x3 - 2.1 * y3 - 1.1 * z3                              //
+            //        - 1.7 * x2y - 1.3 * x2z + 0.9 * xy2 - 0.7 * y2z + 1.5 * xz2   //- 0.7 * yz2
+            //        + 2.8 * xyz                                                   //
+            //   ;
+          };
+
+          // auto p1 = [](const R3& x) {
+          //   return +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];
+          // };
+
+          // auto p2 = [](const R3& x) {
+          //   return +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];
+          // };
+
+          // auto p3 = [](const R3& x) {
+          //   return -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];
+          // };
+
+          SECTION("R data")
+          {
+            for (auto named_mesh : MeshDataBaseForTests::get().all3DMeshes()) {
+              SECTION(named_mesh.name())
+              {
+                auto p_initial_mesh =
+                  CartesianMeshBuilder{TinyVector<3>{0, 0, 0}, TinyVector<3>{4, 4, 4}, TinyVector<3, size_t>{4, 4, 4}}
+                    .mesh()
+                    ->get<Mesh<3>>();   // named_mesh.mesh()->get<Mesh<3>>();
+                auto& initial_mesh = *p_initial_mesh;
+
+                constexpr double theta = 0;
+                TinyMatrix<3> T0{std::cos(theta), -std::sin(theta), 0,
+                                 //
+                                 std::sin(theta), std::cos(theta), 0,
+                                 //
+                                 0, 0, 1};
+                constexpr double phi = 0;
+                TinyMatrix<3> T1{std::cos(phi), 0, -std::sin(phi),
+                                 //
+                                 0, 1, 0,
+                                 //
+                                 std::sin(phi), 0, std::cos(phi)};
+
+                TinyMatrix T = T0 * T1;
+
+                auto xr = initial_mesh.xr();
+
+                NodeValue<R3> new_xr{initial_mesh.connectivity()};
+                parallel_for(
+                  initial_mesh.numberOfNodes(),
+                  PUGS_LAMBDA(const NodeId node_id) { new_xr[node_id] = T * xr[node_id]; });
+
+                std::shared_ptr p_mesh = std::make_shared<const Mesh<3>>(initial_mesh.shared_connectivity(), new_xr);
+                const Mesh<3>& mesh    = *p_mesh;
+                // inverse rotation
+                TinyMatrix<3> inv_T{std::cos(theta), std::sin(theta), 0,
+                                    //
+                                    -std::sin(theta), std::cos(theta), 0,
+                                    //
+                                    0, 0, 1};
+
+                auto R_exact = p0;
+
+                DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree + 3, std::function(R_exact));
+
+                auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
+                auto dpk_fh          = reconstructions[0]->get<DiscreteFunctionDPk<3, const double>>();
+
+                {
+                  // PolynomialReconstructionDescriptor descriptor2{IntegrationMethodType::element, 2};
+                  // DiscreteFunctionP0 f2h = test_only::exact_projection(mesh, 2, std::function(R_exact));
+                  // auto reconstructions2  = PolynomialReconstruction{descriptor2}.build(fh);
+                  // auto dpk_fh2           = reconstructions2[0]->get<DiscreteFunctionDPk<3, const double>>();
+                  // double max_error       = test_only::max_reconstruction_error(mesh, dpk_fh2,
+                  // std::function(R_exact)); REQUIRE(parallel::allReduceMax(max_error) ==
+                  // Catch::Approx(0).margin(1E-12));
+
+                  auto cell_type   = mesh.connectivity().cellType();
+                  auto cell_number = mesh.connectivity().cellNumber();
+
+                  for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
+                    std::clog << cell_id << "(" << cell_number[cell_id] << ")"
+                              << ":---- " << name(cell_type[cell_id]) << "\n2| ";
+                    // {
+                    //   auto coeffs = dpk_fh2.coefficients(cell_id);
+                    //   for (size_t i = 0; i < coeffs.size(); ++i) {
+                    //     if (std::abs(coeffs[i]) > 1e-12) {
+                    //       std::clog << ' ' << i << ':' << coeffs[i];
+                    //     }
+                    //   }
+                    // }
+                    std::clog << '\n';
+                    std::clog << "3| ";
+                    {
+                      auto coeffs = dpk_fh.coefficients(cell_id);
+                      for (size_t i = 0; i < coeffs.size(); ++i) {
+                        if (std::abs(coeffs[i]) > 1e-12) {
+                          std::clog << ' ' << i << ':' << coeffs[i];
+                        }
+                      }
+                    }
+                    std::clog << '\n';
+                    break;
+                  }
+                }
+
+                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{p1(x), p2(x), p3(x)}; };
+
+          //       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{p0(x), p1(x),   //
+          //                     p2(x), p3(x)};
+          //       };
+
+          //       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;
+
+          //       std::array<std::function<double(const R3&)>, 4> vector_exact = {p0, p1, p2, p3};
+
+          //       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>;
+
+  //     auto p0 = [](const R1& x) { return +1.7 * (x[0] + 1) * (x[0] + 1) - 1.1; };
+  //     auto p1 = [](const R1& x) { return -1.2 * (x[0] + 1) * (x[0] + 1) + 1.3; };
+  //     auto p2 = [](const R1& x) { return +1.4 * (x[0] + 1) * (x[0] + 1) - 0.6; };
+
+  //     for (auto descriptor : descriptor_list) {
+  //       SECTION(name(descriptor.integrationMethodType()))
+  //       {
+  //         SECTION("R data")
+  //         {
+  //           auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+  //           auto& mesh = *p_mesh;
+
+  //           auto R_exact = p0;
+
+  //           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-12));
+  //         }
+
+  //         SECTION("R1x1 data")
+  //         {
+  //           using R1x1 = TinyMatrix<1>;
+
+  //           auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+  //           auto& mesh = *p_mesh;
+
+  //           auto R1x1_exact = [&](const R1& x) { return R1x1{p0(x)}; };
+
+  //           DiscreteFunctionP0 fh = test_only::exact_projection(mesh, degree, std::function(R1x1_exact));
+
+  //           auto reconstructions = PolynomialReconstruction{descriptor}.build(fh);
+
+  //           auto dpk_fh = reconstructions[0]->get<DiscreteFunctionDPk<1, const R1x1>>();
+
+  //           double max_error = test_only::max_reconstruction_error(mesh, dpk_fh, std::function(R1x1_exact));
+  //           REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //         }
+
+  //         SECTION("R vector data")
+  //         {
+  //           auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+  //           auto& mesh  = *p_mesh;
+
+  //           std::array<std::function<double(const R1&)>, 3> vector_exact   //
+  //             = {p0, p1, p2};
+
+  //           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-12));
+  //         }
+
+  //         SECTION("R1x1 vector data")
+  //         {
+  //           using R1x1 = TinyMatrix<1>;
+
+  //           auto p_mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+  //           auto& mesh  = *p_mesh;
+
+  //           std::array<std::function<R1x1(const R1&)>, 3> vector_exact   //
+  //             = {[&](const R1& x) { return R1x1{p2(x)}; },               //
+  //                [&](const R1& x) { return R1x1{p0(x)}; },               //
+  //                [&](const R1& x) { return R1x1{p1(x)}; }};
+
+  //           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 R1x1>>();
+
+  //           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>;
+
+  //     auto p_initial_mesh = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+  //     auto& initial_mesh  = *p_initial_mesh;
+
+  //     constexpr double theta = 1;
+  //     TinyMatrix<2> T{std::cos(theta), -std::sin(theta),   //
+  //                     std::sin(theta), std::cos(theta)};
+
+  //     auto xr = initial_mesh.xr();
+
+  //     NodeValue<R2> new_xr{initial_mesh.connectivity()};
+  //     parallel_for(
+  //       initial_mesh.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { new_xr[node_id] = T * xr[node_id]; });
+
+  //     std::shared_ptr p_mesh = std::make_shared<const Mesh<2>>(initial_mesh.shared_connectivity(), new_xr);
+  //     const Mesh<2>& mesh    = *p_mesh;
+  //     // inverse rotation
+  //     TinyMatrix<2> inv_T{std::cos(theta), std::sin(theta),   //
+  //                         -std::sin(theta), std::cos(theta)};
+
+  //     auto p0 = [&inv_T](const R2& X) {
+  //       const R2 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       return +1.7 * x * x + 2 * y * y - 1.1;
+  //     };
+
+  //     auto p1 = [&inv_T](const R2& X) {
+  //       const R2 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       return -1.3 * x * x - 0.2 * y * y + 0.7;
+  //     };
+
+  //     auto p2 = [&inv_T](const R2& X) {
+  //       const R2 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       return +2.6 * x * x - 1.4 * y * y - 1.9;
+  //     };
+
+  //     auto p3 = [&inv_T](const R2& X) {
+  //       const R2 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       return -0.6 * x * x + 1.4 * y * y + 2.3;
+  //     };
+
+  //     auto q0 = [&inv_T](const R2& X) {
+  //       const R2 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       return 3 * x * y;
+  //     };
+
+  //     auto q1 = [&inv_T](const R2& X) {
+  //       const R2 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       return -1.3 * x * y;
+  //     };
+
+  //     for (auto descriptor : descriptor_list) {
+  //       SECTION(name(descriptor.integrationMethodType()))
+  //       {
+  //         SECTION("R data")
+  //         {
+  //           auto R_exact = p0;
+
+  //           DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R_exact));
+
+  //           auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
+
+  //           auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const double>>();
+
+  //           double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R_exact));
+  //           REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //         }
+
+  //         SECTION("R2x2 data")
+  //         {
+  //           using R2x2      = TinyMatrix<2>;
+  //           auto R2x2_exact = [&](const R2& X) -> R2x2 {
+  //             return T * TinyMatrix<2>{p0(X), q0(X), q1(X), p1(X)} * inv_T;
+  //           };
+
+  //           DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R2x2_exact));
+
+  //           auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
+
+  //           auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const R2x2>>();
+
+  //           double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R2x2_exact));
+  //           REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //         }
+
+  //         SECTION("vector of R")
+  //         {
+  //           std::array<std::function<double(const R2&)>, 4> vector_exact   //
+  //             = {p0, p1, p2, p3};
+
+  //           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("vector of R2x2")
+  //         {
+  //           using R2x2 = TinyMatrix<2>;
+
+  //           std::array<std::function<R2x2(const R2&)>, 2> vector_R2x2_exact   //
+  //             = {[&](const R2& X) {
+  //                  return T * R2x2{p0(X), q0(X), q1(X), p1(X)} * inv_T;
+  //                },
+  //                [&](const R2& X) {
+  //                  return T * R2x2{p0(X), q1(X), 0, p1(X)} * inv_T;
+  //                }};
+
+  //           DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_R2x2_exact);
+
+  //           auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
+
+  //           auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<2, const R2x2>>();
+
+  //           double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_R2x2_exact);
+  //           REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //         }
+
+  //         SECTION("list")
+  //         {
+  //           using R2x2 = TinyMatrix<2>;
+
+  //           auto R_exact = p0;
+
+  //           DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R_exact));
+
+  //           std::array<std::function<double(const R2&)>, 4> vector_exact   //
+  //             = {p0, p1, p2, p3};
+
+  //           DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
+
+  //           std::array<std::function<R2x2(const R2&)>, 2> vector_R2x2_exact   //
+  //             = {[&](const R2& X) {
+  //                  return T * R2x2{p0(X), q0(X), q1(X), p1(X)} * inv_T;
+  //                },
+  //                [&](const R2& X) {
+  //                  return T * R2x2{p2(X), q1(X), 0, p3(X)} * inv_T;
+  //                }};
+
+  //           DiscreteFunctionP0Vector Wh = test_only::exact_projection(mesh, degree, vector_R2x2_exact);
+
+  //           auto R2x2_exact = [&](const R2& X) -> R2x2 {
+  //             return T * TinyMatrix<2>{p0(X), q1(X), q0(X), p3(X)} * inv_T;
+  //           };
+
+  //           DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R2x2_exact));
+
+  //           auto reconstructions = PolynomialReconstruction{descriptor}.build(uh, Vh, Wh, Ah);
+
+  //           auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<2, const double>>();
+  //           auto dpk_Vh = reconstructions[1]->get<DiscreteFunctionDPkVector<2, const double>>();
+  //           auto dpk_Wh = reconstructions[2]->get<DiscreteFunctionDPkVector<2, const R2x2>>();
+  //           auto dpk_Ah = reconstructions[3]->get<DiscreteFunctionDPk<2, const R2x2>>();
+
+  //           {
+  //             double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R_exact));
+  //             REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //           }
+  //           {
+  //             double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+  //             REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //           }
+  //           {
+  //             double max_error = test_only::max_reconstruction_error(mesh, dpk_Wh, vector_R2x2_exact);
+  //             REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //           }
+  //           {
+  //             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("3D")
+  //   {
+  //     using R3 = TinyVector<3>;
+
+  //     auto p_initial_mesh = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+  //     auto& initial_mesh  = *p_initial_mesh;
+
+  //     constexpr double theta = 1;
+  //     TinyMatrix<3> T{std::cos(theta), -std::sin(theta), 0,
+  //                     //
+  //                     std::sin(theta), std::cos(theta), 0,
+  //                     //
+  //                     0, 0, 1};
+
+  //     auto xr = initial_mesh.xr();
+
+  //     NodeValue<R3> new_xr{initial_mesh.connectivity()};
+  //     parallel_for(
+  //       initial_mesh.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { new_xr[node_id] = T * xr[node_id]; });
+
+  //     std::shared_ptr p_mesh = std::make_shared<const Mesh<3>>(initial_mesh.shared_connectivity(), new_xr);
+  //     const Mesh<3>& mesh    = *p_mesh;
+  //     // inverse rotation
+  //     TinyMatrix<3> inv_T{std::cos(theta), std::sin(theta), 0,
+  //                         //
+  //                         -std::sin(theta), std::cos(theta), 0,
+  //                         //
+  //                         0, 0, 1};
+
+  //     auto p0 = [&inv_T](const R3& X) {
+  //       const R3 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       const double z = Y[2] - 1;
+  //       return +1.7 * x * x + 2 * y * y + 1.3 * z * z - 1.1;
+  //     };
+
+  //     auto p1 = [&inv_T](const R3& X) {
+  //       const R3 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       const double z = Y[2] - 1;
+  //       return +2.1 * x * x - 1.4 * y * y - 3.1 * z * z + 2.2;
+  //     };
+
+  //     auto p2 = [&inv_T](const R3& X) {
+  //       const R3 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       const double z = Y[2] - 1;
+  //       return -1.1 * x * x - 1.2 * y * y + 1.3 * z * z - 1.7;
+  //     };
+
+  //     auto p3 = [&inv_T](const R3& X) {
+  //       const R3 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       const double z = Y[2] - 1;
+  //       return 1.9 * x * x + 2.1 * y * y - 3.1 * z * z + 1.6;
+  //     };
+
+  //     auto p4 = [&inv_T](const R3& X) {
+  //       const R3 Y     = inv_T * X;
+  //       const double x = Y[0] - 2;
+  //       const double y = Y[1] - 1;
+  //       const double z = Y[2] - 1;
+  //       return -2.4 * x * x + 3.3 * y * y - 1.7 * z * z + 2.1;
+  //     };
+
+  //     SECTION("3 symmetries")
+  //     {
+  //       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")}}};
+
+  //       for (auto descriptor : descriptor_list) {
+  //         SECTION("R data")
+  //         {
+  //           auto R_exact = p0;
+
+  //           DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R_exact));
+
+  //           auto reconstructions = PolynomialReconstruction{descriptor}.build(uh);
+
+  //           auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const double>>();
+
+  //           double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R_exact));
+  //           REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //         }
+
+  //         SECTION("vector of R")
+  //         {
+  //           std::array<std::function<double(const R3&)>, 4> vector_exact   //
+  //             = {p0, p1, p2, p3};
+
+  //           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 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("1 symmetry")
+  //     {
+  //       // Matrix and their transformations are kept simple to
+  //       // derive exact solutions
+  //       std::vector<PolynomialReconstructionDescriptor> descriptor_list =
+  //         {PolynomialReconstructionDescriptor{IntegrationMethodType::element, degree,
+  //                                             std::vector<std::shared_ptr<const IBoundaryDescriptor>>{
+  //                                               std::make_shared<NamedBoundaryDescriptor>("XMAX")}},
+  //          PolynomialReconstructionDescriptor{IntegrationMethodType::boundary, degree,
+  //                                             std::vector<std::shared_ptr<const IBoundaryDescriptor>>{
+  //                                               std::make_shared<NamedBoundaryDescriptor>("XMAX")}}};
+  //       for (auto descriptor : descriptor_list) {
+  //         SECTION(name(descriptor.integrationMethodType()))
+  //         {
+  //           SECTION("R3x3 data")
+  //           {
+  //             // Matrix and their transformations are kept simple to
+  //             // derive exact solutions
+
+  //             using R3x3      = TinyMatrix<3>;
+  //             auto R2x2_exact = [&](const R3& X) -> R3x3 {
+  //               return T * TinyMatrix<3>{p0(X), 0,     0,       //
+  //                                        0,     p1(X), p2(X),   //
+  //                                        0,     p3(X), p4(X)} *
+  //                      inv_T;
+  //             };
+
+  //             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<3, const R3x3>>();
+
+  //             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 of R3x3")
+  //           {
+  //             using R3x3 = TinyMatrix<3>;
+
+  //             std::array<std::function<R3x3(const R3&)>, 2> vector_R3x3_exact   //
+  //               = {[&](const R3& X) {
+  //                    return T * R3x3{p0(X), 0,     0,       //
+  //                                    0,     p1(X), p2(X),   //
+  //                                    0,     p3(X), p4(X)} *
+  //                           inv_T;
+  //                  },
+  //                  [&](const R3& X) {
+  //                    return T * R3x3{p0(X), 0,     0,       //
+  //                                    0,     p2(X), p4(X),   //
+  //                                    0,     p3(X), p1(X)} *
+  //                           inv_T;
+  //                  }};
+
+  //             DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_R3x3_exact);
+
+  //             auto reconstructions = PolynomialReconstruction{descriptor}.build(Vh);
+
+  //             auto dpk_Vh = reconstructions[0]->get<DiscreteFunctionDPkVector<3, const R3x3>>();
+
+  //             double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_R3x3_exact);
+  //             REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //           }
+
+  //           SECTION("list")
+  //           {
+  //             using R3x3 = TinyMatrix<3>;
+
+  //             auto R_exact = p0;
+
+  //             DiscreteFunctionP0 uh = test_only::exact_projection(mesh, degree, std::function(R_exact));
+
+  //             std::array<std::function<double(const R3&)>, 4> vector_exact   //
+  //               = {p0, p1, p2, p3};
+
+  //             DiscreteFunctionP0Vector Vh = test_only::exact_projection(mesh, degree, vector_exact);
+
+  //             std::array<std::function<R3x3(const R3&)>, 2> vector_R3x3_exact   //
+  //               = {[&](const R3& X) {
+  //                    return T * R3x3{p1(X), 0,     0,       //
+  //                                    0,     p3(X), p0(X),   //
+  //                                    0,     p2(X), p4(X)} *
+  //                           inv_T;
+  //                  },
+  //                  [&](const R3& X) {
+  //                    return T * R3x3{p2(X), 0,     0,       //
+  //                                    0,     p0(X), p1(X),   //
+  //                                    0,     p3(X), p4(X)} *
+  //                           inv_T;
+  //                  }};
+
+  //             DiscreteFunctionP0Vector Wh = test_only::exact_projection(mesh, degree, vector_R3x3_exact);
+
+  //             auto R3x3_exact = [&](const R3& X) -> R3x3 {
+  //               return T * R3x3{p0(X), 0,     0,       //
+  //                               0,     p1(X), p2(X),   //
+  //                               0,     p3(X), p4(X)} *
+  //                      inv_T;
+  //             };
+
+  //             DiscreteFunctionP0 Ah = test_only::exact_projection(mesh, degree, std::function(R3x3_exact));
+
+  //             auto reconstructions = PolynomialReconstruction{descriptor}.build(uh, Vh, Wh, Ah);
+
+  //             auto dpk_uh = reconstructions[0]->get<DiscreteFunctionDPk<3, const double>>();
+  //             auto dpk_Vh = reconstructions[1]->get<DiscreteFunctionDPkVector<3, const double>>();
+  //             auto dpk_Wh = reconstructions[2]->get<DiscreteFunctionDPkVector<3, const R3x3>>();
+  //             auto dpk_Ah = reconstructions[3]->get<DiscreteFunctionDPk<3, const R3x3>>();
+
+  //             {
+  //               double max_error = test_only::max_reconstruction_error(mesh, dpk_uh, std::function(R_exact));
+  //               REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //             }
+  //             {
+  //               double max_error = test_only::max_reconstruction_error(mesh, dpk_Vh, vector_exact);
+  //               REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //             }
+  //             {
+  //               double max_error = test_only::max_reconstruction_error(mesh, dpk_Wh, vector_R3x3_exact);
+  //               REQUIRE(parallel::allReduceMax(max_error) == Catch::Approx(0).margin(1E-12));
+  //             }
+  //             {
+  //               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-12));
+  //             }
+  //           }
+  //         }
+  //       }
+  //     }
+  //   }
+  // }
+}
-- 
GitLab