diff --git a/CMakeLists.txt b/CMakeLists.txt
index e9255bff6ac523e8bfdf61fe42044e4366d3993f..4b302fe373681cdc862851e871c53d3842d0d842 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -573,6 +573,7 @@ target_link_libraries(
   pugs
   PugsMesh
   PugsAlgebra
+  PugsAnalysis
   PugsUtils
   PugsLanguage
   PugsLanguageAST
@@ -606,7 +607,7 @@ if(${CMAKE_VERSION} VERSION_LESS "3.13.0")
     LIBRARY DESTINATION lib
     ARCHIVE DESTINATION lib)
 else()
-  install(TARGETS pugs  PugsAlgebra PugsUtils PugsLanguage PugsLanguageAST PugsLanguageModules PugsLanguageAlgorithms  PugsLanguageUtils PugsMesh PugsScheme PugsOutput
+  install(TARGETS pugs PugsAlgebra PugsAnalysis PugsUtils PugsLanguage PugsLanguageAST PugsLanguageModules PugsLanguageAlgorithms  PugsLanguageUtils PugsMesh PugsScheme PugsOutput
 
     RUNTIME DESTINATION bin
     LIBRARY DESTINATION lib
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 16904a7be0cddc9aa54206638ab4b5c759cd0de8..54cae625bc7c899c433bfdf4021ef6242dd5aaf4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -12,6 +12,9 @@ add_subdirectory(language)
 # Pugs algebra
 add_subdirectory(algebra)
 
+# Pugs analysis
+add_subdirectory(analysis)
+
 # Pugs mesh
 add_subdirectory(mesh)
 
diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3cbded76d7fadead5565ba862289aec3dcdbaad8
--- /dev/null
+++ b/src/analysis/CMakeLists.txt
@@ -0,0 +1,13 @@
+# ------------------- Source files --------------------
+
+add_library(
+  PugsAnalysis
+  CubeGaussQuadrature.cpp
+  QuadratureManager.cpp
+  PrismGaussQuadrature.cpp
+  PyramidGaussQuadrature.cpp
+  SquareGaussQuadrature.cpp
+  TensorialGaussLegendreQuadrature.cpp
+  TensorialGaussLobattoQuadrature.cpp
+  TetrahedronGaussQuadrature.cpp
+  TriangleGaussQuadrature.cpp)
diff --git a/src/analysis/CubeGaussQuadrature.cpp b/src/analysis/CubeGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1d35bd875ee46c0faf5d492328c34d9ec7e6466c
--- /dev/null
+++ b/src/analysis/CubeGaussQuadrature.cpp
@@ -0,0 +1,501 @@
+#include <analysis/CubeGaussQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+void
+CubeGaussQuadrature::_buildPointAndWeightLists(const size_t degree)
+{
+  using R3 = TinyVector<3>;
+
+  struct Descriptor
+  {
+    int id;
+    double weight;
+    std::vector<double> lambda_list;
+  };
+
+  auto fill_quadrature_points = [](auto descriptor_list, auto& point_list, auto& weight_list) {
+    Assert(point_list.size() == weight_list.size());
+
+    size_t k = 0;
+    for (size_t i = 0; i < descriptor_list.size(); ++i) {
+      const auto [id, unit_weight, position_list] = descriptor_list[i];
+
+      const double w = 8. * unit_weight;
+
+      switch (id) {
+      case 1: {
+        Assert(position_list.size() == 0);
+        point_list[k]  = {0, 0, 0};
+        weight_list[k] = w;
+        ++k;
+        break;
+      }
+      case 2: {
+        Assert(position_list.size() == 1);
+        const double a = position_list[0];
+
+        point_list[k + 0] = {+a, 0, 0};
+        point_list[k + 1] = {-a, 0, 0};
+        point_list[k + 2] = {0, +a, 0};
+        point_list[k + 3] = {0, -a, 0};
+        point_list[k + 4] = {0, 0, +a};
+        point_list[k + 5] = {0, 0, -a};
+
+        for (size_t l = 0; l < 6; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 6;
+        break;
+      }
+      case 3: {
+        Assert(position_list.size() == 1);
+        const double a = position_list[0];
+
+        point_list[k + 0] = {+a, +a, +a};
+        point_list[k + 1] = {+a, +a, -a};
+        point_list[k + 2] = {+a, -a, +a};
+        point_list[k + 3] = {+a, -a, -a};
+        point_list[k + 4] = {-a, +a, +a};
+        point_list[k + 5] = {-a, +a, -a};
+        point_list[k + 6] = {-a, -a, +a};
+        point_list[k + 7] = {-a, -a, -a};
+
+        for (size_t l = 0; l < 8; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 8;
+        break;
+      }
+      case 4: {
+        Assert(position_list.size() == 1);
+        const double a = position_list[0];
+
+        point_list[k + 0]  = {+a, +a, 0};
+        point_list[k + 1]  = {+a, -a, 0};
+        point_list[k + 2]  = {-a, +a, 0};
+        point_list[k + 3]  = {-a, -a, 0};
+        point_list[k + 4]  = {+a, 0, +a};
+        point_list[k + 5]  = {+a, 0, -a};
+        point_list[k + 6]  = {-a, 0, +a};
+        point_list[k + 7]  = {-a, 0, -a};
+        point_list[k + 8]  = {0, +a, +a};
+        point_list[k + 9]  = {0, +a, -a};
+        point_list[k + 10] = {0, -a, +a};
+        point_list[k + 11] = {0, -a, -a};
+
+        for (size_t l = 0; l < 12; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 12;
+        break;
+      }
+      case 5: {
+        Assert(position_list.size() == 2);
+        const double a = position_list[0];
+        const double b = position_list[1];
+
+        point_list[k + 0]  = {+a, +b, 0};
+        point_list[k + 1]  = {+a, -b, 0};
+        point_list[k + 2]  = {-a, +b, 0};
+        point_list[k + 3]  = {-a, -b, 0};
+        point_list[k + 4]  = {+b, +a, 0};
+        point_list[k + 5]  = {-b, +a, 0};
+        point_list[k + 6]  = {+b, -a, 0};
+        point_list[k + 7]  = {-b, -a, 0};
+        point_list[k + 8]  = {+a, 0, +b};
+        point_list[k + 9]  = {+a, 0, -b};
+        point_list[k + 10] = {-a, 0, +b};
+        point_list[k + 11] = {-a, 0, -b};
+        point_list[k + 12] = {+b, 0, +a};
+        point_list[k + 13] = {-b, 0, +a};
+        point_list[k + 14] = {+b, 0, -a};
+        point_list[k + 15] = {-b, 0, -a};
+        point_list[k + 16] = {0, +a, +b};
+        point_list[k + 17] = {0, +a, -b};
+        point_list[k + 18] = {0, -a, +b};
+        point_list[k + 19] = {0, -a, -b};
+        point_list[k + 20] = {0, +b, +a};
+        point_list[k + 21] = {0, -b, +a};
+        point_list[k + 22] = {0, +b, -a};
+        point_list[k + 23] = {0, -b, -a};
+
+        for (size_t l = 0; l < 24; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 24;
+        break;
+      }
+      case 6: {
+        Assert(position_list.size() == 2);
+        const double a = position_list[0];
+        const double b = position_list[1];
+
+        point_list[k + 0]  = {+a, +a, +b};
+        point_list[k + 1]  = {+a, +a, -b};
+        point_list[k + 2]  = {+a, -a, +b};
+        point_list[k + 3]  = {+a, -a, -b};
+        point_list[k + 4]  = {-a, +a, +b};
+        point_list[k + 5]  = {-a, +a, -b};
+        point_list[k + 6]  = {-a, -a, +b};
+        point_list[k + 7]  = {-a, -a, -b};
+        point_list[k + 8]  = {+a, +b, +a};
+        point_list[k + 9]  = {+a, -b, +a};
+        point_list[k + 10] = {+a, +b, -a};
+        point_list[k + 11] = {+a, -b, -a};
+        point_list[k + 12] = {-a, +b, +a};
+        point_list[k + 13] = {-a, -b, +a};
+        point_list[k + 14] = {-a, +b, -a};
+        point_list[k + 15] = {-a, -b, -a};
+        point_list[k + 16] = {+b, +a, +a};
+        point_list[k + 17] = {-b, +a, +a};
+        point_list[k + 18] = {+b, +a, -a};
+        point_list[k + 19] = {-b, +a, -a};
+        point_list[k + 20] = {+b, -a, +a};
+        point_list[k + 21] = {-b, -a, +a};
+        point_list[k + 22] = {+b, -a, -a};
+        point_list[k + 23] = {-b, -a, -a};
+
+        for (size_t l = 0; l < 24; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 24;
+        break;
+      }
+      case 7: {
+        Assert(position_list.size() == 3);
+        const double a = position_list[0];
+        const double b = position_list[1];
+        const double c = position_list[2];
+
+        point_list[k + 0]  = {+a, +b, +c};
+        point_list[k + 1]  = {+a, +b, -c};
+        point_list[k + 2]  = {+a, -b, +c};
+        point_list[k + 3]  = {+a, -b, -c};
+        point_list[k + 4]  = {-a, +b, +c};
+        point_list[k + 5]  = {-a, +b, -c};
+        point_list[k + 6]  = {-a, -b, +c};
+        point_list[k + 7]  = {-a, -b, -c};
+        point_list[k + 8]  = {+a, +c, +b};
+        point_list[k + 9]  = {+a, -c, +b};
+        point_list[k + 10] = {+a, +c, -b};
+        point_list[k + 11] = {+a, -c, -b};
+        point_list[k + 12] = {-a, +c, +b};
+        point_list[k + 13] = {-a, -c, +b};
+        point_list[k + 14] = {-a, +c, -b};
+        point_list[k + 15] = {-a, -c, -b};
+        point_list[k + 16] = {+b, +a, +c};
+        point_list[k + 17] = {+b, +a, -c};
+        point_list[k + 18] = {-b, +a, +c};
+        point_list[k + 19] = {-b, +a, -c};
+        point_list[k + 20] = {+b, -a, +c};
+        point_list[k + 21] = {+b, -a, -c};
+        point_list[k + 22] = {-b, -a, +c};
+        point_list[k + 23] = {-b, -a, -c};
+        point_list[k + 24] = {+b, +c, +a};
+        point_list[k + 25] = {+b, -c, +a};
+        point_list[k + 26] = {-b, +c, +a};
+        point_list[k + 27] = {-b, -c, +a};
+        point_list[k + 28] = {+b, +c, -a};
+        point_list[k + 29] = {+b, -c, -a};
+        point_list[k + 30] = {-b, +c, -a};
+        point_list[k + 31] = {-b, -c, -a};
+        point_list[k + 32] = {+c, +a, +b};
+        point_list[k + 33] = {-c, +a, +b};
+        point_list[k + 34] = {+c, +a, -b};
+        point_list[k + 35] = {-c, +a, -b};
+        point_list[k + 36] = {+c, -a, +b};
+        point_list[k + 37] = {-c, -a, +b};
+        point_list[k + 38] = {+c, -a, -b};
+        point_list[k + 39] = {-c, -a, -b};
+        point_list[k + 40] = {+c, +b, +a};
+        point_list[k + 41] = {-c, +b, +a};
+        point_list[k + 42] = {+c, -b, +a};
+        point_list[k + 43] = {-c, -b, +a};
+        point_list[k + 44] = {+c, +b, -a};
+        point_list[k + 45] = {-c, +b, -a};
+        point_list[k + 46] = {+c, -b, -a};
+        point_list[k + 47] = {-c, -b, -a};
+
+        for (size_t l = 0; l < 48; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 48;
+        break;
+      }
+        // LCOV_EXCL_START
+      default: {
+        throw UnexpectedError("invalid quadrature id");
+      }
+        // LCOV_EXCL_STOP
+      }
+    }
+  };
+
+  switch (degree) {
+  case 0:
+  case 1: {
+    constexpr size_t nb_points = 1;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.000000000000000e+00, {}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 2:
+  case 3: {
+    constexpr size_t nb_points = 6;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.666666666666667e-01, {+1.000000000000000e+00}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 4:
+  case 5: {
+    constexpr size_t nb_points = 14;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.108033240997230e-01, {-7.958224257542215e-01}},
+       Descriptor{3, 4.189750692520776e-02, {+7.587869106393281e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 6:
+  case 7: {
+    constexpr size_t nb_points = 34;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 3.400458474980031e-02, {-9.388053721060176e-01}},
+       Descriptor{3, 2.529672440135186e-02, {+7.463336100128160e-01}},
+       Descriptor{3, 5.417063533485642e-02, {+4.101308983320144e-01}},
+       Descriptor{4, 1.335280113429432e-02, {+9.064606901228371e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 8:
+  case 9: {
+    constexpr size_t nb_points = 58;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 5.415937446870682e-02, {-6.136814695917090e-01}},
+       Descriptor{3, 6.268599412418628e-03, {+8.700997846619759e-01}},
+       Descriptor{3, 2.485747976800294e-02, {+5.641108070200300e-01}},
+       Descriptor{4, 1.147372576702221e-02, {+8.776871232576783e-01}},
+       Descriptor{6, 1.201460043917167e-02, {+4.322679026308622e-01, +9.385304218646717e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 10:
+  case 11: {
+    constexpr size_t nb_points = 90;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.961692452402056e-02, {+7.221330388744185e-01}},
+       Descriptor{3, 7.751183787523073e-03, {+8.094882019630989e-01}},
+       Descriptor{3, 2.222100810905048e-02, {-5.336540088804971e-01}},
+       Descriptor{3, 1.698870214455200e-02, {+2.807725866512744e-01}},
+       Descriptor{4, 1.602728177693560e-02, {+8.039334672152845e-01}},
+       Descriptor{6, 1.618821190032323e-03, {+9.800994910090715e-01, -5.307838311938264e-01}},
+       Descriptor{6, 8.976342110119554e-03, {+4.056859801950964e-01, +9.545832189295661e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 12:
+  case 13: {
+    constexpr size_t nb_points = 154;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.739128248747042e-02, {-6.344099893707986e-01}},
+       Descriptor{3, 1.838657780061070e-03, {+9.078540479628353e-01}},
+       Descriptor{3, 1.129102483142921e-02, {-2.362052584516462e-01}},
+       Descriptor{4, 5.975759635289482e-03, {+7.375746343132366e-01}},
+       Descriptor{5, 8.691231524417118e-03, {+4.607161476964481e-01, +9.333452030108366e-01}},
+       Descriptor{6, 1.061455871034256e-02, {+3.733841515799997e-01, +6.642163895931538e-01}},
+       Descriptor{6, 1.690358916666084e-03, {+9.495110814642814e-01, +2.576763878569212e-01}},
+       Descriptor{6, 2.284240552839863e-03, {+6.656694827601780e-01, +9.948700428018972e-01}},
+       Descriptor{6, 6.674015652391932e-03, {-8.051009105032320e-01, -4.521405059548300e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 14:
+  case 15: {
+    constexpr size_t nb_points = 256;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 3.340305010694517e-03, {+9.239155149504112e-01}},
+       Descriptor{2, 5.018552337877279e-03, {-2.173948613666498e-01}},
+       Descriptor{3, 9.082758817224784e-03, {+3.384731869494254e-01}},
+       Descriptor{3, 2.936408738387597e-03, {+7.930891310393379e-01}},
+       Descriptor{4, 9.318238647048784e-03, {+6.965519027266717e-01}},
+       Descriptor{5, 6.990786235751096e-03, {+2.642667592628472e-01, +5.849899486873243e-01}},
+       Descriptor{5, 2.841005747490632e-03, {+5.529505913370675e-01, +9.828048727801773e-01}},
+       Descriptor{6, 5.309574072330840e-03, {+2.720325890860906e-01, +8.780894076733160e-01}},
+       Descriptor{6, 3.570894536567292e-03, {+6.186478600855522e-01, +9.399813353075436e-01}},
+       Descriptor{6, 3.505872179585672e-03, {+8.631544625530380e-01, +3.042225951752605e-01}},
+       Descriptor{6, 6.665199051138311e-03, {+4.616057726468051e-01, +7.371361183496182e-01}},
+       Descriptor{6, 7.781518386120085e-04, {+9.603141805416257e-01, +7.670146080341008e-01}},
+       Descriptor{7, 6.249800796596737e-04, {+2.802518397917547e-01, +8.885938329579797e-01, +9.942601945848643e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 16:
+  case 17: {
+    constexpr size_t nb_points = 346;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 9.614592128929721e-03, {+4.853150302302675e-01}},
+       Descriptor{3, 4.393827602159037e-03, {+7.406788158740985e-01}},
+       Descriptor{3, 4.964725194523337e-05, {+9.999995835822517e-01}},
+       Descriptor{4, 2.506284464832531e-03, {+7.546268052131762e-01}},
+       Descriptor{4, 6.133232482070283e-03, {+5.496818141686911e-01}},
+       Descriptor{4, 3.052513689971173e-04, {+9.996685037422811e-01}},
+       Descriptor{5, 2.825437733033075e-03, {+6.157290991684734e-01, +9.530419407049950e-01}},
+       Descriptor{5, 4.817704598799924e-03, {+2.756812731993550e-01, +8.171240681070916e-01}},
+       Descriptor{6, 2.283142338953617e-03, {+2.553429540372821e-01, +9.697740446619386e-01}},
+       Descriptor{6, 1.177437487278679e-03, {+9.352363848980443e-01, +7.467233074183380e-01}},
+       Descriptor{6, 6.184060750627916e-03, {+3.430591461388653e-01, +6.186639578085585e-01}},
+       Descriptor{6, 2.674655837593652e-03, {+2.555841601150721e-01, -1.540702494043778e-01}},
+       Descriptor{6, 3.419177677578799e-03, {+5.416211416468879e-01, +8.956569517854811e-01}},
+       Descriptor{6, 2.334617556615003e-03, {+8.874561091440486e-01, +2.414649778934788e-01}},
+       Descriptor{7, 1.052790164887146e-03, {+8.014733742030056e-01, +9.887847181449660e-01, +4.683593080344387e-01}},
+       Descriptor{7, 2.743830940763945e-03, {+6.099140660050306e-01, +7.868230999431114e-01, +3.205108203811766e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 18:
+  case 19: {
+    constexpr size_t nb_points = 454;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.057371005731085e-03, {+2.357950388975374e-01}},
+       Descriptor{3, 9.660877915728369e-04, {+8.884950400440639e-01}},
+       Descriptor{3, 1.635901446799172e-03, {+6.691818469930052e-01}},
+       Descriptor{5, 8.499912813105116e-04, {+7.123100397018546e-01, +9.982977792850621e-01}},
+       Descriptor{5, 3.595003721736422e-03, {+3.997141162742279e-01, +1.720241294475643e-01}},
+       Descriptor{5, 3.836063429622649e-03, {+8.765805264721680e-01, -4.319090088935915e-01}},
+       Descriptor{6, 5.044128485798918e-03, {-3.101731974045544e-01, +5.411043641351523e-01}},
+       Descriptor{6, 1.951830532879285e-04, {+9.841852549757261e-01, +8.206649948892372e-01}},
+       Descriptor{6, 1.758454840661237e-03, {+8.443872335315854e-01, -1.473766326322086e-01}},
+       Descriptor{6, 3.887320262932200e-03, {+4.875993998852213e-01, +7.674914745033637e-01}},
+       Descriptor{6, 1.887401083675721e-03, {+7.641318497967444e-01, +3.863910365012072e-01}},
+       Descriptor{6, 1.699165183521457e-03, {+7.112537388224306e-01, +9.032012949929131e-01}},
+       Descriptor{6, 7.588378288377641e-04, {+9.551489495939087e-01, +2.226160644570115e-01}},
+       Descriptor{6, 1.455762954103218e-03, {+1.696919803869971e-01, +9.651628675592381e-01}},
+       Descriptor{6, 4.007905075757146e-03, {+6.085335258834057e-01, +9.412726803541549e-02}},
+       Descriptor{6, 1.399186075584291e-03, {+4.265517787687217e-01, +9.784781131096041e-01}},
+       Descriptor{6, 4.099280788926685e-03, {+1.836920702608942e-01, +7.457754226757105e-01}},
+       Descriptor{7, 1.951861257384991e-03, {+6.535330886596633e-01, +9.133523126050157e-01, +2.888629020786040e-01}},
+       Descriptor{7, 9.537937942918817e-04, {+8.475635090984786e-01, +9.748482139426561e-01, +5.534277540813458e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 20:
+  case 21: {
+    constexpr size_t nb_points = 580;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 3.007917561622623e-03, {-6.174957125232848e-01}},
+       Descriptor{2, 6.157551855986320e-03, {-2.913675401437903e-01}},
+       Descriptor{3, 7.451864938264940e-04, {+8.622583182294242e-01}},
+       Descriptor{3, 3.473649875824585e-03, {+5.666798116095041e-01}},
+       Descriptor{5, 5.231065457280251e-04, {+7.962288576876380e-01, +9.999999871114571e-01}},
+       Descriptor{5, 4.721261422431887e-03, {+6.935661772320008e-01, +3.463806774517028e-01}},
+       Descriptor{5, 2.337222373763527e-03, {+8.718362020420203e-01, -6.252215000307171e-01}},
+       Descriptor{6, 3.841754898565957e-03, {-4.164545409594995e-01, +8.053481425029153e-01}},
+       Descriptor{6, 1.507767719634259e-04, {+9.738519593091541e-01, +8.745003988374457e-01}},
+       Descriptor{6, 3.214079409518292e-03, {+7.290234505608810e-01, +2.451675433295293e-01}},
+       Descriptor{6, 2.245446622613030e-03, {+6.496117102813809e-01, +8.441031264282526e-01}},
+       Descriptor{6, 1.041367673151780e-03, {+8.919707516952021e-01, +1.301935885566839e-01}},
+       Descriptor{6, 5.920000965024342e-04, {+7.833271437290619e-01, +9.532187072534166e-01}},
+       Descriptor{6, 4.100172337698149e-04, {+9.699411873288849e-01, +2.710159328732420e-01}},
+       Descriptor{6, 7.465850926621290e-04, {+1.720339772207737e-01, +9.847690037797490e-01}},
+       Descriptor{6, 3.743289683970358e-03, {+5.380985926191851e-01, -2.207631346215846e-01}},
+       Descriptor{6, 4.534357504253366e-04, {+5.536849035893876e-01, +9.989728978231427e-01}},
+       Descriptor{6, 4.182867578178122e-03, {+2.235580139510551e-01, +4.418577456047065e-01}},
+       Descriptor{6, 7.389797381822837e-04, {+8.700625815926416e-01, +4.988147208937697e-01}},
+       Descriptor{7, 1.371282553387760e-03, {+7.501439986013229e-01, +9.439853549264192e-01, +3.972114022576276e-01}},
+       Descriptor{7, 3.675472168789246e-04, {+9.025858122958752e-01, +9.846237882037482e-01, +6.307126444914427e-01}},
+       Descriptor{7, 1.258019483515844e-03, {+2.217794596478607e-01, +9.822849317288322e-02, +8.721988578583585e-01}},
+       Descriptor{7, 1.516565561694638e-03, {+9.568422563542535e-01, +2.081392891496346e-01, +4.958409266028638e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+
+  default: {
+    throw NormalError("Gauss quadrature formulae handle degrees up to " +
+                      std::to_string(CubeGaussQuadrature::max_degree) + " on cubes");
+  }
+  }
+}
diff --git a/src/analysis/CubeGaussQuadrature.hpp b/src/analysis/CubeGaussQuadrature.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..bd4ed179dd3cf3745e0dc4512eaa543f1ba6e1b9
--- /dev/null
+++ b/src/analysis/CubeGaussQuadrature.hpp
@@ -0,0 +1,39 @@
+#ifndef CUBE_GAUSS_QUADRATURE_HPP
+#define CUBE_GAUSS_QUADRATURE_HPP
+
+#include <analysis/QuadratureFormula.hpp>
+
+/**
+ * Defines Cube Gauss quadrature on the reference element
+ * \f$]-1,1[^3\f$.
+ *
+ * \note formulae are provided by
+ *
+ * "Addendum to the paper 'High-order symmetric cubature rules for
+ * tetrahedra and pyramids'" Jan Jasˁkowiec & N. Sukumar (2020).
+ */
+class CubeGaussQuadrature final : public QuadratureFormula<3>
+{
+ public:
+  constexpr static size_t max_degree = 21;
+
+ private:
+  void _buildPointAndWeightLists(const size_t degree);
+
+ public:
+  CubeGaussQuadrature(CubeGaussQuadrature&&)      = default;
+  CubeGaussQuadrature(const CubeGaussQuadrature&) = default;
+
+ private:
+  friend class QuadratureManager;
+
+  explicit CubeGaussQuadrature(const size_t degree) : QuadratureFormula<3>{QuadratureType::Gauss}
+  {
+    this->_buildPointAndWeightLists(degree);
+  }
+
+  CubeGaussQuadrature()  = delete;
+  ~CubeGaussQuadrature() = default;
+};
+
+#endif   // CUBE_GAUSS_QUADRATURE_HPP
diff --git a/src/analysis/GaussLegendreQuadratureDescriptor.hpp b/src/analysis/GaussLegendreQuadratureDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c37122516fa67650307aaab421120da9353f51d0
--- /dev/null
+++ b/src/analysis/GaussLegendreQuadratureDescriptor.hpp
@@ -0,0 +1,50 @@
+#ifndef GAUSS_LEGENDRE_QUADRATURE_DESCRIPTOR_HPP
+#define GAUSS_LEGENDRE_QUADRATURE_DESCRIPTOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+
+#include <sstream>
+
+class GaussLegendreQuadratureDescriptor final : public IQuadratureDescriptor
+{
+ private:
+  size_t m_degree;
+
+ public:
+  bool
+  isTensorial() const
+  {
+    return true;
+  }
+
+  QuadratureType
+  type() const
+  {
+    return QuadratureType::GaussLegendre;
+  }
+
+  size_t
+  degree() const
+  {
+    return m_degree;
+  }
+
+  std::string
+  name() const
+  {
+    std::ostringstream os;
+    os << ::name(this->type()) << "(" << m_degree << ")";
+    return os.str();
+  }
+
+  GaussLegendreQuadratureDescriptor(size_t degree) : m_degree{degree} {}
+  GaussLegendreQuadratureDescriptor() noexcept = delete;
+
+  GaussLegendreQuadratureDescriptor(const GaussLegendreQuadratureDescriptor&) = default;
+
+  GaussLegendreQuadratureDescriptor(GaussLegendreQuadratureDescriptor&&) noexcept = default;
+
+  virtual ~GaussLegendreQuadratureDescriptor() noexcept = default;
+};
+
+#endif   // GAUSS_LEGENDRE_QUADRATURE_DESCRIPTOR_HPP
diff --git a/src/analysis/GaussLobattoQuadratureDescriptor.hpp b/src/analysis/GaussLobattoQuadratureDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0cc3a8e6ef108af6708098c0735338621ed880a4
--- /dev/null
+++ b/src/analysis/GaussLobattoQuadratureDescriptor.hpp
@@ -0,0 +1,50 @@
+#ifndef GAUSS_LOBATTO_QUADRATURE_DESCRIPTOR_HPP
+#define GAUSS_LOBATTO_QUADRATURE_DESCRIPTOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+
+#include <sstream>
+
+class GaussLobattoQuadratureDescriptor final : public IQuadratureDescriptor
+{
+ private:
+  size_t m_degree;
+
+ public:
+  bool
+  isTensorial() const
+  {
+    return true;
+  }
+
+  QuadratureType
+  type() const
+  {
+    return QuadratureType::GaussLobatto;
+  }
+
+  size_t
+  degree() const
+  {
+    return m_degree;
+  }
+
+  std::string
+  name() const
+  {
+    std::ostringstream os;
+    os << ::name(this->type()) << "(" << m_degree << ")";
+    return os.str();
+  }
+
+  GaussLobattoQuadratureDescriptor(size_t degree) : m_degree{degree} {}
+  GaussLobattoQuadratureDescriptor() noexcept = delete;
+
+  GaussLobattoQuadratureDescriptor(const GaussLobattoQuadratureDescriptor&) = default;
+
+  GaussLobattoQuadratureDescriptor(GaussLobattoQuadratureDescriptor&&) noexcept = default;
+
+  virtual ~GaussLobattoQuadratureDescriptor() noexcept = default;
+};
+
+#endif   // GAUSS_LOBATTO_QUADRATURE_DESCRIPTOR_HPP
diff --git a/src/analysis/GaussQuadratureDescriptor.hpp b/src/analysis/GaussQuadratureDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..752ac40d4fa3b609f848ea82bf10b0576a3e794f
--- /dev/null
+++ b/src/analysis/GaussQuadratureDescriptor.hpp
@@ -0,0 +1,50 @@
+#ifndef GAUSS_QUADRATURE_DESCRIPTOR_HPP
+#define GAUSS_QUADRATURE_DESCRIPTOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+
+#include <sstream>
+
+class GaussQuadratureDescriptor final : public IQuadratureDescriptor
+{
+ private:
+  size_t m_degree;
+
+ public:
+  bool
+  isTensorial() const
+  {
+    return false;
+  }
+
+  QuadratureType
+  type() const
+  {
+    return QuadratureType::Gauss;
+  }
+
+  size_t
+  degree() const
+  {
+    return m_degree;
+  }
+
+  std::string
+  name() const
+  {
+    std::ostringstream os;
+    os << ::name(this->type()) << "(" << m_degree << ")";
+    return os.str();
+  }
+
+  GaussQuadratureDescriptor(size_t degree) : m_degree{degree} {}
+  GaussQuadratureDescriptor() noexcept = delete;
+
+  GaussQuadratureDescriptor(const GaussQuadratureDescriptor&) = default;
+
+  GaussQuadratureDescriptor(GaussQuadratureDescriptor&&) noexcept = default;
+
+  virtual ~GaussQuadratureDescriptor() noexcept = default;
+};
+
+#endif   // GAUSS_QUADRATURE_DESCRIPTOR_HPP
diff --git a/src/analysis/IQuadratureDescriptor.hpp b/src/analysis/IQuadratureDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5d7e3685a8c7a8a6014ce7d8753d231183ba52aa
--- /dev/null
+++ b/src/analysis/IQuadratureDescriptor.hpp
@@ -0,0 +1,25 @@
+#ifndef I_QUADRATURE_DESCRIPTOR_HPP
+#define I_QUADRATURE_DESCRIPTOR_HPP
+
+#include <analysis/QuadratureType.hpp>
+
+#include <string>
+
+class IQuadratureDescriptor
+{
+ public:
+  virtual QuadratureType type() const = 0;
+  virtual bool isTensorial() const    = 0;
+  virtual size_t degree() const       = 0;
+  virtual std::string name() const    = 0;
+
+  IQuadratureDescriptor() noexcept = default;
+
+  IQuadratureDescriptor(const IQuadratureDescriptor&) = default;
+
+  IQuadratureDescriptor(IQuadratureDescriptor&&) noexcept = default;
+
+  virtual ~IQuadratureDescriptor() noexcept = default;
+};
+
+#endif   // I_QUADRATURE_DESCRIPTOR_HPP
diff --git a/src/analysis/PrismGaussQuadrature.cpp b/src/analysis/PrismGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c2cde582ba1c4e2b6b5077e976a43dea5deaca77
--- /dev/null
+++ b/src/analysis/PrismGaussQuadrature.cpp
@@ -0,0 +1,804 @@
+#include <analysis/PrismGaussQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+void
+PrismGaussQuadrature::_buildPointAndWeightLists(const size_t degree)
+{
+  using R2 = TinyVector<2>;
+  using R3 = TinyVector<3>;
+
+  struct Descriptor
+  {
+    int id;
+    double weight;
+    std::vector<double> lambda_list;
+  };
+
+  auto fill_quadrature_points = [](auto descriptor_list, auto& point_list, auto& weight_list) {
+    auto to_R3 = [](R2 X, double z) { return R3{X[0], X[1], z}; };
+
+    Assert(point_list.size() == weight_list.size());
+
+    const R2 A = {0, 0};
+    const R2 B = {1, 0};
+    const R2 C = {0, 1};
+
+    size_t k = 0;
+    for (size_t i = 0; i < descriptor_list.size(); ++i) {
+      const auto [id, w, value_list] = descriptor_list[i];
+
+      switch (id) {
+      case 1: {
+        Assert(value_list.size() == 0);
+
+        point_list[k]  = {1. / 3, 1. / 3, 0.};
+        weight_list[k] = w;
+
+        k += 1;
+        break;
+      }
+      case 2: {
+        Assert(value_list.size() == 1);
+        const double c    = value_list[0];
+        point_list[k + 0] = {1. / 3, 1. / 3, +c};
+        point_list[k + 1] = {1. / 3, 1. / 3, -c};
+
+        for (size_t l = 0; l < 2; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 2;
+        break;
+      }
+      case 3: {
+        Assert(value_list.size() == 1);
+        const double l0 = value_list[0];
+        const double l1 = 1 - 2 * l0;
+
+        point_list[k + 0] = to_R3(l0 * A + l0 * B + l1 * C, 0);
+        point_list[k + 1] = to_R3(l0 * A + l1 * B + l0 * C, 0);
+        point_list[k + 2] = to_R3(l1 * A + l0 * B + l0 * C, 0);
+
+        for (size_t l = 0; l < 3; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 3;
+        break;
+      }
+      case 4: {
+        Assert(value_list.size() == 2);
+        const double l0 = value_list[0];
+        const double l1 = 1 - 2 * l0;
+        const double z  = value_list[1];
+
+        point_list[k + 0] = to_R3(l0 * A + l0 * B + l1 * C, +z);
+        point_list[k + 1] = to_R3(l0 * A + l1 * B + l0 * C, +z);
+        point_list[k + 2] = to_R3(l1 * A + l0 * B + l0 * C, +z);
+        point_list[k + 3] = to_R3(l0 * A + l0 * B + l1 * C, -z);
+        point_list[k + 4] = to_R3(l0 * A + l1 * B + l0 * C, -z);
+        point_list[k + 5] = to_R3(l1 * A + l0 * B + l0 * C, -z);
+
+        for (size_t l = 0; l < 6; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 6;
+        break;
+      }
+      case 5: {
+        Assert(value_list.size() == 2);
+        const double l0 = value_list[0];
+        const double l1 = value_list[1];
+        const double l2 = 1 - l0 - l1;
+
+        point_list[k + 0] = to_R3(l0 * A + l1 * B + l2 * C, 0);
+        point_list[k + 1] = to_R3(l0 * A + l2 * B + l1 * C, 0);
+        point_list[k + 2] = to_R3(l1 * A + l0 * B + l2 * C, 0);
+        point_list[k + 3] = to_R3(l1 * A + l2 * B + l0 * C, 0);
+        point_list[k + 4] = to_R3(l2 * A + l0 * B + l1 * C, 0);
+        point_list[k + 5] = to_R3(l2 * A + l1 * B + l0 * C, 0);
+
+        for (size_t l = 0; l < 6; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 6;
+        break;
+      }
+      case 6: {
+        Assert(value_list.size() == 3);
+        const double l0 = value_list[0];
+        const double l1 = value_list[1];
+        const double l2 = 1 - l0 - l1;
+        const double z  = value_list[2];
+
+        point_list[k + 0]  = to_R3(l0 * A + l1 * B + l2 * C, +z);
+        point_list[k + 1]  = to_R3(l0 * A + l2 * B + l1 * C, +z);
+        point_list[k + 2]  = to_R3(l1 * A + l0 * B + l2 * C, +z);
+        point_list[k + 3]  = to_R3(l1 * A + l2 * B + l0 * C, +z);
+        point_list[k + 4]  = to_R3(l2 * A + l0 * B + l1 * C, +z);
+        point_list[k + 5]  = to_R3(l2 * A + l1 * B + l0 * C, +z);
+        point_list[k + 6]  = to_R3(l0 * A + l1 * B + l2 * C, -z);
+        point_list[k + 7]  = to_R3(l0 * A + l2 * B + l1 * C, -z);
+        point_list[k + 8]  = to_R3(l1 * A + l0 * B + l2 * C, -z);
+        point_list[k + 9]  = to_R3(l1 * A + l2 * B + l0 * C, -z);
+        point_list[k + 10] = to_R3(l2 * A + l0 * B + l1 * C, -z);
+        point_list[k + 11] = to_R3(l2 * A + l1 * B + l0 * C, -z);
+
+        for (size_t l = 0; l < 12; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 12;
+        break;
+      }
+        // LCOV_EXCL_START
+      default: {
+        throw UnexpectedError("invalid quadrature id");
+      }
+        // LCOV_EXCL_STOP
+      }
+    }
+  };
+
+  switch (degree) {
+  case 0:
+  case 1: {
+    constexpr size_t nb_points = 1;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.000000000000000e+00, {}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 2: {
+    constexpr size_t nb_points = 5;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.344355869392759e-01, {-8.431650688665664e-01}},
+       Descriptor{3, 1.770429420404827e-01, {+1.046424703769979e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 3: {
+    constexpr size_t nb_points = 8;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.803034341765672e-01, {-9.614404179888022e-01}},
+       Descriptor{3, 1.134313729984015e-01, {+4.890588576053607e-01}},
+       Descriptor{3, 9.969967088388698e-02, {+7.783177802730620e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 4: {
+    constexpr size_t nb_points = 11;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.079119748155355e-01, {-8.668619740090348e-01}},
+       Descriptor{3, 1.364146126054776e-01, {+4.686558098619952e-01}},
+       Descriptor{4, 6.248870209208267e-02, {+1.007404057989106e-01, +6.756398236822597e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 5: {
+    constexpr size_t nb_points = 16;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 2.071428343483058e-01, {}}, Descriptor{3, 3.807558903099764e-02, {+5.176461782716475e-02}},
+       Descriptor{4, 7.637426139226190e-02, {+1.663967696311171e-01, +8.071634863884439e-01}},
+       Descriptor{4, 3.673080503418831e-02, {+4.976649895838920e-01, -3.972616744496609e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 6: {
+    constexpr size_t nb_points = 28;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 3.614462938209505e-02, {+9.850385924415599e-01}},
+       Descriptor{2, 5.544690202422076e-02, {-5.039978952117941e-01}},
+       Descriptor{3, 1.164296357658436e-02, {+1.750424465865124e-02}},
+       Descriptor{3, 7.688064192655711e-02, {+1.617417813899514e-01}},
+       Descriptor{4, 4.962685514962321e-02, {+4.656535513495914e-01, +4.811426008466984e-01}},
+       Descriptor{6, 2.112374914835039e-02, {+3.459486985245699e-02, +2.025039451729335e-01, -8.094634904091534e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 7: {
+    constexpr size_t nb_points = 35;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.844568111268929e-02, {+9.802280695908916e-01}},
+       Descriptor{3, 6.118242173095039e-03, {+8.337595466021473e-03}},
+       Descriptor{4, 2.155086408622646e-02, {+4.815219753291366e-01, -8.413873542065260e-01}},
+       Descriptor{4, 2.917852490209846e-02, {+9.548324837148936e-02, -7.958490905869831e-01}},
+       Descriptor{5, 2.551485633514925e-02, {+7.429966820728956e-01, +1.214913159837829e-02}},
+       Descriptor{6, 3.894070327620761e-02, {+1.529845984247976e-01, +3.051562164322261e-01, +4.039345605321099e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 8: {
+    constexpr size_t nb_points = 46;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.073493664285546e-02, {+2.244338661059906e-01}},
+       Descriptor{2, 5.108942711218155e-02, {-6.818254415708658e-01}},
+       Descriptor{3, 5.269974490650717e-02, {+4.600889628137106e-01}},
+       Descriptor{3, 1.815224878414972e-02, {+5.341235093690714e-02}},
+       Descriptor{4, 7.145357753538616e-03, {+4.723878583976938e-02, +8.288154430586802e-01}},
+       Descriptor{4, 4.062926265898933e-02, {+1.740616079243704e-01, +4.060842293545903e-01}},
+       Descriptor{4, 1.114302734846531e-02, {+1.597492639425890e-01, +9.658651665406544e-01}},
+       Descriptor{4, 2.108650929358649e-02, {+4.585690687909513e-01, +8.761959100002542e-01}},
+       Descriptor{6, 1.364752909087307e-02, {+8.588127507759013e-03, +7.285980718010000e-01, +5.773502691896257e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 9: {
+    constexpr size_t nb_points = 59;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 3.428347129092372e-02, {-2.750184190077768e-01}},
+       Descriptor{3, 1.639279770445363e-02, {+4.938630638969568e-01}},
+       Descriptor{4, 2.347178069870322e-02, {+2.362540169543293e-01, +8.712387576289576e-01}},
+       Descriptor{4, 5.761848158278895e-03, {+7.223833941638236e-02, +9.551937402361604e-01}},
+       Descriptor{4, 3.492479305778020e-02, {+4.383137607101617e-01, +4.364117817130079e-01}},
+       Descriptor{4, 7.373899623212321e-03, {+3.643401649407788e-02, +5.030056360566418e-01}},
+       Descriptor{4, 5.722460846448911e-03, {+4.828779929693860e-01, -9.817457230015865e-01}},
+       Descriptor{4, 2.433874301444529e-02, {+1.628698857202373e-01, -1.748668736196710e-01}},
+       Descriptor{5, 9.412963663135166e-03, {+8.213377527237301e-01, +1.626087609745086e-01}},
+       Descriptor{6, 1.801797749439730e-02, {+4.249444950639278e-02, +2.561926710584905e-01, +6.836158559766136e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 10: {
+    constexpr size_t nb_points = 84;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 3.219608823836797e-02, {}},
+       Descriptor{2, 1.323959532338741e-02, {+9.650866816169476e-01}},
+       Descriptor{3, 1.663071726413940e-02, {+4.300104731739727e-01}},
+       Descriptor{3, 2.237117375679376e-02, {+1.095696683547513e-01}},
+       Descriptor{3, 2.965144268028371e-03, {+1.581483663138682e-02}},
+       Descriptor{4, 2.041220712674361e-02, {+4.356018236697312e-01, +7.829035904287732e-01}},
+       Descriptor{4, 1.414434969765846e-02, {+1.485976165307029e-01, +7.673257532061416e-01}},
+       Descriptor{4, 3.545708824271026e-03, {+5.196648358223575e-02, +4.892913949246038e-01}},
+       Descriptor{4, 6.058080482992846e-03, {+4.990219694442680e-01, +6.916716292769725e-01}},
+       Descriptor{4, 3.555888039958573e-03, {+4.155250160277021e-02, +8.910010843120678e-01}},
+       Descriptor{4, 3.038102466521747e-02, {+2.343945196820784e-01, +4.651342767282841e-01}},
+       Descriptor{6, 6.211856757092111e-03, {+5.281151684656214e-02, +2.716521744885937e-01, +9.566215690575565e-01}},
+       Descriptor{6, 6.618419166146100e-03, {+1.675738337212976e-01, +1.019125209869291e-02, +5.391797458851759e-01}},
+       Descriptor{6, 1.607306259567184e-02, {+3.291869417398026e-01, +5.090816276695182e-02, -2.642847151350913e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 11: {
+    constexpr size_t nb_points = 99;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.168014406730469e-02, {-5.549549808499665e-01}},
+       Descriptor{2, 1.045760798587228e-02, {+9.704322100480244e-01}},
+       Descriptor{2, 3.493462507713402e-03, {-8.225054517645587e-02}},
+       Descriptor{3, 1.610610418171415e-02, {+4.269221881685347e-01}},
+       Descriptor{4, 2.203310285466430e-02, {+4.396327659106838e-01, +6.759979260851990e-01}},
+       Descriptor{4, 1.274418696180960e-02, {+2.009128761138035e-01, +8.360535436001461e-01}},
+       Descriptor{4, 2.842526101831001e-03, {+2.158509804317693e-02, +5.861905524880673e-01}},
+       Descriptor{4, 1.509857722810566e-03, {+4.941546402080231e-01, +9.747001655491775e-01}},
+       Descriptor{4, 3.650427064786428e-03, {+5.996027726544360e-02, +9.450272677498353e-01}},
+       Descriptor{4, 2.100290671698337e-02, {+2.328102236807494e-01, +2.635224843921006e-01}},
+       Descriptor{4, 1.876195853667291e-02, {+1.213201391549184e-01, +4.439791020520624e-01}},
+       Descriptor{4, 5.194819129164601e-03, {+4.987543911906001e-01, +5.750153420806698e-01}},
+       Descriptor{5, 7.050587938770526e-03, {+1.046140524481813e-01, +1.651849633425114e-02}},
+       Descriptor{6, 5.901269948294771e-03, {+7.196325697558484e-02, +2.979854667459965e-01, +9.371332380619902e-01}},
+       Descriptor{6, 6.212665465530579e-03, {+1.998026706474004e-01, +1.720948255102629e-02, +7.302645437140292e-01}},
+       Descriptor{6, 1.385914960018441e-02, {+3.197359768880742e-01, +4.628523865251271e-02, -2.345966255449601e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 12: {
+    constexpr size_t nb_points = 136;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.122540095264984e-02, {+9.249605281775015e-01}},
+       Descriptor{2, 2.250978701634355e-02, {+3.174565048121913e-01}},
+       Descriptor{3, 5.489424083937044e-03, {+4.853372007888398e-02}},
+       Descriptor{3, 4.964406537953573e-04, {+3.340541016130764e-04}},
+       Descriptor{4, 1.020904001099484e-02, {+4.849074297077183e-01, +5.587032406470354e-01}},
+       Descriptor{4, 2.100649550809441e-03, {+1.497566246841529e-01, -9.990255354639939e-01}},
+       Descriptor{4, 1.520942066705075e-02, {+2.344536517846724e-01, +3.308248202440709e-01}},
+       Descriptor{4, 1.365828920018060e-02, {+4.409030162469282e-01, -7.263193646044468e-02}},
+       Descriptor{4, 2.788739322048965e-03, {+4.870412467653055e-01, +9.373420953931805e-01}},
+       Descriptor{4, 2.252095085593022e-03, {+2.486381930021292e-02, +6.814367870790086e-01}},
+       Descriptor{4, 9.917448258967848e-03, {+1.118542147928236e-01, +7.546508799087877e-01}},
+       Descriptor{5, 9.524594103832613e-03, {+1.207867185816364e-01, +7.097236881695401e-01}},
+       Descriptor{5, 6.688135429090100e-03, {+6.481099336610571e-01, +3.419306029008594e-01}},
+       Descriptor{6, 5.667322471331417e-03, {+2.009036502771764e-02, +1.335404714654308e-01, +3.889378443355619e-01}},
+       Descriptor{6, 1.258664545850983e-02, {+3.214917379706315e-01, +1.648190492804087e-01, +7.118012620024268e-01}},
+       Descriptor{6, 1.143701180003211e-02, {+6.899565054914573e-02, +6.636404691861656e-01, -4.223089212637487e-01}},
+       Descriptor{6, 4.495597556144068e-03, {+1.996263664334138e-02, +2.615529990296567e-01, +8.442893497066150e-01}},
+       Descriptor{6, 1.189774020910150e-03, {+9.002977238916547e-02, +1.552185633457250e-02, +9.525314828501794e-01}},
+       Descriptor{6, 4.663778699523011e-03, {+1.172214513692467e-01, +5.641016399337317e-01, -9.443120180277191e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 13: {
+    constexpr size_t nb_points = 162;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{4, 2.629059706793623e-03, {+4.898439392870481e-01, +9.257498273505042e-01}},
+       Descriptor{4, 5.973423749312596e-03, {+4.652402431593082e-01, -6.807809122309588e-01}},
+       Descriptor{4, 8.093549424155690e-03, {+1.189161740974999e-01, +8.113054084779335e-01}},
+       Descriptor{4, 6.819116259035672e-03, {+4.065656818638698e-01, +1.925054913726048e-02}},
+       Descriptor{4, 4.933451368773049e-03, {+3.731243598486834e-01, +9.244211981221383e-01}},
+       Descriptor{4, 2.014708013288427e-03, {+2.222577118367550e-02, +2.903696330212439e-01}},
+       Descriptor{4, 1.233577073707530e-02, {+4.252710267490136e-01, +6.716647847389562e-01}},
+       Descriptor{4, 1.136479433767108e-02, {+2.896929731593648e-01, +3.940165471189255e-01}},
+       Descriptor{4, 6.666864960682452e-03, {+1.072868932263340e-01, +1.612419567102727e-01}},
+       Descriptor{4, 1.194522013639762e-02, {+2.080125480772502e-01, +2.621034383425459e-01}},
+       Descriptor{4, 1.068788094061122e-02, {+2.230030950549982e-01, +7.517293034088793e-01}},
+       Descriptor{4, 1.666344327463221e-03, {+2.617439160849268e-02, +8.530545934139608e-01}},
+       Descriptor{5, 4.512051617065152e-03, {+6.914170159250816e-03, +2.742659465495736e-01}},
+       Descriptor{5, 3.488706839443290e-03, {+1.210652398684976e-01, +2.342377584496339e-02}},
+       Descriptor{5, 1.118035069441576e-02, {+9.353601775463583e-02, +5.415308571572887e-01}},
+       Descriptor{6, 4.205260232450528e-03, {+1.908095378196189e-02, +4.425451813256520e-01, +3.662111224258147e-01}},
+       Descriptor{6, 4.376791749742736e-03, {+2.923396969545124e-01, +1.817765226028588e-02, +7.893982266689565e-01}},
+       Descriptor{6, 4.406755041936957e-03, {+2.123093816715971e-02, +8.615902483468726e-01, +5.566323460176222e-01}},
+       Descriptor{6, 1.203503267726805e-02, {+2.541452627735283e-01, +7.525507194012332e-02, -4.323213872909485e-01}},
+       Descriptor{6, 1.398692188464922e-03, {+1.414034499801619e-01, +2.485309321657718e-02, +9.754978010051167e-01}},
+       Descriptor{6, 4.755154887378063e-03, {+5.920519581168684e-01, +1.079442302776815e-01, +9.602045342477669e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 14: {
+    constexpr size_t nb_points = 194;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.177233712035789e-02, {-5.431508197665302e-01}},
+       Descriptor{3, 1.353619130586776e-02, {+1.531409494078462e-01}},
+       Descriptor{3, 2.863624459018961e-03, {+1.094642266754876e-01}},
+       Descriptor{4, 2.831710195314422e-03, {+4.852139749601931e-01, +9.499413855584002e-01}},
+       Descriptor{4, 4.354649559352045e-03, {+4.811857851081537e-01, -3.416804188824072e-01}},
+       Descriptor{4, 9.145278212110571e-04, {+8.542525491697950e-02, +9.993425247803482e-01}},
+       Descriptor{4, 1.640665865351787e-03, {+4.949498254464672e-01, +4.043764192878801e-01}},
+       Descriptor{4, 1.217329578934041e-02, {+2.006722689888837e-01, +6.704323691216523e-01}},
+       Descriptor{4, 1.062128023171532e-03, {+1.346314023891985e-02, +4.402219142928553e-01}},
+       Descriptor{4, 8.146219031353574e-03, {+3.888073656098609e-01, +8.664942125245655e-01}},
+       Descriptor{4, 1.188198250865211e-02, {+2.589597694248802e-01, -3.805332165701859e-01}},
+       Descriptor{4, 4.510606073543659e-03, {+6.566746391585167e-02, +3.180500426640964e-01}},
+       Descriptor{4, 1.201058503487853e-02, {+4.462504387663027e-01, +6.737378781532631e-01}},
+       Descriptor{4, 4.766438302156675e-03, {+1.155506883468244e-01, +8.527706525272908e-01}},
+       Descriptor{4, 9.777024948606015e-04, {+2.579397983791752e-02, +9.172594869558284e-01}},
+       Descriptor{5, 5.111880702605887e-03, {+2.313133650799548e-02, +3.737411598567404e-01}},
+       Descriptor{5, 2.171762107022176e-03, {+7.808376567245785e-02, +8.716783743732383e-03}},
+       Descriptor{5, 1.040227457387526e-02, {+1.932090397525293e-01, +4.408189862889988e-01}},
+       Descriptor{6, 3.590362689299532e-03, {+1.106956102435793e-02, +3.495104935335414e-01, +6.888113400872247e-01}},
+       Descriptor{6, 5.210848278113965e-03, {+2.813786819004687e-01, +7.252407174904094e-02, +8.818281117320524e-01}},
+       Descriptor{6, 2.902421092721445e-03, {+1.360674597305564e-02, +7.694543610610796e-01, +2.082229797704095e-01}},
+       Descriptor{6, 1.093709739870311e-02, {+3.160642637515643e-01, +9.806129213500263e-02, -2.960531739682007e-01}},
+       Descriptor{6, 1.070623154200375e-03, {+2.010219519914552e-01, +1.015338057469439e-02, +9.567691001326991e-01}},
+       Descriptor{6, 2.366054231962321e-03, {+5.404862370418329e-01, +1.675040220811526e-01, +9.843823434581401e-01}},
+       Descriptor{6, 2.619672090010859e-03, {+1.016331118955575e-01, +1.682509189588544e-02, +7.176031930976530e-01}},
+       Descriptor{6, 7.096030229028862e-03, {+7.553391402298588e-01, +5.528506804337090e-02, -4.978572721822902e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 15: {
+    constexpr size_t nb_points = 238;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.097348853586182e-02, {+6.802218229609156e-01}},
+       Descriptor{2, 3.261395830734888e-03, {+9.049666192679098e-01}},
+       Descriptor{3, 9.394140546438022e-03, {+2.180179116942069e-01}},
+       Descriptor{3, 7.472192538440717e-03, {+1.819996803717089e-01}},
+       Descriptor{3, 1.125148661004542e-03, {+1.740140116312610e-02}},
+       Descriptor{3, 4.027894259445648e-03, {+5.427115018599665e-02}},
+       Descriptor{4, 4.698740527412616e-03, {+3.702896314191779e-01, +1.017726699824298e-01}},
+       Descriptor{4, 4.423106873185891e-03, {+6.837923242190364e-02, +7.328812049735380e-01}},
+       Descriptor{4, 1.190871191679164e-02, {+4.041029865296590e-01, -3.417820508269636e-01}},
+       Descriptor{4, 3.419058355265174e-03, {+4.924009437532922e-01, -1.425907114174295e-01}},
+       Descriptor{4, 7.607869583069166e-03, {+4.679592294726652e-01, +6.612792268372575e-01}},
+       Descriptor{4, 3.487721814344312e-03, {+2.538613877625208e-01, +9.653983029752667e-01}},
+       Descriptor{4, 4.126571123768199e-03, {+1.490233678882002e-01, +9.207186791179688e-01}},
+       Descriptor{4, 2.055229043939243e-03, {+4.943016520035221e-01, +8.528797591642616e-01}},
+       Descriptor{5, 5.333436981117544e-03, {+2.380227290434666e-01, +1.919120975700847e-02}},
+       Descriptor{6, 4.565836294062908e-03, {+2.658819381343187e-01, +9.470242762478344e-02, +8.051851008291565e-01}},
+       Descriptor{6, 5.459132755230557e-04, {+2.824880313716481e-02, +6.797646253429619e-03, +5.550126100536413e-01}},
+       Descriptor{6, 4.319194829106329e-03, {+6.410939576871652e-01, +1.511219453234863e-02, +4.965601017444452e-01}},
+       Descriptor{6, 5.377414350253174e-03, {+1.570345607673937e-01, +7.920488512684705e-02, +2.807402393928705e-01}},
+       Descriptor{6, 7.161694599840893e-03, {+2.618681572601245e-01, +1.978262294209248e-01, +5.539260799071125e-01}},
+       Descriptor{6, 5.425741614460213e-04, {+6.532458874680514e-03, +4.868056225266830e-02, +9.130997730502041e-01}},
+       Descriptor{6, 1.437669184909563e-03, {+3.702624031957258e-01, +9.129771330289427e-02, +9.857586619210927e-01}},
+       Descriptor{6, 1.116039038725027e-03, {+1.377079768509805e-01, +3.730083695338125e-02, +9.761262490766817e-01}},
+       Descriptor{6, 4.855981285749960e-03, {+4.784950101267365e-01, +1.635654688089934e-01, +8.308622970412985e-01}},
+       Descriptor{6, 1.797153116855535e-03, {+3.278240979071815e-01, +2.496158166292168e-02, +9.410500001533124e-01}},
+       Descriptor{6, 9.605058598669360e-03, {+3.475735063324100e-01, +8.713022883913982e-02, -2.293965609348112e-01}},
+       Descriptor{6, 2.702127731284175e-03, {+1.096327891311607e-01, +1.283956773909193e-02, +3.801848607953903e-01}},
+       Descriptor{6, 5.504446034785920e-03, {+2.078784801536998e-01, +8.490456755292054e-02, +5.633005891537184e-01}},
+       Descriptor{6, 2.394682993576167e-03, {+1.953330193360589e-01, +1.430457871757475e-02, +7.767703733759026e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 16: {
+    constexpr size_t nb_points = 287;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.260828009387497e-02, {+6.806235853917343e-01}},
+       Descriptor{3, 5.804416586274102e-04, {+8.954084031214650e-03}},
+       Descriptor{3, 1.451527527703537e-03, {+5.539081004402164e-02}},
+       Descriptor{3, 1.103925194575912e-02, {+3.916317627335951e-01}},
+       Descriptor{4, 2.389660788445647e-03, {+5.610007788583466e-02, +7.685450672389825e-01}},
+       Descriptor{4, 1.118529592275642e-02, {+2.669967593271997e-01, +3.187166719009041e-01}},
+       Descriptor{4, 4.452710626148950e-03, {+1.619375672046858e-01, +7.425213340738127e-01}},
+       Descriptor{4, 9.931042920523256e-03, {+4.259891438745869e-01, -5.391540839796620e-01}},
+       Descriptor{4, 2.798331509865767e-03, {+4.943682456985981e-01, +2.910077027212657e-01}},
+       Descriptor{4, 4.595079144000768e-03, {+4.745817664118026e-01, +7.702843820193086e-01}},
+       Descriptor{4, 2.649741982735800e-03, {+2.785739189962955e-01, +9.637659513314421e-01}},
+       Descriptor{4, 2.379174945004374e-03, {+1.564426776295505e-01, +9.700236378009609e-01}},
+       Descriptor{4, 1.290211537994051e-03, {+4.933597406415689e-01, +9.402630634429575e-01}},
+       Descriptor{5, 7.174970445767767e-03, {+5.254267651000835e-01, +9.931494549031464e-02}},
+       Descriptor{5, 3.904196912906489e-03, {+3.482743570815052e-01, +2.127155614722863e-02}},
+       Descriptor{5, 5.896847921349438e-03, {+1.654756461617898e-01, +8.608509799836524e-02}},
+       Descriptor{6, 2.434940232310643e-03, {+1.639763763638563e-01, +5.517222908705197e-02, +8.775190479419079e-01}},
+       Descriptor{6, 7.867926281754163e-04, {+3.703825228193449e-02, +1.050366659141256e-02, +5.241361844419883e-01}},
+       Descriptor{6, 1.973345895093291e-03, {+6.350080118967869e-01, +5.563959422105249e-03, +6.546900713490693e-01}},
+       Descriptor{6, 3.902804476486658e-03, {+1.297894012222233e-01, +7.248681972075718e-02, +4.803772456633624e-01}},
+       Descriptor{6, 2.865194848993297e-03, {+3.059321030072774e-01, +6.212018826128094e-02, +8.420714610259229e-01}},
+       Descriptor{6, 4.265862973729131e-03, {+1.677554820715698e-01, +2.612303617350463e-01, +6.466653853149219e-01}},
+       Descriptor{6, 2.763846307136280e-04, {+1.529621929633006e-03, +3.376869229909243e-02, +8.957708274465785e-01}},
+       Descriptor{6, 1.594720494514084e-03, {+3.532796606132861e-01, +9.492422215886979e-02, +9.832783024215900e-01}},
+       Descriptor{6, 5.967018601525356e-04, {+9.592623259204197e-02, +2.488171702893576e-02, +9.797358781454657e-01}},
+       Descriptor{6, 5.089010857368133e-03, {+4.963305263335406e-01, +1.757029526928022e-01, +8.611424584617942e-01}},
+       Descriptor{6, 1.023252728565292e-03, {+2.738501035919119e-01, +1.407861220922605e-02, +9.516023826285763e-01}},
+       Descriptor{6, 4.833611837936114e-03, {+3.828844998895772e-01, +5.651716232003427e-02, -3.926210681927463e-01}},
+       Descriptor{6, 2.256570449498587e-03, {+8.993653239530038e-02, +1.924125702016291e-02, +2.277368351329287e-01}},
+       Descriptor{6, 4.394217441895532e-03, {+2.516074150213084e-01, +6.113701845424014e-02, +5.771276473820092e-01}},
+       Descriptor{6, 1.441280420852857e-03, {+1.524244609819593e-01, +5.018269956194812e-03, +7.029237083896506e-01}},
+       Descriptor{6, 7.638903866482450e-03, {+2.475011786624472e-01, +1.365910574355268e-01, -2.555004528315441e-01}},
+       Descriptor{6, 3.266920063147978e-03, {+7.591852986956938e-01, +1.735437181095786e-02, +2.659716737332686e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 17: {
+    constexpr size_t nb_points = 338;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 7.521652592973654e-03, {+7.332980977058475e-01}},
+       Descriptor{4, 3.952340142081542e-03, {+4.665556153120432e-01, +7.407048594232563e-01}},
+       Descriptor{4, 2.658797102389008e-03, {+4.923809674098066e-01, +1.925784688493710e-01}},
+       Descriptor{4, 2.303261680132425e-03, {+4.466963397682847e-01, +9.724914617208265e-01}},
+       Descriptor{4, 4.300609620564292e-03, {+2.058899494758731e-01, +2.386810048778012e-01}},
+       Descriptor{4, 3.022106015301819e-03, {+2.305779360323416e-01, +3.012229853956593e-01}},
+       Descriptor{4, 3.076949950084475e-04, {+7.072650234259644e-03, +6.517254037629554e-01}},
+       Descriptor{4, 3.073440319150984e-03, {+5.248385455049231e-02, -5.044383522897413e-01}},
+       Descriptor{4, 4.905529550079285e-03, {+2.780743897936196e-01, -4.157283981367805e-01}},
+       Descriptor{4, 2.706669648409243e-03, {+3.893995813144771e-01, -8.036253483433273e-01}},
+       Descriptor{4, 3.457774927613496e-03, {+1.132999955833483e-01, +4.250397278118490e-01}},
+       Descriptor{4, 5.170517454197127e-03, {+4.687774749283292e-01, -2.967208107336694e-01}},
+       Descriptor{4, 1.402258311530629e-03, {+4.942337889964747e-01, +8.919484290177192e-01}},
+       Descriptor{4, 4.240102604381064e-03, {+2.906999098818323e-01, +1.317224598994015e-01}},
+       Descriptor{4, 3.284148741170156e-03, {+9.376930399184973e-02, +8.547449509711846e-01}},
+       Descriptor{4, 1.134225985312425e-03, {+1.632812980104028e-01, +9.979247130558654e-01}},
+       Descriptor{4, 7.470854816360809e-03, {+1.774626418569745e-01, -6.791385858496140e-01}},
+       Descriptor{4, 3.434038461352071e-03, {+3.930786561208929e-01, +2.690582708305222e-01}},
+       Descriptor{4, 3.146260969412623e-03, {+2.711153947647855e-01, +9.623986887607461e-01}},
+       Descriptor{4, 5.752613942102343e-04, {+2.430622100156484e-02, +9.425587161003264e-01}},
+       Descriptor{5, 6.555279553032400e-03, {+1.273859447314594e-01, +2.765687474993688e-01}},
+       Descriptor{6, 2.945409561987205e-03, {+2.450510833431427e-01, +8.524685018280553e-02, +9.106045966534763e-01}},
+       Descriptor{6, 2.453063041563170e-03, {+3.192911229101512e-01, +2.764498521125525e-02, +5.755372475087557e-02}},
+       Descriptor{6, 2.870396640498966e-03, {+4.577741357714497e-01, +1.385428807092584e-01, -9.235677741171985e-02}},
+       Descriptor{6, 1.826827790065983e-03, {+2.421105196372634e-01, +1.245683910267135e-02, -2.750816491221573e-01}},
+       Descriptor{6, 4.684287720797846e-03, {+7.269945541994681e-01, +8.880930240992388e-02, +2.353034707366664e-01}},
+       Descriptor{6, 6.198783236777368e-03, {+4.870517086755036e-01, +1.725779658472295e-01, -5.739332375158235e-01}},
+       Descriptor{6, 4.197863049050683e-03, {+1.582123188291182e-01, +3.108975273704328e-01, +8.562321460031186e-01}},
+       Descriptor{6, 2.552814201463011e-03, {+5.973833093262283e-01, +4.329678563553625e-02, +7.520938435967152e-01}},
+       Descriptor{6, 2.090115170917154e-03, {+1.111929938785087e-01, +3.682608320557917e-02, +2.115412627288322e-02}},
+       Descriptor{6, 7.568671403396213e-04, {+3.878650062905668e-02, +7.045410253068108e-03, +2.249484087437713e-01}},
+       Descriptor{6, 1.300065728839461e-03, {+1.417441449927015e-01, +6.656135005864013e-03, +3.824194976983090e-01}},
+       Descriptor{6, 8.479873802967710e-04, {+1.343472140817544e-01, +2.698141838221961e-02, +9.741848074979857e-01}},
+       Descriptor{6, 6.404613040101877e-03, {+3.195031824862086e-01, +8.134405613722046e-02, +4.381759877407309e-01}},
+       Descriptor{6, 1.132352094196614e-03, {+8.263084385883397e-02, +1.026354650397643e-02, +7.684631026560178e-01}},
+       Descriptor{6, 2.064410253144382e-03, {+3.827807429336583e-01, +6.749013415056980e-03, +5.465728592769658e-01}},
+       Descriptor{6, 1.048193458332381e-03, {+2.446543644502634e-01, +5.650199208693200e-03, +8.553667006980621e-01}},
+       Descriptor{6, 9.390285812952761e-04, {+2.637071367985937e-02, +6.264598409479015e-01, +9.755523282064171e-01}},
+       Descriptor{6, 4.216060332324916e-03, {+4.340745420159236e-02, +1.947680793017931e-01, +6.465743617002622e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 18: {
+    constexpr size_t nb_points = 396;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{4, 2.257692848096014e-03, {+4.728631477307805e-01, +9.068774432765224e-01}},
+       Descriptor{4, 3.522000993611865e-03, {+1.090318158369727e-01, +5.191310054808369e-01}},
+       Descriptor{4, 4.461723497786998e-03, {+2.141954002982068e-01, +6.975267033041567e-01}},
+       Descriptor{4, 3.707999359392404e-04, {+8.415088753342596e-03, +4.704963597796460e-01}},
+       Descriptor{4, 2.423661142721756e-03, {+4.941564402940962e-02, -3.771046294090744e-01}},
+       Descriptor{4, 2.814996700016239e-03, {+4.574258170355346e-01, -2.709290831593626e-01}},
+       Descriptor{4, 5.337642166957648e-03, {+2.945570490820424e-01, -1.757797989950063e-01}},
+       Descriptor{4, 8.574266664464696e-03, {+4.148583469418042e-01, -2.903119874076979e-01}},
+       Descriptor{4, 2.265725871486650e-03, {+1.801567120271234e-01, -2.996313713953939e-01}},
+       Descriptor{4, 2.955157959707394e-03, {+4.092592655375933e-01, -8.687914069087144e-01}},
+       Descriptor{4, 3.919466496845992e-03, {+4.106876572727113e-01, -7.102916194693503e-01}},
+       Descriptor{4, 6.030674708196229e-04, {+4.942616172602019e-01, +9.613370670800333e-01}},
+       Descriptor{4, 4.577980994542588e-03, {+1.455821883151533e-01, +2.580797174139677e-01}},
+       Descriptor{4, 2.430663642304682e-03, {+7.759286964629900e-02, +8.574527537341975e-01}},
+       Descriptor{4, 2.159969925839523e-03, {+1.688466480848478e-01, +9.442832226314590e-01}},
+       Descriptor{4, 7.901051089233524e-03, {+2.737821968608845e-01, -5.261705496722796e-01}},
+       Descriptor{4, 2.173845391531516e-03, {+3.193703110432156e-01, +7.838756187326278e-01}},
+       Descriptor{4, 2.248646315174777e-03, {+2.789278090499041e-01, +9.684120338438452e-01}},
+       Descriptor{4, 3.442548195872391e-04, {+1.473433086781356e-02, +9.055493494371784e-01}},
+       Descriptor{5, 8.069935581612784e-03, {+2.803931989784251e-01, +1.730708483947652e-01}},
+       Descriptor{6, 2.387723163590078e-03, {+3.067402740370590e-01, +4.725833161993727e-02, +8.686462758628247e-01}},
+       Descriptor{6, 3.430528221040949e-03, {+5.587472044782793e-01, +6.374864935475613e-02, +1.027445357789138e-01}},
+       Descriptor{6, 6.029545367216524e-03, {+1.295467219204834e-01, +2.834002542935580e-01, -4.512744270430775e-01}},
+       Descriptor{6, 3.276823879158102e-03, {+2.943165499688927e-01, +3.236061609245209e-02, -4.364849670272578e-01}},
+       Descriptor{6, 3.663549210540714e-03, {+7.045618883179275e-01, +6.827271159006545e-02, +1.782321853488997e-01}},
+       Descriptor{6, 4.424488262807732e-03, {+5.336092397180819e-01, +8.232278479338200e-02, -6.571528038169470e-01}},
+       Descriptor{6, 2.760063523214631e-03, {+1.659343236267790e-01, +2.958566157742775e-01, +8.788603720074640e-01}},
+       Descriptor{6, 1.392109889443924e-03, {+5.873912217089957e-01, +9.153855225433397e-03, +7.488835368067833e-01}},
+       Descriptor{6, 2.138435872642536e-03, {+1.216404492275597e-01, +4.924556835776799e-02, -8.479900295560096e-02}},
+       Descriptor{6, 5.166067073488927e-04, {+4.208095332260363e-02, +7.753734519664009e-03, +9.396055358943239e-02}},
+       Descriptor{6, 1.953953141133318e-03, {+4.516351625773616e-01, +2.282191697171249e-02, +4.454084028264259e-01}},
+       Descriptor{6, 1.255008598363820e-03, {+1.252580641579170e-01, +7.396327404727600e-03, +3.172925739593950e-01}},
+       Descriptor{6, 1.042334114181810e-03, {+1.765722195894258e-01, +6.530021157253776e-02, +9.730112939053892e-01}},
+       Descriptor{6, 1.279342041886178e-03, {+2.404267015692479e-01, +8.587901550681881e-03, +1.654571949278815e-01}},
+       Descriptor{6, 1.087772234137440e-03, {+6.440585522631566e-02, +1.226780343665597e-02, +7.058192837170146e-01}},
+       Descriptor{6, 8.506443220554228e-04, {+2.681069831840066e-01, +4.479326561367245e-03, +6.566582555690533e-01}},
+       Descriptor{6, 1.071738436276040e-03, {+1.727205252244992e-01, +1.173345352446697e-02, +8.740347679939118e-01}},
+       Descriptor{6, 1.222389838372311e-03, {+1.118329953359960e-01, +5.446775610503505e-01, +9.879516615930261e-01}},
+       Descriptor{6, 1.426482164900413e-03, {+5.899536365475773e-01, +8.017587417702520e-03, +1.572003893989445e-01}},
+       Descriptor{6, 3.834177899773436e-03, {+9.615405978617643e-02, +2.067498834662211e-01, +7.510430318557424e-01}},
+       Descriptor{6, 4.758021594541443e-04, {+3.068968355307726e-01, +1.177465532391100e-02, +9.786522981024399e-01}},
+       Descriptor{6, 3.711298716255058e-04, {+7.984864478527595e-02, +1.740633561957181e-02, +9.787643170060012e-01}},
+       Descriptor{6, 2.736409660029034e-03, {+3.607777906277682e-02, +1.618562004775664e-01, +5.932565552690645e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 19: {
+    constexpr size_t nb_points = 420;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 6.766510972429800e-03, {+5.244830821459505e-01}},
+       Descriptor{2, 2.666997686915707e-03, {+7.453333881549420e-02}},
+       Descriptor{2, 2.198296683612197e-03, {+9.566097292149883e-01}},
+       Descriptor{3, 1.803557661024045e-03, {+4.935398626809867e-01}},
+       Descriptor{3, 6.354430508374619e-03, {+2.767659367008514e-01}},
+       Descriptor{3, 1.636834721720784e-03, {+5.457779485028427e-02}},
+       Descriptor{3, 4.893654007837226e-04, {+1.399697352188861e-02}},
+       Descriptor{4, 1.846418513040312e-03, {+1.418740786751734e-01, +9.552594993861951e-01}},
+       Descriptor{4, 2.773402798745861e-04, {+8.395599010104145e-03, +6.731987542402844e-01}},
+       Descriptor{4, 2.823634437215422e-03, {+1.035973359771996e-01, -2.005717856238193e-01}},
+       Descriptor{4, 7.726075998933063e-03, {+3.956080996828434e-01, +3.080298478277679e-01}},
+       Descriptor{4, 2.193983773241012e-04, {+1.400193119774357e-02, +2.354401887617481e-01}},
+       Descriptor{4, 4.366650366888420e-03, {+4.801897764603656e-01, +4.828040748109914e-01}},
+       Descriptor{4, 4.029293922819735e-03, {+1.623484102426076e-01, -2.002124012363609e-01}},
+       Descriptor{4, 1.130768692395794e-03, {+4.972236736522475e-01, +7.211608708255114e-01}},
+       Descriptor{4, 4.287863606686208e-03, {+1.990216655966096e-01, +7.769851170153003e-01}},
+       Descriptor{4, 1.592256072404633e-03, {+1.202889101503504e-01, -6.997989522584932e-01}},
+       Descriptor{4, 2.052668995583038e-03, {+5.321915260988313e-02, +6.322324604037224e-01}},
+       Descriptor{4, 3.894795325529755e-03, {+4.012796612843944e-01, +8.985968033126313e-01}},
+       Descriptor{4, 1.803402089659100e-03, {+1.089883669604236e-01, +6.414118273921905e-01}},
+       Descriptor{4, 4.906673814096024e-03, {+4.472748897677901e-01, +1.418065900083249e-01}},
+       Descriptor{4, 8.385245625735418e-04, {+6.923354485715784e-02, +9.592516295656434e-01}},
+       Descriptor{4, 1.344422701677694e-03, {+2.475145234973103e-01, +9.911400740550351e-01}},
+       Descriptor{4, 1.212351202429953e-03, {+4.533377451558121e-01, +9.881823361248698e-01}},
+       Descriptor{4, 2.704967330298844e-04, {+1.808374112681072e-02, +9.537067614804079e-01}},
+       Descriptor{5, 1.194495738162939e-03, {+1.297897131077726e-01, +6.917496569505294e-03}},
+       Descriptor{5, 3.103142225008695e-03, {+3.779418721990778e-01, +3.100617423599103e-02}},
+       Descriptor{5, 5.131660654552337e-03, {+6.446313119552312e-02, +2.203251202566757e-01}},
+       Descriptor{6, 4.803074338815450e-03, {+1.625899734623090e-01, +2.824754920851798e-01, +1.568351483023571e-01}},
+       Descriptor{6, 8.150376242849885e-04, {+8.118414809439785e-03, +5.478835112834492e-02, -3.967511256013889e-01}},
+       Descriptor{6, 4.498489872233412e-03, {+6.582273496659641e-01, +1.178005364736766e-01, -4.885151647510187e-01}},
+       Descriptor{6, 2.731777351387361e-03, {+2.876874815978630e-01, +3.552685414544682e-02, +5.867894747260645e-01}},
+       Descriptor{6, 3.917995492165165e-03, {+2.839484400308330e-01, +2.073917255967352e-01, +4.831782153742011e-01}},
+       Descriptor{6, 8.946420147168489e-04, {+2.824570038349533e-02, +8.622300269568527e-02, -2.019344195912693e-01}},
+       Descriptor{6, 1.272619581981183e-03, {+3.915043117238656e-01, +5.619651603686126e-03, +3.317594047785773e-01}},
+       Descriptor{6, 4.539117516816274e-03, {+7.376676137243446e-02, +3.389265718024346e-01, -3.045944670757652e-01}},
+       Descriptor{6, 1.228077139941574e-03, {+1.582419524500696e-01, +8.020518897879757e-03, +6.242276968562098e-01}},
+       Descriptor{6, 2.481352686408294e-03, {+2.270465393157224e-01, +7.611830639869993e-02, +7.779863298475371e-01}},
+       Descriptor{6, 6.742435916065792e-04, {+1.074023202728770e-02, +6.748601969235382e-02, +8.305354371679037e-01}},
+       Descriptor{6, 3.085688768414200e-03, {+4.506297544312898e-02, +1.495289831435912e-01, +3.936943128499052e-01}},
+       Descriptor{6, 2.827744882828859e-03, {+3.899263834124326e-01, +5.365999012062311e-02, +8.362140373651296e-01}},
+       Descriptor{6, 7.880156766981593e-04, {+1.426168210785968e-02, +4.039638109964547e-01, +9.545849701585185e-01}},
+       Descriptor{6, 9.734356783437631e-04, {+3.314062155351160e-01, +8.833083116611448e-03, +7.096795149078843e-01}},
+       Descriptor{6, 2.344473397349397e-04, {+1.223644156202090e-01, +1.039237935936368e-02, +9.900280695189960e-01}},
+       Descriptor{6, 2.754928134962740e-03, {+2.929652829983945e-01, +1.324448044285512e-01, +9.178743530773790e-01}},
+       Descriptor{6, 8.530955496579550e-04, {+2.534748953911949e-01, +5.351470915991541e-02, +9.845698380380822e-01}},
+       Descriptor{6, 1.720791066604888e-03, {+4.407858720850005e-02, +1.461188761758480e-01, +8.569909006217998e-01}},
+       Descriptor{6, 5.238060160822010e-03, {+5.031457633505478e-01, +1.264604222462178e-01, +6.487185269932558e-01}},
+       Descriptor{6, 7.780035531900878e-04, {+8.749997984117155e-03, +2.433623618774898e-01, +9.028600070589583e-01}},
+       Descriptor{6, 2.799719934133974e-03, {+4.433279475253553e-01, +2.569581578007861e-01, +7.536500284803950e-01}},
+       Descriptor{6, 1.887126925839933e-03, {+2.485412372956145e-01, +1.127715403373798e-02, +2.418109616381699e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 20: {
+    constexpr size_t nb_points = 518;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 4.747044232976959e-03, {+8.420166009462728e-01}},
+       Descriptor{3, 6.640322548036050e-04, {+1.441108736989831e-02}},
+       Descriptor{3, 9.620914054915759e-04, {+5.949741342252451e-02}},
+       Descriptor{4, 2.742173179580797e-03, {+4.716249531173324e-01, +8.152882860873994e-01}},
+       Descriptor{4, 2.814324102526247e-03, {+4.084460043805324e-01, +5.308959921730525e-01}},
+       Descriptor{4, 3.864759174126348e-03, {+2.544280410411358e-01, +7.145164808360277e-01}},
+       Descriptor{4, 3.774538054936480e-03, {+4.774877095488475e-01, +3.302023087843083e-01}},
+       Descriptor{4, 2.695893315348362e-04, {+8.029263668218678e-03, +5.253966144526213e-01}},
+       Descriptor{4, 1.117541442536487e-03, {+4.968613511179721e-01, +1.678823330502635e-01}},
+       Descriptor{4, 1.168183967320753e-03, {+4.952615769053836e-01, +7.059801252317832e-01}},
+       Descriptor{4, 9.039109611475474e-04, {+3.535780971713121e-02, +3.380124871900346e-01}},
+       Descriptor{4, 3.369833201547859e-03, {+2.045970282680664e-01, -9.824414827677468e-02}},
+       Descriptor{4, 2.639764171119988e-03, {+1.406131665045265e-01, +6.840978690759630e-01}},
+       Descriptor{4, 2.599301978924752e-03, {+9.825610533777711e-02, -2.793583988322257e-01}},
+       Descriptor{4, 1.282233014437331e-03, {+5.059516645881403e-02, +5.440523359665514e-01}},
+       Descriptor{4, 1.290196339749456e-03, {+4.494558038329708e-01, +9.661789228499832e-01}},
+       Descriptor{4, 3.634034328176076e-03, {+2.188296334498393e-01, +5.161030390440184e-01}},
+       Descriptor{4, 3.865555241109362e-03, {+3.839280551265914e-01, +6.414153617240698e-01}},
+       Descriptor{4, 1.054906754865126e-03, {+5.997065917075951e-02, +8.943918441529238e-01}},
+       Descriptor{4, 1.562311721738531e-03, {+2.735494152095392e-01, +9.793184961065893e-01}},
+       Descriptor{4, 4.729101647325770e-04, {+4.907393281824849e-01, +9.886251338305795e-01}},
+       Descriptor{4, 1.740638131578173e-04, {+1.149301083025444e-02, +9.267723967676791e-01}},
+       Descriptor{4, 2.895959965319792e-03, {+4.048678509173179e-01, +9.164077711364235e-01}},
+       Descriptor{4, 5.234744278809832e-03, {+2.917111538437177e-01, -3.721772106793935e-01}},
+       Descriptor{4, 3.176639444983119e-03, {+4.459340787080981e-01, +3.782961145575000e-02}},
+       Descriptor{5, 2.716223123318332e-03, {+2.107382124681030e-01, +3.137124037930802e-02}},
+       Descriptor{5, 3.281825797738248e-03, {+2.031438036613264e-01, +1.114400380412596e-01}},
+       Descriptor{5, 3.187894698489262e-03, {+3.734168270486775e-01, +3.629012071225871e-02}},
+       Descriptor{6, 3.590341132235904e-03, {+5.331515253263366e-01, +1.335223252058013e-01, +7.802503826507077e-01}},
+       Descriptor{6, 5.184823629819776e-04, {+3.105220296426577e-03, +6.506617704521390e-02, -2.884770252636346e-01}},
+       Descriptor{6, 2.044053800301851e-03, {+7.387825606686489e-01, +9.320636776575365e-02, -2.442855072953742e-01}},
+       Descriptor{6, 2.454384702584994e-03, {+2.402209181076414e-01, +3.428169043211691e-02, +4.260690982676672e-01}},
+       Descriptor{6, 4.119406732850509e-03, {+8.161298386181808e-02, +3.087274419444517e-01, +2.420309868035705e-01}},
+       Descriptor{6, 5.762946980041836e-03, {+1.728159430622389e-01, +3.228866908185781e-01, -2.377845513467044e-01}},
+       Descriptor{6, 3.254649507017313e-03, {+2.247127800382396e-01, +1.327427633967232e-01, +4.683664775890787e-01}},
+       Descriptor{6, 1.376952861188114e-03, {+2.850103512086270e-02, +1.058138309132070e-01, -1.189934946219634e-01}},
+       Descriptor{6, 1.390144266230514e-03, {+3.896100790636586e-01, +1.097122964947396e-02, +4.812562527542947e-01}},
+       Descriptor{6, 1.973482620122281e-03, {+2.493335646225524e-01, +3.397868885786173e-01, -3.494229691424887e-02}},
+       Descriptor{6, 1.112029053786251e-03, {+7.042023600039281e-03, +3.177207979690921e-01, +2.078247495083118e-01}},
+       Descriptor{6, 1.196110133680204e-03, {+1.229469656661441e-01, +1.566757860788512e-02, +6.094751351099792e-01}},
+       Descriptor{6, 1.888982141486211e-03, {+2.259160262165568e-01, +5.215188464418734e-02, +8.360830877198899e-01}},
+       Descriptor{6, 5.659037184544154e-04, {+9.471493326637022e-03, +4.964414152642235e-02, +7.613841735998289e-01}},
+       Descriptor{6, 1.832188797223636e-03, {+5.243114241272343e-02, +1.452721772236918e-01, +4.996792895702993e-01}},
+       Descriptor{6, 8.460253945737610e-04, {+8.872202555510618e-03, +3.816302433543118e-01, +8.877001109429070e-01}},
+       Descriptor{6, 1.597513197343773e-03, {+2.592518129005248e-01, +5.551397100853696e-01, +8.790486279875863e-01}},
+       Descriptor{6, 2.678157870344808e-03, {+3.527097683072426e-01, +4.546097143409199e-02, +6.369991526576181e-01}},
+       Descriptor{6, 7.373212126545897e-04, {+1.450709676542589e-01, +1.181702543851609e-02, +8.952124765779996e-01}},
+       Descriptor{6, 1.390059413378896e-03, {+1.874143309040665e-01, +1.371981166173774e-01, +9.134247047008914e-01}},
+       Descriptor{6, 7.964285504550415e-04, {+6.814016086596048e-02, +1.498311852045099e-01, +9.751784156687211e-01}},
+       Descriptor{6, 1.460116442574025e-03, {+3.340585216441213e-01, +5.496962861346901e-02, +9.356631230196827e-01}},
+       Descriptor{6, 1.348373684024378e-03, {+6.785157976700519e-02, +1.205147270278029e-01, +7.655747942493478e-01}},
+       Descriptor{6, 3.286050878128491e-03, {+5.023136055537912e-01, +1.076311573233394e-01, +5.182173603742516e-01}},
+       Descriptor{6, 1.124720118777690e-03, {+7.819399743424233e-03, +2.556542053755758e-01, +7.166820851155357e-01}},
+       Descriptor{6, 2.127107274890790e-03, {+6.609230621965876e-01, +9.839569585073644e-02, +6.803594853699388e-01}},
+       Descriptor{6, 7.135219223491392e-04, {+1.784736485903538e-01, +1.796539994312394e-03, +2.866535225594761e-01}},
+       Descriptor{6, 3.719588400835986e-04, {+2.532227359651285e-01, +1.312175908589084e-02, +9.829888431964284e-01}},
+       Descriptor{6, 2.151829924432428e-04, {+1.443505367133109e-02, +6.867387590589011e-02, +9.834928544568246e-01}},
+       Descriptor{6, 8.163226512583331e-04, {+2.794904874495157e-01, +1.274003485132245e-01, +9.907920620663775e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  default: {
+    throw NormalError("Gauss quadrature formulae handle degrees up to " +
+                      std::to_string(PrismGaussQuadrature::max_degree) + "on prisms");
+  }
+  }
+}
diff --git a/src/analysis/PrismGaussQuadrature.hpp b/src/analysis/PrismGaussQuadrature.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2750d76c29894fd960b89afbfd29ec908c54d03b
--- /dev/null
+++ b/src/analysis/PrismGaussQuadrature.hpp
@@ -0,0 +1,38 @@
+#ifndef PRISM_GAUSS_QUADRATURE_HPP
+#define PRISM_GAUSS_QUADRATURE_HPP
+
+#include <analysis/QuadratureFormula.hpp>
+
+/**
+ * Defines Gauss quadrature on the reference prism element
+ *
+ * \note formulae are provided by
+ *
+ * 'High-order symmetric cubature rules for tetrahedra and pyramids'
+ * Jan Jasˁkowiec & N. Sukumar (2020).
+ */
+class PrismGaussQuadrature final : public QuadratureFormula<3>
+{
+ public:
+  constexpr static size_t max_degree = 20;
+
+ private:
+  void _buildPointAndWeightLists(const size_t degree);
+
+ public:
+  PrismGaussQuadrature(PrismGaussQuadrature&&)      = default;
+  PrismGaussQuadrature(const PrismGaussQuadrature&) = default;
+
+ private:
+  friend class QuadratureManager;
+
+  explicit PrismGaussQuadrature(const size_t degree) : QuadratureFormula<3>(QuadratureType::Gauss)
+  {
+    this->_buildPointAndWeightLists(degree);
+  }
+
+  PrismGaussQuadrature()  = delete;
+  ~PrismGaussQuadrature() = default;
+};
+
+#endif   // PRISM_GAUSS_QUADRATURE_HPP
diff --git a/src/analysis/PyramidGaussQuadrature.cpp b/src/analysis/PyramidGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..869408a914c58c2402e2b9b8c7954549466a97f7
--- /dev/null
+++ b/src/analysis/PyramidGaussQuadrature.cpp
@@ -0,0 +1,976 @@
+#include <analysis/PyramidGaussQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+void
+PyramidGaussQuadrature::_buildPointAndWeightLists(const size_t degree)
+{
+  using R3 = TinyVector<3>;
+
+  struct Descriptor
+  {
+    int id;
+    double weight;
+    std::vector<double> lambda_list;
+  };
+
+  auto fill_quadrature_points = [](auto descriptor_list, auto& point_list, auto& weight_list) {
+    Assert(point_list.size() == weight_list.size());
+
+    size_t k = 0;
+    for (size_t i = 0; i < descriptor_list.size(); ++i) {
+      const auto [id, unit_weight, value_list] = descriptor_list[i];
+
+      const double w = (4. / 3) * unit_weight;
+
+      switch (id) {
+      case 1: {
+        Assert(value_list.size() == 1);
+        const double z = value_list[0];
+
+        point_list[k]  = {0, 0, z};
+        weight_list[k] = w;
+        ++k;
+        break;
+      }
+      case 2: {
+        Assert(value_list.size() == 2);
+        const double a = value_list[0];
+        const double z = value_list[1];
+
+        point_list[k + 0] = {+a, 0, z};
+        point_list[k + 1] = {-a, 0, z};
+        point_list[k + 2] = {0, +a, z};
+        point_list[k + 3] = {0, -a, z};
+
+        for (size_t l = 0; l < 4; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 4;
+        break;
+      }
+      case 3: {
+        Assert(value_list.size() == 2);
+        const double a = value_list[0];
+        const double z = value_list[1];
+
+        point_list[k + 0] = {+a, +a, z};
+        point_list[k + 1] = {+a, -a, z};
+        point_list[k + 2] = {-a, +a, z};
+        point_list[k + 3] = {-a, -a, z};
+
+        for (size_t l = 0; l < 4; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 4;
+        break;
+      }
+      case 4: {
+        Assert(value_list.size() == 3);
+        const double a = value_list[0];
+        const double b = value_list[1];
+        const double z = value_list[2];
+
+        point_list[k + 0] = {+a, +b, z};
+        point_list[k + 1] = {+a, -b, z};
+        point_list[k + 2] = {-a, +b, z};
+        point_list[k + 3] = {-a, -b, z};
+        point_list[k + 4] = {+b, +a, z};
+        point_list[k + 5] = {-b, +a, z};
+        point_list[k + 6] = {+b, -a, z};
+        point_list[k + 7] = {-b, -a, z};
+
+        for (size_t l = 0; l < 8; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 8;
+        break;
+      }
+        // LCOV_EXCL_START
+      default: {
+        throw UnexpectedError("invalid quadrature id");
+      }
+        // LCOV_EXCL_STOP
+      }
+    }
+  };
+
+  switch (degree) {
+  case 0:
+  case 1: {
+    constexpr size_t nb_points = 1;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.000000000000000e+00, {+2.500000000000000e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 2: {
+    constexpr size_t nb_points = 5;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 2.798666789016337e-01, {+5.606322125356171e-01}},
+       Descriptor{3, 1.800333302745916e-01, {+5.269974873671749e-01, +1.292784570090256e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 3: {
+    constexpr size_t nb_points = 6;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.534506474854593e-01, {+3.032132711145601e-02}},
+       Descriptor{1, 2.613312220748051e-01, {+5.656071879789744e-01}},
+       Descriptor{3, 1.463045326099339e-01, {+5.845963663947116e-01, +1.666666666666667e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 4: {
+    constexpr size_t nb_points = 10;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 2.068834025895523e-01, {+1.251369531087465e-01}},
+       Descriptor{1, 1.137418831706419e-01, {+6.772327888861374e-01}},
+       Descriptor{2, 1.063245878893255e-01, {+6.505815563982326e-01, +3.223841495782137e-01}},
+       Descriptor{3, 6.351909067062594e-02, {+6.579669971216900e-01, +3.924828389881535e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 5: {
+    constexpr size_t nb_points = 15;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 6.773442693037113e-02, {+7.307094695547904e-01}},
+       Descriptor{1, 6.470893518150579e-02, {+6.197232858190588e-03}},
+       Descriptor{1, 1.772715490151452e-01, {+2.684458095343137e-01}},
+       Descriptor{2, 5.910777216655192e-02, {+7.534406130793294e-01, +1.250000000000000e-01}},
+       Descriptor{3, 6.537546219121122e-02, {+4.171520024257513e-01, +4.218217110028595e-01}},
+       Descriptor{3, 4.808803786048134e-02, {+6.740225164778704e-01, +6.579572180745927e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 6: {
+    constexpr size_t nb_points = 23;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.023699419233705e-01, {+1.335312170632148e-01}},
+       Descriptor{1, 2.544552509057920e-02, {+8.083918187874604e-01}},
+       Descriptor{1, 1.074435834226933e-01, {+3.784035206635531e-01}},
+       Descriptor{2, 3.715744178992644e-02, {+4.210459518278233e-01, +5.563577402280808e-01}},
+       Descriptor{2, 3.663269740345384e-02, {+8.358409250652439e-01, +9.682668434012107e-02}},
+       Descriptor{3, 7.134885171305939e-02, {+5.134178134130217e-01, +2.554780750374050e-01}},
+       Descriptor{3, 8.659461394440064e-03, {+8.719795336426682e-01, +3.348911098405843e-02}},
+       Descriptor{3, 3.738678508995950e-02, {+4.773315577677307e-01, +2.776222122928558e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 7: {
+    constexpr size_t nb_points = 31;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.005139817749384e-01, {+3.936504852592841e-01}},
+       Descriptor{1, 1.571901760701542e-02, {+8.386341427229903e-01}},
+       Descriptor{1, 2.499658963028166e-02, {+1.985131073852604e-05}},
+       Descriptor{2, 2.871093750000000e-02, {+6.172133998483676e-01, +3.333333333333333e-01}},
+       Descriptor{2, 2.669175929300292e-02, {+8.640987597877147e-01, +6.666666666666667e-02}},
+       Descriptor{3, 3.572750182264944e-02, {+5.248875603037457e-01, +2.904549108425410e-01}},
+       Descriptor{3, 2.951904528668866e-02, {+2.541968221946381e-01, +6.054783556814159e-01}},
+       Descriptor{3, 6.594160872648229e-02, {+3.540511188101694e-01, +1.293188463105600e-01}},
+       Descriptor{3, 1.333274388639106e-02, {+6.142719454511971e-01, +1.008633926811357e-04}},
+       Descriptor{3, 1.476900623172677e-02, {+8.028224862699490e-01, +8.012951317750569e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 8: {
+    constexpr size_t nb_points = 47;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 5.595524252285600e-02, {+7.395194949759915e-02}},
+       Descriptor{1, 6.694668391641566e-02, {+4.806418077857804e-01}},
+       Descriptor{1, 4.752523069833954e-03, {+8.978770012649402e-01}},
+       Descriptor{2, 1.488586682102477e-02, {+2.536785615782182e-01, +7.039949439220020e-01}},
+       Descriptor{2, 2.455698624881831e-02, {+7.102737577690728e-01, +1.599976229101533e-01}},
+       Descriptor{2, 1.608138988371910e-02, {+6.364336235983890e-01, +3.474084640816018e-01}},
+       Descriptor{2, 1.442915622061931e-02, {+6.233792819622643e-01, +1.127682420195144e-02}},
+       Descriptor{3, 2.488836268558412e-02, {+5.122596817100590e-01, +6.351022006373874e-02}},
+       Descriptor{3, 3.016542061786949e-02, {+3.796590137942965e-01, +4.628422688700697e-01}},
+       Descriptor{3, 1.825943823062004e-02, {+6.914008694052951e-01, +1.917713050993898e-01}},
+       Descriptor{3, 4.052705114084869e-03, {+8.674219079854986e-01, +1.630913438364360e-02}},
+       Descriptor{3, 5.109969513593878e-02, {+3.171232262910623e-01, +2.368198703013063e-01}},
+       Descriptor{4, 9.833683332222407e-03, {+8.937479716183564e-01, +4.052832634656467e-01, +5.005997974535450e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 9: {
+    constexpr size_t nb_points = 62;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 3.517982458221282e-03, {+9.033806033428579e-01}},
+       Descriptor{1, 4.216520928063209e-02, {+5.649644435959712e-01}},
+       Descriptor{2, 8.521043257455639e-03, {+2.199358452633800e-01, +7.485612463519468e-01}},
+       Descriptor{2, 2.087072229798968e-02, {+2.558015573793007e-01, +1.346997662955544e-01}},
+       Descriptor{2, 1.073969585101612e-02, {+8.739242228416501e-01, +5.467616134251216e-02}},
+       Descriptor{2, 2.521321637389669e-02, {+6.206970648203557e-01, +1.894626948405005e-01}},
+       Descriptor{2, 1.961258547003659e-02, {+4.808872023980156e-01, +2.248423582708249e-02}},
+       Descriptor{2, 1.643323197880765e-02, {+5.476633755989256e-01, +4.134082258927628e-01}},
+       Descriptor{3, 1.830589291063876e-02, {+3.171678212066174e-01, +5.507723363912053e-01}},
+       Descriptor{3, 1.130841811263377e-02, {+5.811421463327766e-01, +3.191414755591298e-01}},
+       Descriptor{3, 2.507245299443831e-02, {+5.230813443611307e-01, +8.935238781868592e-02}},
+       Descriptor{3, 4.419409904347655e-03, {+8.727886385585986e-01, +6.151583405729108e-02}},
+       Descriptor{3, 4.065272607298719e-02, {+2.744783026131462e-01, +3.264219851596609e-01}},
+       Descriptor{4, 1.219793270502279e-02, {+4.578740192946059e-01, +7.687455968654898e-01, +1.755724204367565e-01}},
+       Descriptor{4, 6.516970715496498e-03, {+5.294592086619503e-01, +8.651632950329239e-01, +1.570840570495663e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 10: {
+    constexpr size_t nb_points = 80;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 2.226887246752692e-03, {+9.176665489678424e-01}},
+       Descriptor{1, 1.676862119517167e-02, {+7.090093740468727e-01}},
+       Descriptor{1, 5.218524659101940e-02, {+2.271396740104223e-01}},
+       Descriptor{1, 2.912684113372536e-02, {+6.874011069480644e-02}},
+       Descriptor{2, 2.061250827144200e-02, {+6.542017017611934e-01, +1.239254499739905e-01}},
+       Descriptor{2, 1.001318317045432e-02, {+7.663392799174616e-01, +2.096299791450872e-02}},
+       Descriptor{2, 3.805894719789539e-03, {+8.042105610181555e-01, +1.657731354676645e-01}},
+       Descriptor{2, 1.603356460939568e-02, {+2.171975788362678e-01, +4.934052093395356e-01}},
+       Descriptor{2, 4.210573985824022e-03, {+2.093734735103665e-01, +7.896976713483934e-01}},
+       Descriptor{2, 8.129732309410973e-03, {+5.283070905328926e-01, +4.716928890485879e-01}},
+       Descriptor{3, 1.345646795638387e-02, {+3.468149019049753e-01, +2.011394567580291e-02}},
+       Descriptor{3, 1.016376299548378e-02, {+5.423477423323926e-01, +3.682210199892779e-01}},
+       Descriptor{3, 2.981645762492043e-02, {+4.296576954455671e-01, +1.266159486770112e-01}},
+       Descriptor{3, 8.657127834237414e-03, {+7.187141460172495e-01, +2.433819982878694e-02}},
+       Descriptor{3, 9.531999164931240e-03, {+7.624400857874878e-01, +1.270144250256904e-01}},
+       Descriptor{3, 7.139582674758052e-04, {+9.642735008631930e-01, +1.067692573406055e-02}},
+       Descriptor{3, 1.519293397619959e-02, {+2.789738869874377e-01, +5.970429731303039e-01}},
+       Descriptor{4, 1.008533639437561e-02, {+7.312452983523516e-01, +3.711506789000326e-01, +2.175616631549504e-01}},
+       Descriptor{4, 5.859338229960730e-03, {+9.219480705797174e-01, +4.196652283339353e-01, +4.137857207280887e-02}},
+       Descriptor{4, 2.134779341185569e-02, {+1.995426151915722e-01, +4.434009744724249e-01, +3.261536572399062e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 11: {
+    constexpr size_t nb_points = 103;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.286363653695535e-02, {+6.929563964750883e-01}},
+       Descriptor{1, 2.595721225427735e-03, {+9.110152344627592e-01}},
+       Descriptor{1, 8.176385700475526e-03, {+2.503292907254847e-01}},
+       Descriptor{2, 5.119414398042891e-03, {+3.095914411121506e-01, +5.040798390564714e-02}},
+       Descriptor{2, 6.846996666462350e-03, {+7.699392208102979e-01, +1.178348626907169e-02}},
+       Descriptor{2, 2.021405278036776e-02, {+7.002703811744613e-01, +1.010394090255615e-01}},
+       Descriptor{2, 1.240017518258633e-02, {+4.609503542907282e-01, +3.427275760580460e-01}},
+       Descriptor{2, 1.050060859922616e-02, {+2.992893821695951e-01, +6.286989821677651e-01}},
+       Descriptor{2, 1.207523991498537e-02, {+5.911494375715570e-01, +2.582284852308509e-01}},
+       Descriptor{2, 7.945494399371682e-03, {+1.861308147704910e-02, +4.358344954832800e-01}},
+       Descriptor{2, 8.979308019639792e-03, {+1.603429097546466e-01, +2.192831941429789e-01}},
+       Descriptor{3, 4.328610077172669e-03, {+1.445075355444747e-01, +7.879743305346877e-01}},
+       Descriptor{3, 9.591505938503333e-03, {+5.936945097967551e-01, +3.089295886910713e-01}},
+       Descriptor{3, 8.458896075424903e-03, {+2.749014180295009e-01, +1.781185684646454e-02}},
+       Descriptor{3, 2.093917618186587e-02, {+2.968007724724139e-01, +1.112703360898260e-01}},
+       Descriptor{3, 1.007335618421271e-02, {+5.984926370656903e-01, +2.444716310077481e-02}},
+       Descriptor{3, 1.474935950898293e-02, {+6.103141530732866e-01, +1.204187551930976e-01}},
+       Descriptor{3, 4.054320200259096e-03, {+3.734502308185636e-01, +5.775253255524150e-01}},
+       Descriptor{3, 1.768012056788626e-02, {+2.624599146327740e-01, +4.916980190881633e-01}},
+       Descriptor{3, 2.349367280688882e-02, {+3.686063571979057e-01, +2.631132571277226e-01}},
+       Descriptor{3, 3.003553459527201e-03, {+8.702629344236114e-01, +1.908681361607608e-02}},
+       Descriptor{3, 4.330579718905655e-03, {+8.102574500242316e-01, +1.169261992676211e-01}},
+       Descriptor{4, 7.882977643914898e-03, {+7.896765746747297e-01, +3.552721383504759e-01, +1.796809539017722e-01}},
+       Descriptor{4, 5.094911855326565e-03, {+9.280503494413060e-01, +4.148421023475579e-01, +3.535559961728909e-02}},
+       Descriptor{4, 6.675422227745319e-03, {+2.331378908359926e-01, +5.562877919132911e-01, +4.146440202422456e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 12: {
+    constexpr size_t nb_points = 127;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 2.715854620871203e-02, {+4.570070042407191e-01}},
+       Descriptor{1, 4.353142424489953e-04, {+9.624848010480895e-01}},
+       Descriptor{1, 1.391222140872406e-02, {+2.656531684537741e-02}},
+       Descriptor{2, 3.872881466492529e-03, {+6.175240822879710e-01, +5.374720825773919e-03}},
+       Descriptor{2, 2.179679715936184e-03, {+9.538341781347592e-01, +3.942123626649530e-02}},
+       Descriptor{2, 1.267133485574918e-02, {+5.950300580138582e-01, +7.619184421748394e-02}},
+       Descriptor{2, 8.734641630353807e-03, {+4.353935982023290e-01, +5.246187553362018e-01}},
+       Descriptor{2, 2.891467316158506e-03, {+1.329135556120804e-01, +8.387199403114260e-01}},
+       Descriptor{2, 3.366868485115227e-03, {+7.971436348244696e-01, +2.007518977789794e-01}},
+       Descriptor{2, 1.476038031344119e-03, {+3.107829504939940e-01, +6.826546910531470e-01}},
+       Descriptor{2, 1.998444773599242e-02, {+4.689689670336921e-01, +3.171338246752144e-01}},
+       Descriptor{2, 6.189271706444372e-03, {+6.329314929065794e-02, +1.270962903467187e-01}},
+       Descriptor{3, 8.200787054788633e-03, {+1.169412447366524e-01, +6.712417429492287e-01}},
+       Descriptor{3, 5.096515464581623e-03, {+4.771085065521420e-01, +4.586280271864225e-01}},
+       Descriptor{3, 1.739298850473944e-02, {+3.479673540088846e-01, +1.193889061370197e-01}},
+       Descriptor{3, 1.948797782027694e-02, {+2.174809398989127e-01, +2.574178486652196e-01}},
+       Descriptor{3, 5.388962434670257e-03, {+6.742620754102585e-01, +2.260502558938976e-02}},
+       Descriptor{3, 6.769117314513475e-03, {+6.541531257781691e-01, +8.746307755558261e-02}},
+       Descriptor{3, 4.675876093665083e-03, {+2.604386899595714e-01, +6.714016412797261e-01}},
+       Descriptor{3, 1.838172037009153e-02, {+2.744210786837723e-01, +4.760425504887897e-01}},
+       Descriptor{3, 1.407964506457295e-02, {+5.125902273710998e-01, +2.277795581554027e-01}},
+       Descriptor{3, 2.833700634607033e-03, {+8.687887353904908e-01, +5.850272176027390e-02}},
+       Descriptor{3, 4.791372228177735e-03, {+7.118168180030836e-01, +2.205399573194206e-01}},
+       Descriptor{3, 9.906806338191463e-03, {+3.675456953238826e-01, +2.548724290196442e-02}},
+       Descriptor{4, 5.355743630233014e-03, {+8.489693748245684e-01, +5.120364631720311e-01, +1.114583653780480e-01}},
+       Descriptor{4, 8.168185107182221e-04, {+9.738540313599703e-01, +7.467034094055778e-01, +3.392208246799127e-03}},
+       Descriptor{4, 8.541753721892767e-03, {+3.230240551801264e-01, +6.255561325380413e-01, +3.272515164405167e-01}},
+       Descriptor{4, 1.018769584471249e-02, {+1.945007634528372e-01, +6.980595936427326e-01, +1.480516686189832e-01}},
+       Descriptor{4, 5.723677926726618e-03, {+3.289488538718914e-01, +8.538315289662523e-01, +2.487563023274973e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 13: {
+    constexpr size_t nb_points = 152;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.203669565577547e-03, {+9.297400586425600e-01}},
+       Descriptor{1, 1.890734566258640e-02, {+5.838703604447901e-01}},
+       Descriptor{1, 2.412883548353303e-02, {+1.094245815067315e-01}},
+       Descriptor{1, 1.430958560741041e-02, {+3.004359812964663e-01}},
+       Descriptor{2, 1.462233467606358e-02, {+4.122389208832898e-01, +4.035176514416959e-01}},
+       Descriptor{2, 1.310471335067461e-03, {+9.448240879795888e-01, +5.541396470307911e-03}},
+       Descriptor{2, 1.315348594597115e-03, {+1.501230655029512e-01, +8.497929440328683e-01}},
+       Descriptor{2, 6.041729387793333e-03, {+3.633828357756222e-01, +6.071950436217343e-01}},
+       Descriptor{2, 8.289343691958597e-03, {+3.289712237345070e-01, +2.033792783786767e-02}},
+       Descriptor{2, 1.293341373858936e-02, {+5.570493589160862e-01, +1.060119987373025e-01}},
+       Descriptor{2, 5.638854191543832e-03, {+7.106035443742125e-01, +2.491041991532026e-01}},
+       Descriptor{2, 4.504991398813604e-03, {+8.853174870868126e-01, +8.107956240970675e-02}},
+       Descriptor{3, 3.636184206108946e-03, {+4.888475450384162e-01, +9.508151681060791e-03}},
+       Descriptor{3, 3.888421168021828e-03, {+4.503177245667275e-01, +4.881936050921409e-01}},
+       Descriptor{3, 4.023653208263601e-03, {+6.587295691554786e-01, +2.769083115660864e-01}},
+       Descriptor{3, 9.559019492448712e-03, {+6.258827191179884e-01, +1.224809382042812e-01}},
+       Descriptor{3, 1.938656716858041e-02, {+2.517896003319362e-01, +2.232305727986451e-01}},
+       Descriptor{3, 9.687548837381962e-04, {+9.133819128044913e-01, +1.207165002118426e-02}},
+       Descriptor{3, 5.022143502861996e-03, {+1.111786164172054e-01, +7.551872471669051e-01}},
+       Descriptor{3, 1.448769412031482e-02, {+3.781492498737534e-01, +8.424825204838618e-02}},
+       Descriptor{3, 4.599764313830424e-03, {+2.314619569157776e-01, +6.318857723177680e-01}},
+       Descriptor{3, 1.107636061349879e-02, {+1.859866728257666e-01, +3.989297765923411e-01}},
+       Descriptor{3, 4.692602004327276e-03, {+7.294149054289296e-01, +2.502635713295603e-02}},
+       Descriptor{3, 2.851949824498801e-03, {+8.041924177792682e-01, +1.203800076513554e-01}},
+       Descriptor{3, 1.323033835935081e-02, {+4.703608283841747e-01, +2.927035935703978e-01}},
+       Descriptor{3, 1.119723439728825e-03, {+2.753565752001155e-01, +7.074274892155881e-01}},
+       Descriptor{3, 9.182464621381196e-03, {+2.584044366257879e-01, +5.283274650941000e-01}},
+       Descriptor{4, 4.564906537010502e-03, {+7.745630168641912e-01, +4.397893897194206e-01, +1.953840901290468e-01}},
+       Descriptor{4, 5.475034834428800e-03, {+7.201414655134084e-01, +2.217479044308266e-01, +2.260035188498128e-02}},
+       Descriptor{4, 9.922612076457428e-03, {+1.750984955638136e-01, +5.856079751486080e-01, +2.160852053363709e-01}},
+       Descriptor{4, 1.103829121564573e-03, {+9.376254731805550e-01, +7.398267532513132e-01, +6.232437306564607e-02}},
+       Descriptor{4, 2.362282939586705e-03, {+9.258882242137035e-01, +5.000209314552113e-01, +1.929314718254384e-02}},
+       Descriptor{4, 6.306665849348717e-03, {+2.682073921041632e-01, +5.618519868919964e-01, +4.010246655048774e-01}},
+       Descriptor{4, 6.754925131024099e-03, {+3.620795943973790e-01, +7.924562807098640e-01, +9.200529105614789e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 14: {
+    constexpr size_t nb_points = 184;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 6.009828410914801e-04, {+9.455096078380157e-01}},
+       Descriptor{1, 1.255487583666288e-02, {+6.138243564839928e-01}},
+       Descriptor{1, 2.319377258130698e-02, {+2.454049718059621e-01}},
+       Descriptor{1, 1.905556551345681e-02, {+4.183282539085985e-01}},
+       Descriptor{2, 1.191795956205729e-02, {+4.218261367348597e-01, +4.017536316056771e-01}},
+       Descriptor{2, 3.867184412332806e-03, {+8.465613435886886e-02, +8.958821481037450e-02}},
+       Descriptor{2, 1.923763095174742e-03, {+9.516713196592316e-01, +3.128290548598175e-02}},
+       Descriptor{2, 1.234179804493060e-03, {+1.221288178114563e-01, +8.656263522240111e-01}},
+       Descriptor{2, 4.716648039115899e-03, {+3.459755786873036e-01, +5.615491563602975e-01}},
+       Descriptor{2, 5.134044973384534e-03, {+3.250020921247040e-01, +1.398939848661482e-02}},
+       Descriptor{2, 1.154564746408180e-03, {+8.527465617518474e-01, +1.655718296284047e-03}},
+       Descriptor{2, 1.181264118995304e-02, {+6.382775647715264e-01, +6.493816225859560e-02}},
+       Descriptor{2, 2.436937834631011e-03, {+6.723114741458501e-01, +3.226524499585241e-01}},
+       Descriptor{2, 2.442180393951459e-03, {+8.537496059510902e-01, +1.428460235322913e-01}},
+       Descriptor{2, 2.760272065188771e-03, {+3.235182852814910e-01, +6.650982181901243e-01}},
+       Descriptor{2, 1.491875937488001e-02, {+4.969751483237657e-01, +1.927846766563645e-01}},
+       Descriptor{3, 8.607885480520022e-03, {+5.041706295584495e-01, +5.324772752904031e-02}},
+       Descriptor{3, 9.201054026224215e-04, {+6.385320288130391e-01, +3.499922479913266e-01}},
+       Descriptor{3, 3.506580713358855e-03, {+7.643224482486065e-01, +1.609611231580418e-01}},
+       Descriptor{3, 1.184517921997258e-02, {+5.613330619383329e-01, +1.603998892409423e-01}},
+       Descriptor{3, 1.517200950119656e-02, {+2.878680539515507e-01, +1.373276390573706e-01}},
+       Descriptor{3, 7.988570964665479e-04, {+9.016308382757633e-01, +1.032358209500777e-02}},
+       Descriptor{3, 3.465322056721376e-03, {+9.459747575878658e-02, +7.677917941640774e-01}},
+       Descriptor{3, 5.649017528916505e-03, {+2.627574405731676e-01, +4.943554173327801e-02}},
+       Descriptor{3, 3.144992576543379e-03, {+3.864091536381419e-01, +5.560266141216673e-01}},
+       Descriptor{3, 1.042467052306092e-02, {+2.282300577985542e-01, +4.962791997555249e-01}},
+       Descriptor{3, 1.353353344199375e-03, {+7.057872269283543e-01, +2.008807154101613e-03}},
+       Descriptor{3, 6.863946049178268e-04, {+9.147875524427646e-01, +4.956231677804342e-02}},
+       Descriptor{3, 8.488466463152199e-03, {+5.088306451368273e-01, +3.463558696873174e-01}},
+       Descriptor{3, 1.389764713997917e-03, {+2.183390551054974e-01, +7.454288363105724e-01}},
+       Descriptor{3, 5.646871721661347e-03, {+2.052421004307298e-01, +6.415316471289261e-01}},
+       Descriptor{3, 1.822286920356053e-02, {+2.910901390165372e-01, +3.062273833527194e-01}},
+       Descriptor{3, 4.755535844463758e-03, {+7.486540248046429e-01, +4.936880562940795e-02}},
+       Descriptor{4, 3.308393786732543e-03, {+7.378246466058306e-01, +4.928495648091606e-01, +2.369940069990296e-01}},
+       Descriptor{4, 4.070045103160022e-03, {+8.589060620098202e-01, +3.738196822630493e-01, +2.776225858911670e-02}},
+       Descriptor{4, 8.268489304502566e-03, {+2.243615064922758e-01, +6.317949456704877e-01, +2.592830550287851e-01}},
+       Descriptor{4, 2.221378811027735e-03, {+9.001757758936474e-01, +6.261867885017652e-01, +7.806621986847780e-02}},
+       Descriptor{4, 5.341491722009947e-04, {+9.789501849840820e-01, +6.256578399323836e-01, +4.430014519257858e-03}},
+       Descriptor{4, 4.879462347378159e-03, {+2.560758373363789e-01, +5.119481066576588e-01, +4.542857276885983e-01}},
+       Descriptor{4, 7.897297568864630e-03, {+2.974176206871381e-01, +7.755395865083232e-01, +1.176110467333258e-01}},
+       Descriptor{4, 2.696628566117115e-03, {+2.907965956609688e-01, +6.192615227882655e-01, +7.834837395674860e-03}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 15: {
+    constexpr size_t nb_points = 234;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 4.253301554860054e-04, {+9.516784060639266e-01}},
+       Descriptor{1, 1.525748792328103e-02, {+2.989009214424757e-01}},
+       Descriptor{2, 5.280176625857056e-03, {+7.001403574239718e-01, +1.037252232620472e-01}},
+       Descriptor{2, 2.446394589904036e-03, {+9.193631484833984e-01, +6.052669044962753e-02}},
+       Descriptor{2, 9.737060312972353e-04, {+1.101334889442361e-01, +8.776707213450818e-01}},
+       Descriptor{2, 7.776124953823158e-03, {+4.465008567015498e-01, +3.306143190884451e-01}},
+       Descriptor{2, 2.676970927679192e-03, {+8.020538575801487e-01, +1.891305716440682e-01}},
+       Descriptor{2, 6.190812842666219e-03, {+5.301512452732464e-01, +1.554979319555147e-01}},
+       Descriptor{2, 6.561865616728806e-04, {+9.732273442892753e-01, +4.905110329399738e-03}},
+       Descriptor{2, 6.569940566182691e-03, {+5.055718663406419e-01, +6.238803484862593e-02}},
+       Descriptor{2, 2.673087432145158e-03, {+4.618938260051262e-02, +5.328414641791853e-01}},
+       Descriptor{2, 1.759495302160485e-03, {+6.230136406382800e-01, +3.769559380708412e-01}},
+       Descriptor{2, 3.312857608561831e-03, {+2.950931045827234e-01, +6.814825140552475e-01}},
+       Descriptor{2, 7.510345065720940e-03, {+4.776005857611240e-01, +2.309753566333424e-01}},
+       Descriptor{2, 8.931665290893454e-03, {+3.689801580562112e-01, +5.033566690504305e-01}},
+       Descriptor{3, 5.568418259966719e-03, {+3.723782904830595e-01, +4.235367529321636e-01}},
+       Descriptor{3, 2.649457392781329e-03, {+5.341902002288372e-01, +4.129667979379346e-01}},
+       Descriptor{3, 1.970289990554734e-03, {+6.063808134784291e-02, +7.864057957899899e-01}},
+       Descriptor{3, 5.134839039660163e-03, {+4.777068770658753e-01, +3.265377521300765e-01}},
+       Descriptor{3, 7.421925481559295e-03, {+1.987543120573730e-01, +5.291423306214975e-02}},
+       Descriptor{3, 1.644857216298594e-03, {+7.599695611037520e-01, +6.930996189193400e-03}},
+       Descriptor{3, 5.534536939100995e-03, {+5.055458420266429e-01, +1.799129130112892e-02}},
+       Descriptor{3, 3.256281564205464e-03, {+2.206482423626575e-01, +8.304773708384203e-03}},
+       Descriptor{3, 1.515060824261142e-03, {+3.440503418178477e-01, +6.178873991598769e-01}},
+       Descriptor{3, 5.911265567277567e-03, {+1.962412964100753e-01, +1.926549836686368e-01}},
+       Descriptor{3, 8.392379781416604e-04, {+9.194790158373803e-01, +1.617257265834036e-02}},
+       Descriptor{3, 4.022404010252260e-03, {+2.620925839988805e-01, +5.954881060323367e-01}},
+       Descriptor{3, 2.231695755198927e-03, {+1.744887942814471e-01, +7.673520458998934e-01}},
+       Descriptor{3, 6.156882008161937e-03, {+1.325632428580384e-01, +6.445714883912202e-01}},
+       Descriptor{3, 8.642320589201948e-03, {+3.077710707446309e-01, +2.682338927247724e-01}},
+       Descriptor{3, 7.452703387505539e-03, {+4.543788145456805e-01, +1.924966376990235e-01}},
+       Descriptor{3, 5.028803835070171e-03, {+1.820389321231959e-01, +3.545120556149344e-01}},
+       Descriptor{3, 8.796385122255454e-03, {+1.880443006828807e-01, +1.378439700992989e-01}},
+       Descriptor{3, 1.747812460887048e-03, {+8.528586603669460e-01, +8.819642588395045e-02}},
+       Descriptor{3, 9.189893838229260e-03, {+1.939535843143651e-01, +4.412634170405248e-01}},
+       Descriptor{3, 6.208073919367888e-03, {+6.362056944924487e-01, +1.812670653663255e-01}},
+       Descriptor{3, 5.683980552459499e-03, {+7.160962950323663e-01, +6.273138420630826e-02}},
+       Descriptor{3, 1.836585716232118e-03, {+7.297058862326863e-01, +2.223880047949926e-01}},
+       Descriptor{4, 2.218692647777552e-03, {+8.582850877054248e-01, +5.768403835499213e-01, +1.231132989726892e-01}},
+       Descriptor{4, 2.609887405864026e-03, {+7.633676619034022e-01, +2.116780932527140e-01, +2.653658688975024e-02}},
+       Descriptor{4, 6.168756108593601e-03, {+2.161303509635070e-01, +5.839223522583539e-01, +3.294970252944507e-01}},
+       Descriptor{4, 1.229957612267995e-03, {+9.561584299674409e-01, +6.424182794168720e-01, +2.375108271645151e-02}},
+       Descriptor{4, 1.954938784233806e-03, {+8.759221925259401e-01, +3.779756583128506e-01, +1.156156746353602e-02}},
+       Descriptor{4, 3.467639337694073e-03, {+2.445394062188940e-01, +4.671941605403201e-01, +5.047812448827769e-01}},
+       Descriptor{4, 6.770266043344008e-03, {+3.706058970009432e-01, +5.485269061480423e-01, +9.182493970820699e-02}},
+       Descriptor{4, 1.678294163556885e-03, {+6.156798284057371e-01, +7.140171587890908e-03, +1.061641469954417e-02}},
+       Descriptor{4, 7.130797590166880e-03, {+7.101327396551219e-01, +2.791894198719184e-01, +1.869924830554508e-01}},
+       Descriptor{4, 4.675472898850534e-03, {+8.258711059677443e-01, +3.568302621331758e-01, +7.551964039133613e-02}},
+       Descriptor{4, 2.534207524207739e-03, {+4.755766339064903e-01, +6.928649578239733e-01, +2.863100113252361e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 16: {
+    constexpr size_t nb_points = 285;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 2.767212020768290e-04, {+9.586035463620837e-01}},
+       Descriptor{2, 7.349442577079450e-03, {+5.187038802297479e-01, +1.406555941365744e-01}},
+       Descriptor{2, 1.487404370728813e-03, {+9.153129245676680e-01, +4.316491102619298e-02}},
+       Descriptor{2, 3.876936384211876e-03, {+1.655472656731889e-02, +3.952283264601336e-01}},
+       Descriptor{2, 1.030614824039442e-02, {+3.691213557507810e-01, +2.989012556159393e-01}},
+       Descriptor{2, 2.726731988438958e-03, {+2.060330211667108e-02, +6.870461262955434e-02}},
+       Descriptor{2, 2.446292134085587e-03, {+8.719419371010426e-01, +1.119349596786030e-01}},
+       Descriptor{2, 5.549243997617444e-03, {+3.153842078793646e-01, +5.666543504043285e-01}},
+       Descriptor{2, 5.022624174664197e-03, {+6.139955556315307e-01, +1.595773278635465e-01}},
+       Descriptor{2, 6.711117008479383e-04, {+9.765007634893953e-02, +8.922021492300448e-01}},
+       Descriptor{2, 3.168735186737549e-03, {+7.317646448078422e-01, +2.475499337151233e-01}},
+       Descriptor{2, 2.307072686856537e-03, {+8.405422094352291e-01, +1.014565417563986e-02}},
+       Descriptor{2, 2.233554340355750e-03, {+5.557460950562904e-01, +4.288551242077440e-01}},
+       Descriptor{2, 2.286856931321262e-03, {+2.643802171735182e-01, +7.164979649536070e-01}},
+       Descriptor{3, 5.838886999649284e-03, {+1.375503146864574e-01, +1.787752340257923e-01}},
+       Descriptor{3, 4.120387561185287e-03, {+5.559992997537619e-01, +2.929550108496573e-01}},
+       Descriptor{3, 1.730379481687403e-03, {+6.803760252907728e-01, +2.744752357294187e-01}},
+       Descriptor{3, 4.106809066021676e-03, {+6.898707466220132e-01, +1.571412723940531e-01}},
+       Descriptor{3, 6.354389480672592e-03, {+4.665601661638106e-01, +2.503592476644397e-01}},
+       Descriptor{3, 4.326210184433355e-04, {+9.365742707871645e-01, +2.983913639912041e-02}},
+       Descriptor{3, 3.452632929818020e-03, {+1.066846406851088e-01, +7.018965704311338e-01}},
+       Descriptor{3, 4.240714557365053e-03, {+5.636286631903044e-01, +5.534643995880605e-02}},
+       Descriptor{3, 3.405266316789942e-03, {+8.721470315267314e-02, +5.880318078594904e-01}},
+       Descriptor{3, 7.009429293023371e-03, {+2.882648291123767e-01, +3.576004004129416e-01}},
+       Descriptor{3, 3.711845099382991e-03, {+2.350324970540493e-01, +6.380849983494681e-01}},
+       Descriptor{3, 6.557699894218194e-03, {+2.450858218595137e-01, +1.118004676798527e-01}},
+       Descriptor{3, 1.699962978331957e-03, {+5.003064978621564e-01, +4.579960420164317e-01}},
+       Descriptor{3, 1.347400048357294e-03, {+5.973130190403576e-02, +8.160093965927975e-01}},
+       Descriptor{3, 3.633969504612614e-03, {+3.628596393971073e-01, +5.597294995869231e-02}},
+       Descriptor{3, 1.216194629008639e-03, {+8.316301192301562e-01, +1.257310660131593e-01}},
+       Descriptor{3, 5.495797083167567e-03, {+5.981515120789380e-01, +1.068019913435455e-01}},
+       Descriptor{3, 9.733027356279389e-04, {+3.245246862338501e-01, +6.465862007088805e-01}},
+       Descriptor{3, 7.253930032328553e-03, {+1.840043782869460e-01, +2.540941713633634e-01}},
+       Descriptor{3, 2.616000670590770e-03, {+8.068558507667803e-01, +5.465918013108818e-02}},
+       Descriptor{3, 5.483336923370839e-03, {+3.903817283942642e-01, +4.534593946231034e-01}},
+       Descriptor{3, 1.501870645161511e-03, {+1.620565672046460e-01, +7.891766078049036e-01}},
+       Descriptor{4, 2.242112763518706e-03, {+7.955779101854147e-01, +5.265131942175433e-01, +1.868978854107283e-01}},
+       Descriptor{4, 2.543570121402526e-03, {+8.200116575745190e-01, +5.876389794788515e-01, +1.494308906905272e-02}},
+       Descriptor{4, 5.361444814068662e-03, {+4.810677594905461e-01, +1.491370161533038e-01, +3.977024606563388e-01}},
+       Descriptor{4, 1.505662384876206e-03, {+9.169801723597639e-01, +6.303394356928483e-01, +6.620609897561705e-02}},
+       Descriptor{4, 1.126519980001580e-03, {+9.578199602162939e-01, +3.327783186859149e-01, +1.432824105432797e-02}},
+       Descriptor{4, 2.836972153671841e-03, {+2.122679281909628e-01, +4.214367174540187e-01, +5.509963820368152e-01}},
+       Descriptor{4, 4.234433041605569e-03, {+2.485957380035636e-01, +6.262321767993255e-01, +2.800030049831467e-01}},
+       Descriptor{4, 3.184147857413263e-03, {+5.534790138573062e-01, +1.881580592010290e-01, +2.436330465584662e-01}},
+       Descriptor{4, 4.029779987731148e-03, {+8.242282785245648e-01, +3.637620573617559e-01, +7.506931441576017e-02}},
+       Descriptor{4, 3.669736816875404e-03, {+1.460654272875972e-01, +4.426831022376114e-01, +5.795059845476017e-02}},
+       Descriptor{4, 2.488887287575317e-03, {+3.881072073502287e-01, +6.235512572087863e-01, +3.563115863132650e-01}},
+       Descriptor{4, 4.888892260572901e-04, {+9.567448428329013e-01, +7.984451750366893e-01, +5.975255115121650e-03}},
+       Descriptor{4, 3.252645155099476e-03, {+6.098484948586518e-01, +3.120515076281816e-01, +1.006650964023973e-02}},
+       Descriptor{4, 4.955059091457157e-03, {+2.484742721540007e-01, +1.340653178754635e-01, +4.761968373134181e-01}},
+       Descriptor{4, 4.570242180251667e-03, {+6.991894353535666e-01, +1.776787645087477e-01, +5.836635447459246e-02}},
+       Descriptor{4, 4.810617642413540e-03, {+3.601541528165914e-01, +4.301177602083764e-01, +1.649556291845448e-01}},
+       Descriptor{4, 5.387917947217286e-03, {+7.366874205051257e-01, +3.029873517367453e-01, +1.596398472135355e-01}},
+       Descriptor{4, 2.469280567426453e-03, {+3.142480630211401e-01, +2.522065148266495e-02, +1.331460692424767e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 17: {
+    constexpr size_t nb_points = 319;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.221682107890185e-04, {+9.722032338703692e-01}},
+       Descriptor{1, 5.639436295815340e-03, {+6.387035292668141e-01}},
+       Descriptor{1, 1.871168380432278e-03, {+6.802004320805534e-01}},
+       Descriptor{2, 8.790055406139772e-04, {+9.524140858124460e-01, +4.194980824924494e-02}},
+       Descriptor{2, 6.397368832390104e-04, {+8.275692125030436e-02, +9.014118320415641e-01}},
+       Descriptor{2, 1.919424779584283e-03, {+2.657854504312773e-01, +6.843252932276896e-01}},
+       Descriptor{2, 1.088568001837918e-03, {+8.115594002847951e-01, +8.245324867689100e-08}},
+       Descriptor{2, 5.791783484828203e-03, {+3.523279995401680e-01, +5.541739087364920e-01}},
+       Descriptor{2, 3.335679832473916e-03, {+6.030377121897242e-01, +3.625880598876809e-01}},
+       Descriptor{2, 4.839934014148432e-03, {+7.840260420215905e-01, +1.387570850346267e-01}},
+       Descriptor{2, 1.171223275540323e-02, {+4.934647219864639e-01, +2.426016748645620e-01}},
+       Descriptor{2, 7.818032837412321e-03, {+3.503875164890664e-01, +4.013077580688794e-01}},
+       Descriptor{2, 3.163898916366554e-03, {+3.364043121074268e-03, +3.815297326937233e-01}},
+       Descriptor{2, 8.136278822739700e-03, {+4.307451267906745e-01, +1.190145691544862e-01}},
+       Descriptor{2, 4.719826157526003e-03, {+5.925480128294769e-01, +4.928012549325394e-02}},
+       Descriptor{2, 9.623713493802133e-04, {+2.255068603658533e-01, +7.620639151935824e-01}},
+       Descriptor{3, 1.623787740135037e-03, {+6.397586374033618e-01, +3.115346949712161e-01}},
+       Descriptor{3, 8.713459275997466e-03, {+1.646347487963126e-01, +2.565099172621796e-01}},
+       Descriptor{3, 4.120086184663263e-03, {+4.930356331962961e-01, +7.449733603189460e-02}},
+       Descriptor{3, 5.220647228935927e-04, {+7.479128039630975e-01, +1.307885480845669e-03}},
+       Descriptor{3, 3.897164563433518e-03, {+2.625131786428597e-01, +5.285858021846196e-01}},
+       Descriptor{3, 2.229921875344444e-03, {+1.748421558986807e-01, +9.552210724735053e-03}},
+       Descriptor{3, 9.418067622583964e-03, {+2.760246878567057e-01, +3.414298990596975e-01}},
+       Descriptor{3, 6.615832230310311e-04, {+4.351255240898762e-01, +2.702292596780164e-04}},
+       Descriptor{3, 1.073942218989596e-04, {+9.530769415773807e-01, +4.692242455320396e-02}},
+       Descriptor{3, 3.031478525882112e-03, {+4.842559790519491e-01, +3.444971328350538e-01}},
+       Descriptor{3, 8.193264106692307e-04, {+2.938106184332757e-01, +6.681192301420841e-01}},
+       Descriptor{3, 4.651214490706081e-03, {+1.605720050369202e-01, +6.739128715712450e-01}},
+       Descriptor{3, 1.676067168115160e-03, {+8.101452921420736e-01, +3.437584591482228e-02}},
+       Descriptor{3, 8.257689574656562e-03, {+1.800932575491559e-01, +1.554287257829705e-01}},
+       Descriptor{3, 3.975458595443641e-03, {+5.013679940745993e-01, +2.697182873385593e-01}},
+       Descriptor{3, 5.172252054798609e-04, {+9.228440785543659e-01, +1.181428825288093e-02}},
+       Descriptor{3, 6.241363480381797e-03, {+3.207724957060760e-01, +6.366317285532444e-02}},
+       Descriptor{3, 2.499541224162367e-03, {+2.145401504974506e-02, +6.762724457801919e-02}},
+       Descriptor{3, 3.441345824053468e-03, {+6.529307874193767e-01, +2.014966460540852e-01}},
+       Descriptor{3, 3.730857641897916e-03, {+3.810920673681604e-01, +4.858674466671989e-01}},
+       Descriptor{3, 9.853795017806486e-03, {+4.141312471403079e-01, +1.907105246425098e-01}},
+       Descriptor{3, 7.367579520284493e-03, {+1.431188794593187e-01, +5.103737585320696e-01}},
+       Descriptor{3, 1.780487305606260e-03, {+6.816745760360433e-02, +8.035124551324651e-01}},
+       Descriptor{3, 2.423131685196213e-03, {+2.297724242201254e-01, +4.121355156423638e-02}},
+       Descriptor{3, 1.057340089483043e-03, {+7.821120027007296e-01, +1.776001617612250e-01}},
+       Descriptor{3, 1.450363434917984e-03, {+4.764642149060935e-01, +4.843901243932818e-01}},
+       Descriptor{3, 2.710579935745260e-03, {+6.655005676200287e-01, +4.796979480806327e-02}},
+       Descriptor{3, 1.120145668844513e-03, {+1.575537160038546e-01, +8.010812345834409e-01}},
+       Descriptor{4, 1.209171043596973e-03, {+7.476025549089913e-01, +1.335733555833585e-01, +2.410142270350212e-01}},
+       Descriptor{4, 1.624713950736176e-03, {+6.402449723756475e-01, +3.697899872598127e-01, +1.069891762813818e-02}},
+       Descriptor{4, 6.208589669146640e-03, {+2.772500272074544e-01, +6.588035927979838e-01, +2.398863138338967e-01}},
+       Descriptor{4, 1.357245778023027e-03, {+8.843596956349281e-01, +7.690144551026831e-01, +8.514410320936129e-02}},
+       Descriptor{4, 7.768823018224295e-04, {+9.543208345441746e-01, +2.756529256636570e-01, +8.471105348916165e-03}},
+       Descriptor{4, 1.905388500388057e-03, {+1.961068637617923e-01, +4.881593428940726e-01, +4.956224506553615e-01}},
+       Descriptor{4, 1.836355855674232e-03, {+5.253928688656228e-01, +9.036540229831336e-01, +4.642983560786509e-02}},
+       Descriptor{4, 1.733122655911971e-03, {+7.871050645706265e-01, +5.305666908542513e-01, +1.965615185289643e-01}},
+       Descriptor{4, 8.755868144016887e-04, {+8.730512492238405e-01, +5.824961459878540e-01, +6.875723092085683e-03}},
+       Descriptor{4, 5.299199352810601e-03, {+4.953985501065977e-01, +2.191018460614218e-01, +3.867051301301994e-01}},
+       Descriptor{4, 1.909231403394242e-03, {+8.695725428246946e-01, +3.055722326033879e-01, +1.089610679849025e-01}},
+       Descriptor{4, 3.332703296489462e-03, {+8.255682153916121e-01, +1.820166521926553e-01, +4.416884971526350e-02}},
+       Descriptor{4, 1.372582739425581e-03, {+2.263367583189702e-01, +3.553849916667940e-01, +6.248925484789940e-01}},
+       Descriptor{4, 4.405628951728082e-03, {+5.513678656050841e-01, +7.486875952236592e-01, +1.172732959999338e-01}},
+       Descriptor{4, 6.902657704569894e-03, {+6.416804127385760e-01, +2.589654916989230e-01, +1.195843625945489e-01}},
+       Descriptor{4, 2.027344695276262e-03, {+4.862393601034299e-01, +1.128685901856588e-01, +1.562091696416078e-02}},
+       Descriptor{4, 2.046435953340190e-03, {+4.272869183745079e-01, +6.283616414580556e-01, +3.513262055700426e-01}},
+       Descriptor{4, 3.453264166213793e-04, {+9.787077639713484e-01, +7.341055271589447e-01, +1.323405399603627e-02}},
+       Descriptor{4, 2.425089503329792e-03, {+7.058505258664157e-01, +4.135891617947305e-01, +2.986233869137579e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 18: {
+    constexpr size_t nb_points = 357;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.340052013254401e-04, {+9.689131877314755e-01}},
+       Descriptor{2, 6.532829236534353e-03, {+7.386927583195001e-01, +1.121808531538677e-01}},
+       Descriptor{2, 2.462677829860450e-03, {+7.680665730295287e-01, +2.095203731422156e-01}},
+       Descriptor{2, 2.362663198775904e-03, {+3.167394480989801e-01, +4.504333920940983e-01}},
+       Descriptor{2, 2.412846260195942e-03, {+6.187757221807219e-01, +3.671380924150245e-01}},
+       Descriptor{2, 5.639399189884141e-03, {+3.723529440295544e-01, +5.149694555513888e-01}},
+       Descriptor{2, 5.308794645373922e-03, {+5.038719357078286e-01, +2.465874064866041e-01}},
+       Descriptor{2, 3.303209677609701e-03, {+7.220783650726965e-01, +3.347288009925681e-02}},
+       Descriptor{2, 7.157096872930287e-03, {+3.940311335958268e-01, +1.760491594601057e-01}},
+       Descriptor{2, 3.560209000799229e-03, {+1.181947980612448e-01, +1.262767090030246e-01}},
+       Descriptor{2, 2.869089106153775e-03, {+5.117986204426422e-01, +1.146186842907397e-02}},
+       Descriptor{2, 1.192387955426962e-03, {+9.344056049843052e-02, +8.286557181156362e-01}},
+       Descriptor{2, 2.357089773027461e-04, {+9.778630528838397e-01, +4.114776635706134e-03}},
+       Descriptor{2, 3.324152026523092e-03, {+2.689311802882549e-01, +6.643176008593026e-01}},
+       Descriptor{2, 6.442449838790662e-04, {+1.858887671719376e-01, +8.071453164684427e-01}},
+       Descriptor{3, 9.304082839731633e-04, {+8.803206212322914e-01, +6.759778909905437e-02}},
+       Descriptor{3, 6.153126385517773e-04, {+6.814521213676156e-01, +2.940343113268007e-01}},
+       Descriptor{3, 2.890748228612777e-04, {+4.969903643427416e-01, +4.966550809316037e-01}},
+       Descriptor{3, 9.579280577883828e-03, {+3.063401808222882e-01, +2.791132054901511e-01}},
+       Descriptor{3, 2.562433782912423e-03, {+5.853996645538505e-01, +7.876704807666435e-02}},
+       Descriptor{3, 1.970097108482327e-03, {+7.769439547694348e-01, +4.825635484814583e-02}},
+       Descriptor{3, 2.248538016231859e-03, {+1.869536840196541e-01, +8.812474861667117e-03}},
+       Descriptor{3, 1.783785036670776e-03, {+6.440284406129158e-01, +2.005872107121198e-02}},
+       Descriptor{3, 4.615488477875886e-04, {+1.993968440939729e-01, +7.563909496805638e-01}},
+       Descriptor{3, 3.835673525848779e-03, {+4.915166272295902e-01, +1.515550593825411e-01}},
+       Descriptor{3, 4.243233892689520e-03, {+2.197188373659789e-01, +6.084275881937196e-01}},
+       Descriptor{3, 2.977098509258866e-03, {+3.818186027529240e-01, +5.256954545937250e-01}},
+       Descriptor{3, 7.500976584211740e-03, {+1.323357443649693e-01, +4.028122600425206e-01}},
+       Descriptor{3, 2.080642319051259e-03, {+2.643490668003158e-01, +4.148931197347632e-02}},
+       Descriptor{3, 6.762965500915199e-03, {+2.625901672258577e-01, +1.466170076483845e-01}},
+       Descriptor{3, 3.005874847678541e-03, {+6.368304996885981e-01, +2.566159742055003e-01}},
+       Descriptor{3, 5.694731752763933e-03, {+5.033323003573335e-01, +2.555236724339244e-01}},
+       Descriptor{3, 3.293452280749932e-03, {+4.305306648769874e-01, +3.698185671948173e-01}},
+       Descriptor{3, 3.193430746935616e-03, {+4.412117799623165e-01, +1.554708555805914e-02}},
+       Descriptor{3, 3.007859639579343e-03, {+1.233852653491083e-01, +4.951092940950103e-02}},
+       Descriptor{3, 6.210180814260240e-03, {+2.819646674678744e-01, +4.572992412719536e-01}},
+       Descriptor{3, 6.903801293936010e-03, {+1.230206594250918e-01, +2.586539281154879e-01}},
+       Descriptor{3, 1.006183863763346e-04, {+9.699534085616927e-01, +1.471561063742728e-02}},
+       Descriptor{3, 1.356307828233825e-04, {+1.536759742301584e-01, +8.430293850954313e-01}},
+       Descriptor{3, 7.789376721955394e-04, {+3.232491600294373e-01, +6.422131631619533e-01}},
+       Descriptor{3, 2.674414703364759e-03, {+7.979061002341173e-02, +7.081546894975584e-01}},
+       Descriptor{3, 4.775459413065313e-04, {+5.572751587573097e-02, +9.075661676644768e-01}},
+       Descriptor{3, 7.406610401180713e-04, {+8.040718007941732e-01, +1.580109891260832e-01}},
+       Descriptor{3, 5.291370439452658e-03, {+1.063079492932114e-01, +5.586401442375718e-01}},
+       Descriptor{3, 1.500664611055249e-03, {+1.496811655200803e-01, +7.655341121253637e-01}},
+       Descriptor{3, 2.626273999362915e-03, {+7.217980235897704e-01, +1.203862817704082e-01}},
+       Descriptor{4, 1.065436899176937e-03, {+8.179717082248776e-01, +6.432021548505883e-01, +1.654004245581700e-01}},
+       Descriptor{4, 6.080722561497974e-03, {+4.547777230252971e-01, +1.663641899799587e-01, +7.191647566286845e-02}},
+       Descriptor{4, 5.310023010585634e-03, {+2.207750902147102e-01, +6.491941928630117e-01, +2.436936622868156e-01}},
+       Descriptor{4, 2.020033424832363e-03, {+8.584914437706522e-01, +2.644232091625772e-01, +3.997911405124788e-02}},
+       Descriptor{4, 2.164703930837454e-03, {+1.908724705162439e-01, +4.565598584631854e-01, +5.240352269645050e-01}},
+       Descriptor{4, 4.353306989152711e-03, {+3.778781661386005e-01, +6.640629325475310e-01, +6.332251756914624e-02}},
+       Descriptor{4, 6.863403477821615e-04, {+8.939555664639546e-01, +1.728332917834724e-01, +6.271673653011652e-03}},
+       Descriptor{4, 4.019067831153739e-03, {+4.099141505393519e-01, +5.003413197772252e-02, +3.461255047044217e-01}},
+       Descriptor{4, 7.912728473970523e-04, {+9.319810865442065e-01, +1.066217398606188e-01, +4.934944134498662e-02}},
+       Descriptor{4, 2.446710528967660e-03, {+2.801822060720651e-01, +8.499319598680132e-01, +1.205025919278488e-01}},
+       Descriptor{4, 1.950381902056960e-03, {+5.772816541361394e-01, +4.620142974473129e-01, +3.942812057164277e-01}},
+       Descriptor{4, 1.105915938820858e-03, {+8.445158080852770e-01, +6.175679175506062e-01, +8.380194537906345e-03}},
+       Descriptor{4, 5.030114823747663e-03, {+5.294157289381186e-01, +2.334853358468202e-01, +3.799152175151670e-01}},
+       Descriptor{4, 6.596708157003132e-04, {+9.282752180189467e-01, +6.794178627981728e-01, +6.658426856019066e-02}},
+       Descriptor{4, 6.132163978139013e-04, {+9.683651638409505e-01, +4.614425143846638e-01, +1.360996700708025e-02}},
+       Descriptor{4, 8.965714244660760e-04, {+1.674457184003009e-01, +3.143041479415947e-01, +6.737791163005959e-01}},
+       Descriptor{4, 3.639835604771190e-03, {+4.923271845449609e-01, +7.398884292540342e-01, +1.512239686451382e-01}},
+       Descriptor{4, 2.111289190280422e-03, {+8.565997422827011e-01, +5.406106561720058e-01, +5.960704457999711e-02}},
+       Descriptor{4, 5.700622245430812e-03, {+2.670227736126091e-01, +5.753139352915381e-01, +1.555725209349524e-01}},
+       Descriptor{4, 1.955327766010427e-03, {+4.150645766534223e-01, +7.173101873515257e-01, +2.646731283533919e-01}},
+       Descriptor{4, 6.238495350617601e-04, {+9.446623957916176e-01, +8.161908018659551e-01, +1.193065095103094e-02}},
+       Descriptor{4, 1.517921654020750e-03, {+7.145052262408169e-01, +3.117978322705600e-01, +7.294130256492850e-03}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 19: {
+    constexpr size_t nb_points = 418;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.287002239747240e-04, {+9.673913352370350e-01}},
+       Descriptor{1, 5.390761254300309e-03, {+5.436653246463873e-01}},
+       Descriptor{2, 6.666871057805598e-03, {+3.736572163452879e-01, +4.188756062275946e-01}},
+       Descriptor{2, 6.198011213798800e-03, {+4.375282703042845e-01, +2.977489444568584e-01}},
+       Descriptor{2, 1.196903942172590e-03, {+5.578275629324269e-01, +4.363255890435926e-01}},
+       Descriptor{2, 9.000351976243133e-03, {+2.727587922914985e-01, +1.312347586523022e-01}},
+       Descriptor{2, 4.164637105384020e-03, {+2.887241175382593e-01, +5.688216954681572e-01}},
+       Descriptor{2, 1.824497208727276e-03, {+1.127746562325076e-01, +6.623057363188606e-01}},
+       Descriptor{2, 4.523718683330509e-03, {+6.547023041522421e-01, +2.196378912459858e-01}},
+       Descriptor{2, 8.161116064814124e-03, {+2.229510128266209e-01, +2.273269633283456e-01}},
+       Descriptor{2, 1.648326828637079e-03, {+3.517668912115330e-01, +5.417515126253033e-03}},
+       Descriptor{2, 5.571492309500324e-04, {+2.069773541366567e-01, +7.929490613648335e-01}},
+       Descriptor{2, 1.118215252194827e-03, {+9.311118902772667e-01, +4.180662980331772e-02}},
+       Descriptor{2, 1.885810158761153e-03, {+8.430335844595135e-01, +1.361551806457953e-01}},
+       Descriptor{2, 2.828101226263676e-04, {+7.303948013428374e-02, +9.187547560280617e-01}},
+       Descriptor{2, 1.815863546152488e-03, {+6.780735133862854e-01, +3.019190562178010e-01}},
+       Descriptor{2, 1.247082448220326e-03, {+8.819848262866421e-01, +9.387499849089011e-03}},
+       Descriptor{2, 1.074162553027278e-03, {+3.858781168610365e-01, +6.065859845746238e-01}},
+       Descriptor{2, 2.579770777378314e-03, {+1.370888473751836e-01, +7.535348220210553e-01}},
+       Descriptor{3, 9.170154497260322e-04, {+5.255852110826873e-01, +4.408685369060898e-01}},
+       Descriptor{3, 3.068778721173566e-03, {+4.530164257058980e-01, +4.188977508155631e-01}},
+       Descriptor{3, 1.871058171917288e-03, {+6.712511632017596e-01, +1.251810432139165e-02}},
+       Descriptor{3, 1.536909390806274e-04, {+3.695141864916882e-01, +6.246014838381103e-01}},
+       Descriptor{3, 3.845290732110408e-03, {+2.883822343558625e-01, +5.439973273857342e-02}},
+       Descriptor{3, 2.243116164244068e-04, {+8.285723838946580e-01, +1.619186219316668e-01}},
+       Descriptor{3, 8.402087626133961e-04, {+6.724607008544720e-01, +2.930033138342824e-01}},
+       Descriptor{3, 3.041957674540431e-03, {+4.873668933896256e-01, +2.634265889387019e-01}},
+       Descriptor{3, 2.294841766987932e-04, {+9.503664431507984e-01, +1.127614473867753e-02}},
+       Descriptor{3, 4.182420591869638e-03, {+9.132353058612172e-02, +3.580798461499961e-01}},
+       Descriptor{3, 6.327091081505453e-03, {+1.552069555141659e-01, +4.714188671670859e-01}},
+       Descriptor{3, 7.592834005951271e-04, {+5.052806321356114e-02, +8.544640711209540e-01}},
+       Descriptor{3, 4.596836662276725e-03, {+3.978139280416619e-01, +1.293031169145601e-01}},
+       Descriptor{3, 2.766364274235799e-03, {+3.760791314064199e-01, +7.899233073306565e-02}},
+       Descriptor{3, 2.144850801510551e-03, {+1.643638267982826e-01, +1.672886065733771e-02}},
+       Descriptor{3, 5.283144486182838e-03, {+3.468892555686117e-01, +3.885050437237611e-01}},
+       Descriptor{3, 4.881052322614178e-04, {+1.191332579371944e-01, +8.475473975749326e-01}},
+       Descriptor{3, 2.120695458348499e-03, {+7.454583814188197e-01, +1.626360280018163e-01}},
+       Descriptor{3, 1.615060199482018e-03, {+7.610495209730348e-01, +7.862268330374027e-02}},
+       Descriptor{3, 6.942257092948444e-03, {+2.420812058877188e-01, +3.202806437619411e-01}},
+       Descriptor{3, 1.002683020081214e-03, {+8.445798381566891e-01, +1.382133423647079e-02}},
+       Descriptor{3, 9.207753167491697e-04, {+1.645646578166248e-01, +7.687752250435795e-01}},
+       Descriptor{3, 3.556169477445426e-03, {+2.747842347238860e-01, +5.301402408730526e-01}},
+       Descriptor{3, 2.186178713296525e-03, {+5.849919450560005e-01, +3.008923824441204e-01}},
+       Descriptor{3, 2.691651662032098e-03, {+1.489738124603387e-01, +6.167319270078593e-01}},
+       Descriptor{3, 3.016740104757321e-03, {+4.389290397617306e-01, +1.376377254881576e-02}},
+       Descriptor{3, 7.466645241550527e-04, {+8.798197120003726e-01, +7.210305012229555e-02}},
+       Descriptor{3, 2.997482336902333e-03, {+1.168533193079235e-01, +6.238118616203953e-02}},
+       Descriptor{3, 1.447847026063246e-03, {+3.428716485856084e-01, +5.855795349165888e-01}},
+       Descriptor{4, 1.072809629353361e-03, {+8.643327984186001e-01, +6.422236743536752e-01, +1.179192064779849e-01}},
+       Descriptor{4, 4.839037348378018e-03, {+5.784047959665971e-01, +1.570037147921136e-01, +1.114252341292851e-01}},
+       Descriptor{4, 2.747099059820038e-03, {+3.108799114935293e-01, +5.391600980992765e-01, +2.750267281218793e-01}},
+       Descriptor{4, 4.335016889029408e-03, {+2.047256512125053e-01, +5.650506986268603e-01, +3.472846972655921e-01}},
+       Descriptor{4, 2.718100159849547e-03, {+1.547714831454746e-01, +8.008730085913268e-01, +7.549551730236130e-02}},
+       Descriptor{4, 9.168547398610547e-04, {+9.112222485018308e-01, +3.619692710244549e-01, +3.464296962031117e-02}},
+       Descriptor{4, 6.808951019688473e-03, {+2.376393444235864e-01, +4.605447477525737e-01, +2.044152977923567e-01}},
+       Descriptor{4, 4.013856896610910e-04, {+9.302667501209977e-01, +3.215980764268125e-01, +6.972695561067982e-02}},
+       Descriptor{4, 2.155002895452285e-03, {+2.733067099141755e-01, +7.541757880282322e-01, +2.199898046581558e-01}},
+       Descriptor{4, 1.611542573023053e-03, {+6.197790831123378e-01, +4.009499952384629e-01, +3.646225494795918e-01}},
+       Descriptor{4, 2.743455266744436e-04, {+9.673179731072893e-01, +7.175030678238746e-01, +4.586334961223785e-03}},
+       Descriptor{4, 2.300082975062185e-03, {+2.810759624172646e-01, +1.446964075338812e-01, +6.664230048871306e-01}},
+       Descriptor{4, 2.945835759051771e-04, {+9.572349478971794e-01, +8.054431343183734e-01, +3.975302308577006e-02}},
+       Descriptor{4, 6.007312216772520e-04, {+9.341504389265958e-01, +5.642632183544610e-01, +3.073598825835916e-02}},
+       Descriptor{4, 2.620175037577330e-04, {+2.212443839381749e-01, +2.763723337715620e-01, +7.207735178967332e-01}},
+       Descriptor{4, 4.192832498709637e-03, {+5.144086015486090e-01, +6.711096936374128e-01, +1.403609469612069e-01}},
+       Descriptor{4, 1.736294308688135e-03, {+8.466968176319485e-01, +6.476309524361428e-01, +5.264191965632582e-02}},
+       Descriptor{4, 9.439101669434532e-04, {+5.837234015734821e-01, +7.587849158481068e-01, +2.270233248942167e-01}},
+       Descriptor{4, 2.175513608503740e-03, {+2.312548427820351e-01, +7.579578663965916e-01, +2.881326163838643e-02}},
+       Descriptor{4, 1.283394690183355e-03, {+6.535198164687628e-01, +2.118649081243290e-01, +5.321389368184961e-03}},
+       Descriptor{4, 2.726150499605700e-03, {+6.953450152489690e-01, +1.860513163719893e-01, +1.591709313067350e-01}},
+       Descriptor{4, 3.185339603850620e-03, {+8.127234831798691e-02, +5.241033074238067e-01, +4.286780869626788e-02}},
+       Descriptor{4, 2.663092866644570e-03, {+6.731624418813449e-01, +4.707459336103425e-01, +2.281921560389168e-01}},
+       Descriptor{4, 2.326668465288244e-03, {+8.297715217071255e-01, +3.845634575264398e-01, +1.149758475625305e-01}},
+       Descriptor{4, 9.734631151970315e-04, {+8.530842873329166e-01, +4.855129905435616e-01, +6.546603573639168e-03}},
+       Descriptor{4, 3.483456851399480e-03, {+4.467073185069126e-01, +1.775709599102257e-01, +4.889658061311413e-01}},
+       Descriptor{4, 4.061954216963042e-03, {+6.700967212030146e-01, +4.619994161131860e-01, +5.843691849737929e-02}},
+       Descriptor{4, 3.324559356884226e-04, {+9.767336037107472e-01, +2.679001927626974e-01, +6.104985413274207e-03}},
+       Descriptor{4, 9.232817417531915e-04, {+3.188790007212526e-01, +4.538176463136764e-01, +5.340866401557672e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 20: {
+    constexpr size_t nb_points = 489;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 5.374309731479196e-03, {+6.041261328156059e-01}},
+       Descriptor{2, 5.671734293778256e-03, {+4.816935434431252e-01, +1.283254121370188e-01}},
+       Descriptor{2, 3.248860881724051e-03, {+6.985803886987003e-01, +2.363645279387677e-01}},
+       Descriptor{2, 3.452094526682737e-03, {+5.346865795177097e-01, +3.694814396873626e-01}},
+       Descriptor{2, 6.666033444769521e-04, {+4.222838087844267e-01, +5.741352229223836e-01}},
+       Descriptor{2, 3.995792460381601e-03, {+3.068045188877912e-01, +5.709702769195857e-01}},
+       Descriptor{2, 4.992778959530078e-03, {+2.287749849768982e-01, +4.453272633438413e-01}},
+       Descriptor{2, 2.469622976420997e-03, {+5.538968741280718e-01, +1.850719926469714e-01}},
+       Descriptor{2, 7.066155754130616e-03, {+3.848922804233133e-01, +2.828189872249547e-01}},
+       Descriptor{2, 1.916980035371976e-03, {+2.571105687384414e-01, +7.453813550512145e-03}},
+       Descriptor{2, 2.387541558193644e-04, {+9.495420833471157e-01, +2.730608888531540e-03}},
+       Descriptor{2, 1.680239670130047e-04, {+9.635090648066924e-02, +8.947468307319109e-01}},
+       Descriptor{2, 9.944327373233232e-04, {+9.104493126181845e-01, +5.489260378459437e-02}},
+       Descriptor{2, 3.454941677138827e-03, {+2.683082435742811e-01, +1.789054761711333e-01}},
+       Descriptor{2, 1.377780657771690e-03, {+8.274465668629972e-01, +1.443872792953364e-01}},
+       Descriptor{2, 6.135678341459227e-05, {+3.789270083108932e-02, +9.600532902975129e-01}},
+       Descriptor{2, 9.961596076112076e-04, {+5.735205240815813e-01, +4.226237395657491e-01}},
+       Descriptor{2, 1.734688844684138e-03, {+7.423115722700304e-01, +9.373411784322374e-03}},
+       Descriptor{2, 6.408905290179002e-04, {+2.846909641320057e-01, +7.051164480862681e-01}},
+       Descriptor{2, 1.747701749929037e-03, {+1.128162795069089e-01, +8.097603763752680e-01}},
+       Descriptor{3, 5.356183098283481e-04, {+4.535676239967043e-01, +5.210700225779646e-01}},
+       Descriptor{3, 3.744128155669667e-03, {+3.336110385261160e-01, +4.762219126204001e-01}},
+       Descriptor{3, 3.648498543938937e-03, {+3.773337527822211e-01, +4.553198949867700e-02}},
+       Descriptor{3, 2.722104630492699e-04, {+3.061117491692780e-01, +6.823651677845297e-01}},
+       Descriptor{3, 4.038058627934289e-03, {+2.904460076524542e-01, +3.676651830752369e-01}},
+       Descriptor{3, 2.849455013152634e-03, {+5.914106884092367e-01, +3.140155892114704e-02}},
+       Descriptor{3, 4.946318515118939e-04, {+7.660549649135295e-01, +2.085476317987768e-01}},
+       Descriptor{3, 3.435750243515581e-03, {+4.496216926317984e-01, +3.209594399518608e-01}},
+       Descriptor{3, 7.654793642697566e-04, {+6.125691075458759e-01, +3.559054105435636e-01}},
+       Descriptor{3, 9.270297025443059e-05, {+9.651777563788078e-01, +1.963481046583392e-02}},
+       Descriptor{3, 4.440447400598151e-03, {+1.850944308523854e-01, +5.811084832537615e-01}},
+       Descriptor{3, 4.995284180274901e-03, {+1.305339914072845e-01, +2.203018355115161e-01}},
+       Descriptor{3, 2.327605643441156e-03, {+8.713330755302490e-02, +7.074337461014996e-01}},
+       Descriptor{3, 3.058461110576954e-03, {+1.162657955161678e-01, +4.975461839293467e-01}},
+       Descriptor{3, 2.716703334283396e-04, {+3.353132384174681e-02, +9.019799487990967e-01}},
+       Descriptor{3, 3.592040414552888e-03, {+6.300704464975707e-01, +1.583017481238254e-01}},
+       Descriptor{3, 4.506232324626833e-03, {+1.008155351127816e-01, +3.470348929613336e-01}},
+       Descriptor{3, 2.662059522271117e-04, {+9.712721271803038e-02, +8.772759077750505e-01}},
+       Descriptor{3, 1.722422251895809e-03, {+6.503674461202499e-01, +2.509808878874181e-01}},
+       Descriptor{3, 1.529902163082495e-03, {+7.881670555390969e-01, +1.228748706112481e-01}},
+       Descriptor{3, 4.331723455652033e-03, {+4.632610253517699e-01, +2.156955721524682e-01}},
+       Descriptor{3, 8.698495590594387e-04, {+8.742622562545352e-01, +4.316688220319717e-02}},
+       Descriptor{3, 3.166557615941483e-03, {+9.693365579654889e-02, +1.072427543090557e-01}},
+       Descriptor{3, 1.455167758465003e-03, {+5.265123436991546e-01, +3.296427782470148e-01}},
+       Descriptor{3, 1.137616034931676e-03, {+2.105801835055998e-01, +7.160068077591689e-01}},
+       Descriptor{3, 1.884904388466294e-03, {+4.834178601814259e-01, +4.197363107268692e-01}},
+       Descriptor{3, 6.049803062524013e-03, {+2.723239805399290e-01, +1.077197094023458e-01}},
+       Descriptor{3, 2.785053160390842e-04, {+8.874839070114657e-01, +9.137076099343852e-02}},
+       Descriptor{3, 1.644290914957632e-03, {+3.400434017708192e-01, +5.743322455524006e-01}},
+       Descriptor{3, 4.384669874639433e-03, {+2.465713452115888e-01, +2.965278956806727e-01}},
+       Descriptor{3, 2.508217360325574e-03, {+7.203261185246220e-01, +6.861567128774998e-02}},
+       Descriptor{4, 6.403192997163759e-04, {+8.693467247184354e-01, +6.605377674376229e-01, +1.211268750412950e-01}},
+       Descriptor{4, 2.743609819133677e-03, {+5.165700364414527e-01, +1.268114432243165e-01, +6.232922095164157e-02}},
+       Descriptor{4, 4.425132157331708e-03, {+2.141949283591463e-01, +5.351420520651734e-01, +2.887837560282615e-01}},
+       Descriptor{4, 3.793533547619605e-03, {+3.860615525036007e-01, +1.430987462436435e-01, +4.198199632798237e-01}},
+       Descriptor{4, 2.598731880891428e-03, {+3.148265223116505e-01, +5.788734905442336e-01, +3.671971633512633e-01}},
+       Descriptor{4, 1.100562071404283e-03, {+1.333776220074420e-01, +8.685734808776638e-01, +3.539982573087990e-02}},
+       Descriptor{4, 7.559451274546276e-04, {+8.933451426565783e-01, +2.970093961186454e-01, +6.894095497062928e-03}},
+       Descriptor{4, 4.164432957175231e-03, {+6.420732128180285e-01, +2.185904390936289e-01, +1.803409782254840e-01}},
+       Descriptor{4, 7.986472006319258e-04, {+9.015566925902675e-01, +2.850450105795807e-01, +8.701177937660884e-02}},
+       Descriptor{4, 1.274772831709237e-03, {+1.795078173861985e-01, +6.796606097350029e-01, +3.018403418988506e-01}},
+       Descriptor{4, 7.742072015194865e-04, {+5.287716454511575e-01, +3.752537914501156e-01, +4.637531647615307e-01}},
+       Descriptor{4, 1.079867868775378e-03, {+8.568477683810934e-01, +7.061426122653948e-01, +1.259447070049874e-02}},
+       Descriptor{4, 1.550935782403934e-03, {+2.374464971047839e-01, +9.039264484495425e-02, +6.846455484276207e-01}},
+       Descriptor{4, 4.993786208816673e-04, {+9.406598770106565e-01, +7.437511960991476e-01, +4.669618994654997e-02}},
+       Descriptor{4, 4.262366420646442e-04, {+9.654498557948427e-01, +5.581648667244832e-01, +9.979099797204329e-03}},
+       Descriptor{4, 5.244528523523384e-04, {+1.185997715939458e-01, +1.973055345489109e-01, +7.917076093806301e-01}},
+       Descriptor{4, 2.954838737142077e-03, {+4.383682874673118e-01, +6.801053445689546e-01, +2.323974680712023e-01}},
+       Descriptor{4, 1.540648448261571e-03, {+8.501501583895467e-01, +5.776785987998531e-01, +6.212941698545271e-02}},
+       Descriptor{4, 4.849861236258662e-03, {+4.097788353774976e-01, +5.992649167860331e-01, +1.025000747100843e-01}},
+       Descriptor{4, 7.495372210984812e-04, {+6.254025057545211e-01, +7.793811396398747e-01, +1.963810536401145e-01}},
+       Descriptor{4, 1.677329035907760e-03, {+1.658794771155705e-01, +6.513788165859978e-01, +3.807225396126873e-02}},
+       Descriptor{4, 2.242040914713952e-03, {+5.062559144366789e-01, +2.470826877566723e-01, +9.699938299695787e-03}},
+       Descriptor{4, 3.054238951987089e-03, {+7.429293632807329e-01, +1.682359345705867e-01, +8.712730173221997e-02}},
+       Descriptor{4, 2.363709150448725e-03, {+1.867609649526593e-02, +2.536204599781817e-01, +4.169093747605618e-02}},
+       Descriptor{4, 4.124773767916695e-03, {+2.608600527928172e-01, +4.094605558894180e-01, +1.898222236268910e-01}},
+       Descriptor{4, 2.522814373870828e-03, {+7.938808312983506e-01, +4.691881899824492e-01, +1.276217397945529e-01}},
+       Descriptor{4, 1.143364037200234e-03, {+7.930116554406030e-01, +2.986457776503003e-01, +1.955063271970715e-01}},
+       Descriptor{4, 2.785004192577059e-03, {+4.492999606983316e-01, +1.696796572740249e-01, +4.935677375430703e-01}},
+       Descriptor{4, 2.222625782566179e-03, {+7.687383145879386e-01, +3.967745055048160e-01, +3.472481178210238e-02}},
+       Descriptor{4, 2.609946044201681e-04, {+9.785997597757148e-01, +1.909470577592247e-01, +1.518094278770999e-02}},
+       Descriptor{4, 1.252174210689572e-03, {+2.135890927684384e-01, +3.604161529615568e-01, +6.154203152262100e-01}},
+       Descriptor{4, 1.279670338642788e-03, {+7.962525773875013e-01, +1.562852697636821e-01, +1.257520475253165e-01}},
+       Descriptor{4, 7.990495656729902e-04, {+9.193985656269752e-01, +4.148607451061945e-01, +4.457888016467764e-02}},
+       Descriptor{4, 7.812219859741840e-04, {+5.150733025160915e-01, +6.896293007082364e-01, +3.017078195063375e-01}},
+       Descriptor{4, 2.172588051809931e-04, {+9.636121389116856e-01, +8.595767942792535e-01, +5.771284380553639e-03}},
+       Descriptor{4, 7.595577614438183e-04, {+7.214071149093607e-01, +4.920954370020814e-01, +2.395309192181843e-03}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  default: {
+    throw NormalError("Gauss quadrature formulae handle degrees up to " +
+                      std::to_string(PyramidGaussQuadrature::max_degree) + " on pyramids");
+  }
+  }
+}
diff --git a/src/analysis/PyramidGaussQuadrature.hpp b/src/analysis/PyramidGaussQuadrature.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b68c518040e39611a0f1670758f40563271abb11
--- /dev/null
+++ b/src/analysis/PyramidGaussQuadrature.hpp
@@ -0,0 +1,38 @@
+#ifndef PYRAMID_GAUSS_QUADRATURE_HPP
+#define PYRAMID_GAUSS_QUADRATURE_HPP
+
+#include <analysis/QuadratureFormula.hpp>
+
+/**
+ * Defines Gauss quadrature on the reference pyramid element
+ *
+ * \note formulae are provided by
+ *
+ * 'High-order symmetric cubature rules for tetrahedra and pyramids'
+ * Jan Jasˁkowiec & N. Sukumar (2020).
+ */
+class PyramidGaussQuadrature final : public QuadratureFormula<3>
+{
+ public:
+  constexpr static size_t max_degree = 20;
+
+ private:
+  void _buildPointAndWeightLists(const size_t degree);
+
+ public:
+  PyramidGaussQuadrature(PyramidGaussQuadrature&&)      = default;
+  PyramidGaussQuadrature(const PyramidGaussQuadrature&) = default;
+
+ private:
+  friend class QuadratureManager;
+
+  explicit PyramidGaussQuadrature(const size_t degree) : QuadratureFormula<3>(QuadratureType::Gauss)
+  {
+    this->_buildPointAndWeightLists(degree);
+  }
+
+  PyramidGaussQuadrature()  = delete;
+  ~PyramidGaussQuadrature() = default;
+};
+
+#endif   // PYRAMID_GAUSS_QUADRATURE_HPP
diff --git a/src/analysis/QuadratureFormula.hpp b/src/analysis/QuadratureFormula.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c472e3556a298d14df73d1cb45a8d32c9fe6dc28
--- /dev/null
+++ b/src/analysis/QuadratureFormula.hpp
@@ -0,0 +1,63 @@
+#ifndef QUADRATURE_FORMULA_HPP
+#define QUADRATURE_FORMULA_HPP
+
+#include <algebra/TinyVector.hpp>
+#include <analysis/QuadratureType.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/PugsMacros.hpp>
+#include <utils/SmallArray.hpp>
+
+template <size_t Dimension>
+class QuadratureFormula
+{
+ public:
+ protected:
+  QuadratureType m_type;
+  SmallArray<const TinyVector<Dimension>> m_point_list;
+  SmallArray<const double> m_weight_list;
+
+ public:
+  PUGS_INLINE size_t
+  numberOfPoints() const
+  {
+    Assert(m_point_list.size() == m_weight_list.size());
+    return m_point_list.size();
+  }
+
+  PUGS_INLINE
+  const SmallArray<const TinyVector<Dimension>>&
+  pointList() const
+  {
+    return m_point_list;
+  }
+
+  PUGS_INLINE
+  const TinyVector<Dimension>&
+  point(const size_t i) const
+  {
+    return m_point_list[i];
+  }
+
+  PUGS_INLINE
+  const SmallArray<const double>&
+  weightList() const
+  {
+    return m_weight_list;
+  }
+
+  PUGS_INLINE
+  const double&
+  weight(const size_t i) const
+  {
+    return m_weight_list[i];
+  }
+
+ protected:
+  explicit QuadratureFormula(const QuadratureType type) : m_type{type} {}
+
+ public:
+  QuadratureFormula()          = default;
+  virtual ~QuadratureFormula() = default;
+};
+
+#endif   // QUADRATURE_FORMULA_HPP
diff --git a/src/analysis/QuadratureManager.cpp b/src/analysis/QuadratureManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ca63299d184197d3a63168b87111646aec9bf5b7
--- /dev/null
+++ b/src/analysis/QuadratureManager.cpp
@@ -0,0 +1,194 @@
+#include <analysis/QuadratureManager.hpp>
+
+#include <analysis/CubeGaussQuadrature.hpp>
+#include <analysis/PrismGaussQuadrature.hpp>
+#include <analysis/PyramidGaussQuadrature.hpp>
+#include <analysis/SquareGaussQuadrature.hpp>
+#include <analysis/TensorialGaussLegendreQuadrature.hpp>
+#include <analysis/TensorialGaussLobattoQuadrature.hpp>
+#include <analysis/TetrahedronGaussQuadrature.hpp>
+#include <analysis/TriangleGaussQuadrature.hpp>
+
+QuadratureManager* QuadratureManager::s_instance{nullptr};
+
+void
+QuadratureManager::create()
+{
+  Assert(s_instance == nullptr, "QuadratureManager is already created");
+  s_instance = new QuadratureManager;
+}
+
+void
+QuadratureManager::destroy()
+{
+  Assert(s_instance != nullptr, "QuadratureManager was not created!");
+
+  delete s_instance;
+  s_instance = nullptr;
+}
+
+Array<const QuadratureFormula<1>>
+QuadratureManager::_buildLineGaussLegendreFormulaList()
+{
+  Array<QuadratureFormula<1>> line_gauss_legendre_formula_list(TensorialGaussLegendreQuadrature<1>::max_degree / 2 + 1);
+  for (size_t i = 0; i < TensorialGaussLegendreQuadrature<1>::max_degree / 2 + 1; ++i) {
+    line_gauss_legendre_formula_list[i] = TensorialGaussLegendreQuadrature<1>(2 * i + 1);
+  }
+
+  return line_gauss_legendre_formula_list;
+}
+
+Array<const QuadratureFormula<1>>
+QuadratureManager::_buildLineGaussLobattoFormulaList()
+{
+  Array<QuadratureFormula<1>> line_gauss_lobatto_formula_list(TensorialGaussLobattoQuadrature<1>::max_degree / 2 + 1);
+  for (size_t i = 0; i < TensorialGaussLobattoQuadrature<1>::max_degree / 2 + 1; ++i) {
+    line_gauss_lobatto_formula_list[i] = TensorialGaussLobattoQuadrature<1>(2 * i + 1);
+  }
+
+  return line_gauss_lobatto_formula_list;
+}
+
+Array<const QuadratureFormula<2>>
+QuadratureManager::_buildSquareGaussFormulaList()
+{
+  Array<QuadratureFormula<2>> square_gauss_formula_list(SquareGaussQuadrature::max_degree / 2 + 1);
+  for (size_t i = 0; i < SquareGaussQuadrature::max_degree / 2 + 1; ++i) {
+    square_gauss_formula_list[i] = SquareGaussQuadrature(2 * i + 1);
+  }
+
+  return square_gauss_formula_list;
+}
+
+Array<const QuadratureFormula<2>>
+QuadratureManager::_buildSquareGaussLegendreFormulaList()
+{
+  Array<QuadratureFormula<2>> square_gauss_legendre_formula_list(TensorialGaussLegendreQuadrature<2>::max_degree / 2 +
+                                                                 1);
+  for (size_t i = 0; i < TensorialGaussLegendreQuadrature<2>::max_degree / 2 + 1; ++i) {
+    square_gauss_legendre_formula_list[i] = TensorialGaussLegendreQuadrature<2>(2 * i + 1);
+  }
+
+  return square_gauss_legendre_formula_list;
+}
+
+Array<const QuadratureFormula<2>>
+QuadratureManager::_buildSquareGaussLobattoFormulaList()
+{
+  Array<QuadratureFormula<2>> square_gauss_lobatto_formula_list(TensorialGaussLobattoQuadrature<2>::max_degree / 2 + 1);
+  for (size_t i = 0; i < TensorialGaussLobattoQuadrature<2>::max_degree / 2 + 1; ++i) {
+    square_gauss_lobatto_formula_list[i] = TensorialGaussLobattoQuadrature<2>(2 * i + 1);
+  }
+
+  return square_gauss_lobatto_formula_list;
+}
+
+Array<const QuadratureFormula<2>>
+QuadratureManager::_buildTriangleGaussFormulaList()
+{
+  Array<QuadratureFormula<2>> triangle_gauss_formula_list(TriangleGaussQuadrature::max_degree);
+  for (size_t i = 0; i < TriangleGaussQuadrature::max_degree; ++i) {
+    triangle_gauss_formula_list[i] = TriangleGaussQuadrature(i + 1);
+  }
+
+  return triangle_gauss_formula_list;
+}
+
+Array<const QuadratureFormula<3>>
+QuadratureManager::_buildCubeGaussFormulaList()
+{
+  Array<QuadratureFormula<3>> cube_gauss_formula_list(CubeGaussQuadrature::max_degree / 2 + 1);
+  for (size_t i = 0; i < CubeGaussQuadrature::max_degree / 2 + 1; ++i) {
+    cube_gauss_formula_list[i] = CubeGaussQuadrature(2 * i + 1);
+  }
+
+  return cube_gauss_formula_list;
+}
+
+Array<const QuadratureFormula<3>>
+QuadratureManager::_buildCubeGaussLegendreFormulaList()
+{
+  Array<QuadratureFormula<3>> cube_gauss_legendre_formula_list(TensorialGaussLegendreQuadrature<3>::max_degree / 2 + 1);
+  for (size_t i = 0; i < TensorialGaussLegendreQuadrature<3>::max_degree / 2 + 1; ++i) {
+    cube_gauss_legendre_formula_list[i] = TensorialGaussLegendreQuadrature<3>(2 * i + 1);
+  }
+
+  return cube_gauss_legendre_formula_list;
+}
+
+Array<const QuadratureFormula<3>>
+QuadratureManager::_buildCubeGaussLobattoFormulaList()
+{
+  Array<QuadratureFormula<3>> cube_gauss_lobatto_formula_list(TensorialGaussLobattoQuadrature<3>::max_degree / 2 + 1);
+  for (size_t i = 0; i < TensorialGaussLobattoQuadrature<3>::max_degree / 2 + 1; ++i) {
+    cube_gauss_lobatto_formula_list[i] = TensorialGaussLobattoQuadrature<3>(2 * i + 1);
+  }
+
+  return cube_gauss_lobatto_formula_list;
+}
+
+Array<const QuadratureFormula<3>>
+QuadratureManager::_buildPrismGaussFormulaList()
+{
+  Array<QuadratureFormula<3>> prism_gauss_formula_list(PrismGaussQuadrature::max_degree);
+  for (size_t i = 0; i < PrismGaussQuadrature::max_degree; ++i) {
+    prism_gauss_formula_list[i] = PrismGaussQuadrature(i + 1);
+  }
+
+  return prism_gauss_formula_list;
+}
+
+Array<const QuadratureFormula<3>>
+QuadratureManager::_buildPyramidGaussFormulaList()
+{
+  Array<QuadratureFormula<3>> pyramid_gauss_formula_list(PyramidGaussQuadrature::max_degree);
+  for (size_t i = 0; i < PyramidGaussQuadrature::max_degree; ++i) {
+    pyramid_gauss_formula_list[i] = PyramidGaussQuadrature(i + 1);
+  }
+
+  return pyramid_gauss_formula_list;
+}
+
+Array<const QuadratureFormula<3>>
+QuadratureManager::_buildTetrahedronGaussFormulaList()
+{
+  Array<QuadratureFormula<3>> tetrahedron_gauss_formula_list(TetrahedronGaussQuadrature::max_degree);
+  for (size_t i = 0; i < TetrahedronGaussQuadrature::max_degree; ++i) {
+    tetrahedron_gauss_formula_list[i] = TetrahedronGaussQuadrature(i + 1);
+  }
+
+  return tetrahedron_gauss_formula_list;
+}
+
+QuadratureManager::QuadratureManager()
+  : m_line_gauss_legendre_formula_list{this->_buildLineGaussLegendreFormulaList()},
+    m_line_gauss_lobatto_formula_list{this->_buildLineGaussLobattoFormulaList()},
+    //
+    m_square_gauss_formula_list{this->_buildSquareGaussFormulaList()},
+    m_square_gauss_legendre_formula_list{this->_buildSquareGaussLegendreFormulaList()},
+    m_square_gauss_lobatto_formula_list{this->_buildSquareGaussLobattoFormulaList()},
+    m_triangle_gauss_formula_list{this->_buildTriangleGaussFormulaList()},
+    //
+    m_cube_gauss_formula_list{this->_buildCubeGaussFormulaList()},
+    m_cube_gauss_legendre_formula_list{this->_buildCubeGaussLegendreFormulaList()},
+    m_cube_gauss_lobatto_formula_list{this->_buildCubeGaussLobattoFormulaList()},
+    m_prism_gauss_formula_list{this->_buildPrismGaussFormulaList()},
+    m_pyramid_gauss_formula_list{this->_buildPyramidGaussFormulaList()},
+    m_tetrahedron_gauss_formula_list{this->_buildTetrahedronGaussFormulaList()}
+{
+  Assert(m_line_gauss_legendre_formula_list.size() * 2 - 1 == TensorialGaussLegendreQuadrature<1>::max_degree);
+  Assert(m_square_gauss_legendre_formula_list.size() * 2 - 1 == TensorialGaussLegendreQuadrature<2>::max_degree);
+  Assert(m_cube_gauss_legendre_formula_list.size() * 2 - 1 == TensorialGaussLegendreQuadrature<3>::max_degree);
+
+  Assert(m_line_gauss_lobatto_formula_list.size() * 2 - 1 == TensorialGaussLobattoQuadrature<1>::max_degree);
+  Assert(m_square_gauss_lobatto_formula_list.size() * 2 - 1 == TensorialGaussLobattoQuadrature<2>::max_degree);
+  Assert(m_cube_gauss_lobatto_formula_list.size() * 2 - 1 == TensorialGaussLobattoQuadrature<3>::max_degree);
+
+  Assert(m_square_gauss_formula_list.size() * 2 - 1 == SquareGaussQuadrature::max_degree);
+  Assert(m_triangle_gauss_formula_list.size() == TriangleGaussQuadrature::max_degree);
+
+  Assert(m_cube_gauss_formula_list.size() * 2 - 1 == CubeGaussQuadrature::max_degree);
+  Assert(m_prism_gauss_formula_list.size() == PrismGaussQuadrature::max_degree);
+  Assert(m_pyramid_gauss_formula_list.size() == PyramidGaussQuadrature::max_degree);
+  Assert(m_tetrahedron_gauss_formula_list.size() == TetrahedronGaussQuadrature::max_degree);
+}
diff --git a/src/analysis/QuadratureManager.hpp b/src/analysis/QuadratureManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b0f81c543c0612181cc5728b2c7ef36fc1e25515
--- /dev/null
+++ b/src/analysis/QuadratureManager.hpp
@@ -0,0 +1,282 @@
+#ifndef QUADRATURE_MANAGER_HPP
+#define QUADRATURE_MANAGER_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <analysis/QuadratureFormula.hpp>
+#include <utils/Array.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/PugsMacros.hpp>
+
+class QuadratureManager
+{
+ private:
+  static QuadratureManager* s_instance;
+
+  Array<const QuadratureFormula<1>> m_line_gauss_legendre_formula_list;
+  Array<const QuadratureFormula<1>> m_line_gauss_lobatto_formula_list;
+
+  Array<const QuadratureFormula<2>> m_square_gauss_formula_list;
+  Array<const QuadratureFormula<2>> m_square_gauss_legendre_formula_list;
+  Array<const QuadratureFormula<2>> m_square_gauss_lobatto_formula_list;
+  Array<const QuadratureFormula<2>> m_triangle_gauss_formula_list;
+
+  Array<const QuadratureFormula<3>> m_cube_gauss_formula_list;
+  Array<const QuadratureFormula<3>> m_cube_gauss_legendre_formula_list;
+  Array<const QuadratureFormula<3>> m_cube_gauss_lobatto_formula_list;
+  Array<const QuadratureFormula<3>> m_prism_gauss_formula_list;
+  Array<const QuadratureFormula<3>> m_pyramid_gauss_formula_list;
+  Array<const QuadratureFormula<3>> m_tetrahedron_gauss_formula_list;
+
+  Array<const QuadratureFormula<1>> _buildLineGaussLobattoFormulaList();
+  Array<const QuadratureFormula<1>> _buildLineGaussLegendreFormulaList();
+
+  Array<const QuadratureFormula<2>> _buildSquareGaussFormulaList();
+  Array<const QuadratureFormula<2>> _buildSquareGaussLegendreFormulaList();
+  Array<const QuadratureFormula<2>> _buildSquareGaussLobattoFormulaList();
+  Array<const QuadratureFormula<2>> _buildTriangleGaussFormulaList();
+
+  Array<const QuadratureFormula<3>> _buildCubeGaussFormulaList();
+  Array<const QuadratureFormula<3>> _buildCubeGaussLegendreFormulaList();
+  Array<const QuadratureFormula<3>> _buildCubeGaussLobattoFormulaList();
+  Array<const QuadratureFormula<3>> _buildPrismGaussFormulaList();
+  Array<const QuadratureFormula<3>> _buildPyramidGaussFormulaList();
+  Array<const QuadratureFormula<3>> _buildTetrahedronGaussFormulaList();
+
+ public:
+  const QuadratureFormula<1>&
+  getLineFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss:
+    case QuadratureType::GaussLegendre: {
+      return m_line_gauss_legendre_formula_list[quadrature_descriptor.degree() / 2];
+      break;
+    }
+    case QuadratureType::GaussLobatto: {
+      return m_line_gauss_lobatto_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    default: {
+      throw UnexpectedError("invalid quadrature type");
+    }
+    }
+  }
+
+  const QuadratureFormula<2>&
+  getTriangleFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_triangle_gauss_formula_list[quadrature_descriptor.degree() - 1];
+    }
+    default: {
+      throw UnexpectedError(quadrature_descriptor.name() + " is not defined on triangles");
+    }
+    }
+  }
+
+  const QuadratureFormula<2>&
+  getSquareFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_square_gauss_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    case QuadratureType::GaussLegendre: {
+      return m_square_gauss_legendre_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    case QuadratureType::GaussLobatto: {
+      return m_square_gauss_lobatto_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    default: {
+      throw UnexpectedError("invalid quadrature type");
+    }
+    }
+  }
+
+  const QuadratureFormula<3>&
+  getTetrahedronFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_tetrahedron_gauss_formula_list[quadrature_descriptor.degree() - 1];
+    }
+    default: {
+      throw UnexpectedError(quadrature_descriptor.name() + " is not defined on tetrahedron");
+    }
+    }
+  }
+
+  const QuadratureFormula<3>&
+  getPrismFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_prism_gauss_formula_list[quadrature_descriptor.degree() - 1];
+    }
+    default: {
+      throw UnexpectedError(quadrature_descriptor.name() + " is not defined on prism");
+    }
+    }
+  }
+
+  const QuadratureFormula<3>&
+  getPyramidFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_pyramid_gauss_formula_list[quadrature_descriptor.degree() - 1];
+    }
+    default: {
+      throw UnexpectedError(quadrature_descriptor.name() + " is not defined on pyramid");
+    }
+    }
+  }
+
+  const QuadratureFormula<3>&
+  getCubeFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_cube_gauss_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    case QuadratureType::GaussLegendre: {
+      return m_cube_gauss_legendre_formula_list[quadrature_descriptor.degree() / 2];
+      break;
+    }
+    case QuadratureType::GaussLobatto: {
+      return m_cube_gauss_lobatto_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    default: {
+      throw UnexpectedError("invalid quadrature type");
+    }
+    }
+  }
+
+  size_t
+  maxLineDegree(const QuadratureType type) const
+  {
+    switch (type) {
+    case QuadratureType::Gauss:
+    case QuadratureType::GaussLegendre: {
+      return m_line_gauss_legendre_formula_list.size() * 2 - 1;
+    }
+    case QuadratureType::GaussLobatto: {
+      return m_line_gauss_lobatto_formula_list.size() * 2 - 1;
+    }
+    default: {
+      throw UnexpectedError("invalid quadrature type");
+    }
+    }
+  }
+
+  size_t
+  maxSquareDegree(const QuadratureType type) const
+  {
+    switch (type) {
+    case QuadratureType::Gauss: {
+      return m_square_gauss_formula_list.size() * 2 - 1;
+    }
+    case QuadratureType::GaussLegendre: {
+      return m_square_gauss_legendre_formula_list.size() * 2 - 1;
+    }
+    case QuadratureType::GaussLobatto: {
+      return m_square_gauss_lobatto_formula_list.size() * 2 - 1;
+    }
+    default: {
+      throw UnexpectedError("invalid quadrature type");
+    }
+    }
+  }
+
+  size_t
+  maxTriangleDegree(const QuadratureType type) const
+  {
+    switch (type) {
+    case QuadratureType::Gauss: {
+      return m_triangle_gauss_formula_list.size();
+    }
+    default: {
+      throw UnexpectedError(name(type) + " is not defined on triangle");
+    }
+    }
+  }
+
+  size_t
+  maxCubeDegree(const QuadratureType type) const
+  {
+    switch (type) {
+    case QuadratureType::Gauss: {
+      return m_cube_gauss_formula_list.size() * 2 - 1;
+    }
+    case QuadratureType::GaussLegendre: {
+      return m_cube_gauss_legendre_formula_list.size() * 2 - 1;
+    }
+    case QuadratureType::GaussLobatto: {
+      return m_cube_gauss_lobatto_formula_list.size() * 2 - 1;
+    }
+    default: {
+      throw UnexpectedError("invalid quadrature type");
+    }
+    }
+  }
+
+  size_t
+  maxPrismDegree(const QuadratureType type) const
+  {
+    switch (type) {
+    case QuadratureType::Gauss: {
+      return m_prism_gauss_formula_list.size();
+    }
+    default: {
+      throw UnexpectedError(::name(type) + " is not defined on prism");
+    }
+    }
+  }
+
+  size_t
+  maxPyramidDegree(const QuadratureType type) const
+  {
+    switch (type) {
+    case QuadratureType::Gauss: {
+      return m_pyramid_gauss_formula_list.size();
+    }
+    default: {
+      throw UnexpectedError(::name(type) + " is not defined on pyramid");
+    }
+    }
+  }
+
+  size_t
+  maxTetrahedronDegree(const QuadratureType type) const
+  {
+    switch (type) {
+    case QuadratureType::Gauss: {
+      return m_tetrahedron_gauss_formula_list.size();
+    }
+    default: {
+      throw UnexpectedError(::name(type) + " is not defined on tetrahedron");
+    }
+    }
+  }
+
+  static void create();
+  static void destroy();
+
+  PUGS_INLINE
+  static const QuadratureManager&
+  instance()
+  {
+    Assert(s_instance != nullptr, "QuadratureManager was not created!");
+    return *s_instance;
+  }
+
+ private:
+  QuadratureManager(const QuadratureManager&) = delete;
+  QuadratureManager(QuadratureManager&&)      = delete;
+
+  QuadratureManager();
+  ~QuadratureManager() = default;
+};
+
+#endif   // QUADRATURE_MANAGER_HPP
diff --git a/src/analysis/QuadratureType.hpp b/src/analysis/QuadratureType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1c67daa0daec9f4692e909194a5f2861245f0c7a
--- /dev/null
+++ b/src/analysis/QuadratureType.hpp
@@ -0,0 +1,36 @@
+#ifndef QUADRATURE_TYPE_HPP
+#define QUADRATURE_TYPE_HPP
+
+#include <utils/Exceptions.hpp>
+#include <utils/PugsMacros.hpp>
+
+#include <string>
+
+enum class QuadratureType
+{
+  Gauss         = 0,
+  GaussLegendre = 1,
+  GaussLobatto  = 2,
+};
+
+PUGS_INLINE
+std::string
+name(QuadratureType type)
+{
+  switch (type) {
+  case QuadratureType::Gauss: {
+    return "Gauss";
+  }
+  case QuadratureType::GaussLegendre: {
+    return "Gauss-Legendre";
+  }
+  case QuadratureType::GaussLobatto: {
+    return "Gauss-Lobatto";
+  }
+  default: {
+    throw UnexpectedError("unknown quadrature type name");
+  }
+  }
+}
+
+#endif   // QUADRATURE_TYPE_HPP
diff --git a/src/analysis/SquareGaussQuadrature.cpp b/src/analysis/SquareGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f0df6af9f2cbbc23318e861403dbaadaa844d749
--- /dev/null
+++ b/src/analysis/SquareGaussQuadrature.cpp
@@ -0,0 +1,327 @@
+#include <analysis/SquareGaussQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+void
+SquareGaussQuadrature::_buildPointAndWeightLists(const size_t degree)
+{
+  using R2 = TinyVector<2>;
+
+  struct Descriptor
+  {
+    int id;
+    double weight;
+    std::vector<double> lambda_list;
+  };
+
+  auto fill_quadrature_points = [](auto descriptor_list, auto& point_list, auto& weight_list) {
+    Assert(point_list.size() == weight_list.size());
+
+    size_t k = 0;
+    for (size_t i = 0; i < descriptor_list.size(); ++i) {
+      const auto [id, w, position_list] = descriptor_list[i];
+
+      switch (id) {
+      case 1: {
+        Assert(position_list.size() == 0);
+
+        point_list[k]  = {0, 0};
+        weight_list[k] = w;
+
+        k += 1;
+        break;
+      }
+      case 2: {
+        Assert(position_list.size() == 1);
+        const double& a = position_list[0];
+
+        point_list[k + 0] = {+a, 0};
+        point_list[k + 1] = {-a, 0};
+        point_list[k + 2] = {0, +a};
+        point_list[k + 3] = {0, -a};
+
+        for (size_t l = 0; l < 4; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 4;
+        break;
+      }
+      case 3: {
+        Assert(position_list.size() == 1);
+        const double& a = position_list[0];
+
+        point_list[k + 0] = {+a, +a};
+        point_list[k + 1] = {+a, -a};
+        point_list[k + 2] = {-a, +a};
+        point_list[k + 3] = {-a, -a};
+
+        for (size_t l = 0; l < 4; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 4;
+        break;
+      }
+      case 4: {
+        Assert(position_list.size() == 2);
+        const double& a = position_list[0];
+        const double& b = position_list[1];
+
+        point_list[k + 0] = {+a, +b};
+        point_list[k + 1] = {+a, -b};
+        point_list[k + 2] = {-a, +b};
+        point_list[k + 3] = {-a, -b};
+        point_list[k + 4] = {+b, +a};
+        point_list[k + 5] = {+b, -a};
+        point_list[k + 6] = {-b, +a};
+        point_list[k + 7] = {-b, -a};
+
+        for (size_t l = 0; l < 8; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 8;
+        break;
+      }
+        // LCOV_EXCL_START
+      default: {
+        throw UnexpectedError("invalid quadrature id");
+      }
+        // LCOV_EXCL_STOP
+      }
+    }
+
+    Assert(k == point_list.size(), "invalid number of quadrature points");
+  };
+
+  switch (degree) {
+  case 0:
+  case 1: {
+    constexpr size_t nb_points = 1;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 4.000000000000000e+00, {}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 2:
+  case 3: {
+    constexpr size_t nb_points = 4;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{3, 1.000000000000000e+00, {5.773502691896257e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 4:
+  case 5: {
+    constexpr size_t nb_points = 8;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 8.163265306122449e-01, {6.831300510639732e-01}},
+       Descriptor{3, 1.836734693877551e-01, {8.819171036881969e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 6:
+  case 7: {
+    constexpr size_t nb_points = 12;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.419753086419753e-01, {9.258200997725514e-01}},
+       Descriptor{3, 2.374317746906302e-01, {8.059797829185987e-01}},
+       Descriptor{3, 5.205929166673945e-01, {3.805544332083157e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 8:
+  case 9: {
+    constexpr size_t nb_points = 20;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 4.541639606867490e-01, {4.889268569743691e-01}},
+       Descriptor{3, 4.273123186577577e-02, {9.396552580968377e-01}},
+       Descriptor{3, 2.142003609268616e-01, {6.908805504863439e-01}},
+       Descriptor{4, 1.444522232603068e-01, {9.186204410567222e-01, 3.448720253644036e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 10:
+  case 11: {
+    constexpr size_t nb_points = 28;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.174004398687120e-01, {7.146178296646060e-01}},
+       Descriptor{3, 2.772741029838511e-01, {2.736572101714596e-01}},
+       Descriptor{3, 2.139336378782481e-01, {6.366039322123010e-01}},
+       Descriptor{4, 4.407456911498309e-02, {9.516303887840335e-01, 8.155654336896384e-01}},
+       Descriptor{4, 1.016213405196113e-01, {3.462072000476454e-01, 9.355678714875911e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 12:
+  case 13: {
+    constexpr size_t nb_points = 37;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 2.999933443589096e-01, {}},
+       Descriptor{2, 3.812862534985274e-02, {9.834613326132455e-01}},
+       Descriptor{2, 1.845354689698072e-01, {6.398614183671097e-01}},
+       Descriptor{3, 3.950714064745244e-02, {9.187784880797114e-01}},
+       Descriptor{3, 2.313985194006854e-01, {3.796285051867438e-01}},
+       Descriptor{3, 1.372420967130035e-01, {6.995542165133511e-01}},
+       Descriptor{4, 3.351978005038143e-02, {6.435973749966181e-01, 9.750688839059836e-01}},
+       Descriptor{4, 1.135751263643542e-01, {3.335398811647831e-01, 8.644276092670619e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 14:
+  case 15: {
+    constexpr size_t nb_points = 48;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.149329272635254e-01, {7.980989878970071e-01}},
+       Descriptor{2, 1.816857589672718e-01, {3.038530263984597e-01}},
+       Descriptor{3, 4.123848378876581e-02, {8.824222701068853e-01}},
+       Descriptor{3, 5.933746485839221e-03, {9.777897995399027e-01}},
+       Descriptor{4, 1.011491434774324e-01, {8.087121358195035e-01, 5.672135733965907e-01}},
+       Descriptor{4, 1.478367216328812e-01, {3.046547990370526e-01, 5.787361940358066e-01}},
+       Descriptor{4, 2.327319414467321e-02, {9.805048437245319e-01, 6.974636319909671e-01}},
+       Descriptor{4, 5.584548249231202e-02, {2.648441558723162e-01, 9.557425198095117e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 16:
+  case 17: {
+    constexpr size_t nb_points = 60;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.388043776872199e-01, {5.450429872091483e-01}},
+       Descriptor{2, 6.534782380746491e-02, {9.174971882191532e-01}},
+       Descriptor{3, 1.428564196221996e-01, {1.925542426140469e-01}},
+       Descriptor{3, 3.671590848525151e-02, {8.713286846954432e-01}},
+       Descriptor{3, 8.625569963814381e-02, {6.943880900895507e-01}},
+       Descriptor{3, 1.345562732119169e-01, {4.576829928831555e-01}},
+       Descriptor{3, 7.655633414909936e-03, {9.692042560915087e-01}},
+       Descriptor{4, 1.031767247849354e-01, {7.620882760801982e-01, 2.794777667151921e-01}},
+       Descriptor{4, 5.482640023729144e-02, {9.073943521982236e-01, 5.468986704143101e-01}},
+       Descriptor{4, 1.511981038825686e-02, {7.612486664390827e-01, 9.837879715015495e-01}},
+       Descriptor{4, 2.078099665596304e-02, {9.870947070447679e-01, 3.028074817888614e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 18:
+  case 19: {
+    constexpr size_t nb_points = 72;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 9.773736469282875e-02, {7.143442595727942e-01}},
+       Descriptor{2, 1.393076129224823e-01, {2.656720521209637e-01}},
+       Descriptor{2, 3.486958349188791e-02, {9.644342692579673e-01}},
+       Descriptor{3, 4.780454716279579e-02, {8.033797294676850e-01}},
+       Descriptor{3, 1.617715177911761e-03, {9.921654058971348e-01}},
+       Descriptor{3, 1.744104803435576e-02, {9.294496027996094e-01}},
+       Descriptor{4, 1.177258328400561e-01, {5.102782573693434e-01, 2.666403145945622e-01}},
+       Descriptor{4, 1.617957761165785e-02, {3.907342057752498e-01, 9.878929331417353e-01}},
+       Descriptor{4, 8.284661898340490e-02, {7.171679213097452e-01, 5.124918772160977e-01}},
+       Descriptor{4, 6.498908149259605e-02, {2.654400078112960e-01, 8.689024341545042e-01}},
+       Descriptor{4, 3.898055239641054e-02, {6.200353986932564e-01, 9.263029558071293e-01}},
+       Descriptor{4, 9.889400934743484e-03, {8.016715847185969e-01, 9.884465306839737e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 20:
+  case 21: {
+    constexpr size_t nb_points = 85;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.351928403561428e-01, {}},
+       Descriptor{2, 1.067941587859404e-01, {4.733510743582242e-01}},
+       Descriptor{2, 6.625720800494439e-02, {8.352784297558683e-01}},
+       Descriptor{3, 1.198844836463405e-01, {2.573719006072290e-01}},
+       Descriptor{3, 8.229461289220009e-03, {9.633378321156234e-01}},
+       Descriptor{3, 3.060364092877565e-02, {8.624519253796515e-01}},
+       Descriptor{3, 9.679179189359521e-02, {4.968979625193457e-01}},
+       Descriptor{3, 6.151634148131656e-02, {7.043321751954006e-01}},
+       Descriptor{4, 8.672849951320823e-02, {2.418781854767020e-01, 6.741462199553178e-01}},
+       Descriptor{4, 5.587911740412735e-02, {4.801569663127951e-01, 8.246473752709207e-01}},
+       Descriptor{4, 3.712882744091382e-02, {9.322419359217540e-01, 2.706991841016649e-01}},
+       Descriptor{4, 2.877560085102053e-02, {9.349023258240106e-01, 6.750395674370753e-01}},
+       Descriptor{4, 1.150952044249317e-02, {9.904554675295242e-01, 4.889162511771669e-01}},
+       Descriptor{4, 7.528482130401646e-03, {8.311352459759014e-01, 9.889606113865724e-01}},
+       Descriptor{4, 1.051230415825108e-02, {9.146960092229130e-02, 9.840171484895990e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  default: {
+    throw NormalError("Gauss quadrature formulae handle degrees up to " +
+                      std::to_string(SquareGaussQuadrature::max_degree) + " on squares");
+  }
+  }
+}
diff --git a/src/analysis/SquareGaussQuadrature.hpp b/src/analysis/SquareGaussQuadrature.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..71ea25a7a7f25c65f862fbddc92f31f4d5057fa4
--- /dev/null
+++ b/src/analysis/SquareGaussQuadrature.hpp
@@ -0,0 +1,39 @@
+#ifndef SQUARE_GAUSS_QUADRATURE_HPP
+#define SQUARE_GAUSS_QUADRATURE_HPP
+
+#include <analysis/QuadratureFormula.hpp>
+
+/**
+ * Defines Square Gauss quadrature on the reference element
+ * \f$]-1,1[^2\f$
+ *
+ * \note formulae are provided by
+ *
+ * ‘On the identification of symmetric quadrature rules for finite
+ * element methods‘ by F.D. Witherden & P.E. Vincent (2015).
+ */
+class SquareGaussQuadrature final : public QuadratureFormula<2>
+{
+ public:
+  constexpr static size_t max_degree = 21;
+
+ private:
+  void _buildPointAndWeightLists(const size_t degree);
+
+ public:
+  SquareGaussQuadrature(SquareGaussQuadrature&&)      = default;
+  SquareGaussQuadrature(const SquareGaussQuadrature&) = default;
+
+ private:
+  friend class QuadratureManager;
+
+  explicit SquareGaussQuadrature(const size_t degree) : QuadratureFormula<2>(QuadratureType::Gauss)
+  {
+    this->_buildPointAndWeightLists(degree);
+  }
+
+  SquareGaussQuadrature()  = delete;
+  ~SquareGaussQuadrature() = default;
+};
+
+#endif   // SQUARE_GAUSS_QUADRATURE_HPP
diff --git a/src/analysis/TensorialGaussLegendreQuadrature.cpp b/src/analysis/TensorialGaussLegendreQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9a4b524c0f70cbc3a68088bd5b14ad17d6c521ea
--- /dev/null
+++ b/src/analysis/TensorialGaussLegendreQuadrature.cpp
@@ -0,0 +1,292 @@
+#include <analysis/TensorialGaussLegendreQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+template <>
+void
+TensorialGaussLegendreQuadrature<1>::_buildPointAndWeightLists(const size_t degree)
+{
+  const size_t nb_points = degree / 2 + 1;
+
+  SmallArray<TinyVector<1>> point_list(nb_points);
+  SmallArray<double> weight_list(nb_points);
+
+  switch (degree) {
+  case 0:
+  case 1: {
+    point_list[0] = 0;
+
+    weight_list[0] = 2;
+    break;
+  }
+  case 2:
+  case 3: {
+    point_list[0] = -0.5773502691896257645091488;
+    point_list[1] = +0.5773502691896257645091488;
+
+    weight_list[0] = 1;
+    weight_list[1] = 1;
+    break;
+  }
+  case 4:
+  case 5: {
+    point_list[0] = -0.7745966692414833770358531;
+    point_list[1] = 0;
+    point_list[2] = +0.7745966692414833770358531;
+
+    weight_list[0] = 0.5555555555555555555555556;
+    weight_list[1] = 0.8888888888888888888888889;
+    weight_list[2] = 0.5555555555555555555555556;
+    break;
+  }
+  case 6:
+  case 7: {
+    point_list[0] = -0.8611363115940525752239465;
+    point_list[1] = -0.3399810435848562648026658;
+    point_list[2] = +0.3399810435848562648026658;
+    point_list[3] = +0.8611363115940525752239465;
+
+    weight_list[0] = 0.3478548451374538573730639;
+    weight_list[1] = 0.6521451548625461426269361;
+    weight_list[2] = 0.6521451548625461426269361;
+    weight_list[3] = 0.3478548451374538573730639;
+    break;
+  }
+  case 8:
+  case 9: {
+    point_list[0] = -0.9061798459386639927976269;
+    point_list[1] = -0.5384693101056830910363144;
+    point_list[2] = 0;
+    point_list[3] = +0.5384693101056830910363144;
+    point_list[4] = +0.9061798459386639927976269;
+
+    weight_list[0] = 0.2369268850561890875142640;
+    weight_list[1] = 0.4786286704993664680412915;
+    weight_list[2] = 0.5688888888888888888888889;
+    weight_list[3] = 0.4786286704993664680412915;
+    weight_list[4] = 0.2369268850561890875142640;
+    break;
+  }
+  case 10:
+  case 11: {
+    point_list[0] = -0.9324695142031520278123016;
+    point_list[1] = -0.6612093864662645136613996;
+    point_list[2] = -0.2386191860831969086305017;
+    point_list[3] = +0.2386191860831969086305017;
+    point_list[4] = +0.6612093864662645136613996;
+    point_list[5] = +0.9324695142031520278123016;
+
+    weight_list[0] = 0.1713244923791703450402961;
+    weight_list[1] = 0.3607615730481386075698335;
+    weight_list[2] = 0.4679139345726910473898703;
+    weight_list[3] = 0.4679139345726910473898703;
+    weight_list[4] = 0.3607615730481386075698335;
+    weight_list[5] = 0.1713244923791703450402961;
+    break;
+  }
+  case 12:
+  case 13: {
+    point_list[0] = -0.9491079123427585245261897;
+    point_list[1] = -0.7415311855993944398638648;
+    point_list[2] = -0.4058451513773971669066064;
+    point_list[3] = 0;
+    point_list[4] = +0.4058451513773971669066064;
+    point_list[5] = +0.7415311855993944398638648;
+    point_list[6] = +0.9491079123427585245261897;
+
+    weight_list[0] = 0.1294849661688696932706114;
+    weight_list[1] = 0.2797053914892766679014678;
+    weight_list[2] = 0.3818300505051189449503698;
+    weight_list[3] = 0.4179591836734693877551020;
+    weight_list[4] = 0.3818300505051189449503698;
+    weight_list[5] = 0.2797053914892766679014678;
+    weight_list[6] = 0.1294849661688696932706114;
+    break;
+  }
+  case 14:
+  case 15: {
+    point_list[0] = -0.9602898564975362316835609;
+    point_list[1] = -0.7966664774136267395915539;
+    point_list[2] = -0.5255324099163289858177390;
+    point_list[3] = -0.1834346424956498049394761;
+    point_list[4] = +0.1834346424956498049394761;
+    point_list[5] = +0.5255324099163289858177390;
+    point_list[6] = +0.7966664774136267395915539;
+    point_list[7] = +0.9602898564975362316835609;
+
+    weight_list[0] = 0.1012285362903762591525314;
+    weight_list[1] = 0.2223810344533744705443560;
+    weight_list[2] = 0.3137066458778872873379622;
+    weight_list[3] = 0.3626837833783619829651504;
+    weight_list[4] = 0.3626837833783619829651504;
+    weight_list[5] = 0.3137066458778872873379622;
+    weight_list[6] = 0.2223810344533744705443560;
+    weight_list[7] = 0.1012285362903762591525314;
+    break;
+  }
+  case 16:
+  case 17: {
+    point_list[0] = -0.9681602395076260898355762;
+    point_list[1] = -0.8360311073266357942994298;
+    point_list[2] = -0.6133714327005903973087020;
+    point_list[3] = -0.3242534234038089290385380;
+    point_list[4] = 0;
+    point_list[5] = +0.3242534234038089290385380;
+    point_list[6] = +0.6133714327005903973087020;
+    point_list[7] = +0.8360311073266357942994298;
+    point_list[8] = +0.9681602395076260898355762;
+
+    weight_list[0] = 0.0812743883615744119718922;
+    weight_list[1] = 0.1806481606948574040584720;
+    weight_list[2] = 0.2606106964029354623187429;
+    weight_list[3] = 0.3123470770400028400686304;
+    weight_list[4] = 0.3302393550012597631645251;
+    weight_list[5] = 0.3123470770400028400686304;
+    weight_list[6] = 0.2606106964029354623187429;
+    weight_list[7] = 0.1806481606948574040584720;
+    weight_list[8] = 0.0812743883615744119718922;
+    break;
+  }
+  case 18:
+  case 19: {
+    point_list[0] = -0.9739065285171717200779640;
+    point_list[1] = -0.8650633666889845107320967;
+    point_list[2] = -0.6794095682990244062343274;
+    point_list[3] = -0.4333953941292471907992659;
+    point_list[4] = -0.1488743389816312108848260;
+    point_list[5] = +0.1488743389816312108848260;
+    point_list[6] = +0.4333953941292471907992659;
+    point_list[7] = +0.6794095682990244062343274;
+    point_list[8] = +0.8650633666889845107320967;
+    point_list[9] = +0.9739065285171717200779640;
+
+    weight_list[0] = 0.0666713443086881375935688;
+    weight_list[1] = 0.1494513491505805931457763;
+    weight_list[2] = 0.2190863625159820439955349;
+    weight_list[3] = 0.2692667193099963550912269;
+    weight_list[4] = 0.2955242247147528701738930;
+    weight_list[5] = 0.2955242247147528701738930;
+    weight_list[6] = 0.2692667193099963550912269;
+    weight_list[7] = 0.2190863625159820439955349;
+    weight_list[8] = 0.1494513491505805931457763;
+    weight_list[9] = 0.0666713443086881375935688;
+    break;
+  }
+  case 20:
+  case 21: {
+    point_list[0]  = -0.9782286581460569928039380;
+    point_list[1]  = -0.8870625997680952990751578;
+    point_list[2]  = -0.7301520055740493240934163;
+    point_list[3]  = -0.5190961292068118159257257;
+    point_list[4]  = -0.2695431559523449723315320;
+    point_list[5]  = 0;
+    point_list[6]  = +0.2695431559523449723315320;
+    point_list[7]  = +0.5190961292068118159257257;
+    point_list[8]  = +0.7301520055740493240934163;
+    point_list[9]  = +0.8870625997680952990751578;
+    point_list[10] = +0.9782286581460569928039380;
+
+    weight_list[0]  = 0.0556685671161736664827537;
+    weight_list[1]  = 0.1255803694649046246346940;
+    weight_list[2]  = 0.1862902109277342514260980;
+    weight_list[3]  = 0.2331937645919904799185237;
+    weight_list[4]  = 0.2628045445102466621806889;
+    weight_list[5]  = 0.2729250867779006307144835;
+    weight_list[6]  = 0.2628045445102466621806889;
+    weight_list[7]  = 0.2331937645919904799185237;
+    weight_list[8]  = 0.1862902109277342514260980;
+    weight_list[9]  = 0.1255803694649046246346940;
+    weight_list[10] = 0.0556685671161736664827537;
+    break;
+  }
+  case 22:
+  case 23: {
+    point_list[0]  = -0.9815606342467192506905491;
+    point_list[1]  = -0.9041172563704748566784659;
+    point_list[2]  = -0.7699026741943046870368938;
+    point_list[3]  = -0.5873179542866174472967024;
+    point_list[4]  = -0.3678314989981801937526915;
+    point_list[5]  = -0.1252334085114689154724414;
+    point_list[6]  = +0.1252334085114689154724414;
+    point_list[7]  = +0.3678314989981801937526915;
+    point_list[8]  = +0.5873179542866174472967024;
+    point_list[9]  = +0.7699026741943046870368938;
+    point_list[10] = +0.9041172563704748566784659;
+    point_list[11] = +0.9815606342467192506905491;
+
+    weight_list[0]  = 0.0471753363865118271946160;
+    weight_list[1]  = 0.1069393259953184309602547;
+    weight_list[2]  = 0.1600783285433462263346525;
+    weight_list[3]  = 0.2031674267230659217490645;
+    weight_list[4]  = 0.2334925365383548087608499;
+    weight_list[5]  = 0.2491470458134027850005624;
+    weight_list[6]  = 0.2491470458134027850005624;
+    weight_list[7]  = 0.2334925365383548087608499;
+    weight_list[8]  = 0.2031674267230659217490645;
+    weight_list[9]  = 0.1600783285433462263346525;
+    weight_list[10] = 0.1069393259953184309602547;
+    weight_list[11] = 0.0471753363865118271946160;
+    break;
+  }
+  default: {
+    throw NormalError("Gauss-Legendre quadrature formulae handle degrees up to " +
+                      std::to_string(TensorialGaussLegendreQuadrature<1>::max_degree));
+  }
+  }
+
+  m_point_list  = point_list;
+  m_weight_list = weight_list;
+}
+
+template <>
+void
+TensorialGaussLegendreQuadrature<2>::_buildPointAndWeightLists(const size_t degree)
+{
+  const size_t nb_points_1d = degree / 2 + 1;
+  const size_t nb_points    = nb_points_1d * nb_points_1d;
+
+  SmallArray<TinyVector<2>> point_list(nb_points);
+  SmallArray<double> weight_list(nb_points);
+
+  TensorialGaussLegendreQuadrature<1> gauss_legendre_1d(degree);
+  const auto& point_list_1d  = gauss_legendre_1d.pointList();
+  const auto& weight_list_1d = gauss_legendre_1d.weightList();
+
+  size_t l = 0;
+  for (size_t i = 0; i < nb_points_1d; ++i) {
+    for (size_t j = 0; j < nb_points_1d; ++j, ++l) {
+      point_list[l]  = TinyVector<2>{point_list_1d[i][0], point_list_1d[j][0]};
+      weight_list[l] = weight_list_1d[i] * weight_list_1d[j];
+    }
+  }
+
+  this->m_point_list  = point_list;
+  this->m_weight_list = weight_list;
+}
+
+template <>
+void
+TensorialGaussLegendreQuadrature<3>::_buildPointAndWeightLists(const size_t degree)
+{
+  const size_t nb_points_1d = degree / 2 + 1;
+  const size_t nb_points    = nb_points_1d * nb_points_1d * nb_points_1d;
+
+  SmallArray<TinyVector<3>> point_list(nb_points);
+  SmallArray<double> weight_list(nb_points);
+
+  TensorialGaussLegendreQuadrature<1> gauss_legendre_1d(degree);
+  const auto& point_list_1d  = gauss_legendre_1d.pointList();
+  const auto& weight_list_1d = gauss_legendre_1d.weightList();
+
+  size_t l = 0;
+  for (size_t i = 0; i < nb_points_1d; ++i) {
+    for (size_t j = 0; j < nb_points_1d; ++j) {
+      for (size_t k = 0; k < nb_points_1d; ++k, ++l) {
+        point_list[l]  = TinyVector<3>{point_list_1d[i][0], point_list_1d[j][0], point_list_1d[k][0]};
+        weight_list[l] = weight_list_1d[i] * weight_list_1d[j] * weight_list_1d[k];
+      }
+    }
+  }
+
+  this->m_point_list  = point_list;
+  this->m_weight_list = weight_list;
+}
diff --git a/src/analysis/TensorialGaussLegendreQuadrature.hpp b/src/analysis/TensorialGaussLegendreQuadrature.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..51aadae6f64b13f2f8c3b68a59665174ad74150e
--- /dev/null
+++ b/src/analysis/TensorialGaussLegendreQuadrature.hpp
@@ -0,0 +1,40 @@
+#ifndef TENSORIAL_GAUSS_LEGENDRE_QUADRATURE_HPP
+#define TENSORIAL_GAUSS_LEGENDRE_QUADRATURE_HPP
+
+#include <analysis/QuadratureFormula.hpp>
+
+/**
+ * Defines Gauss Legendre quadrature on the reference element
+ * \f$]-1,1[^d\f$, where \f$d\f$ denotes the \a Dimension.
+ *
+ * \note formulae are extracted from High-order Finite Element Method [2004 -  Chapman & Hall]
+ */
+template <size_t Dimension>
+class TensorialGaussLegendreQuadrature final : public QuadratureFormula<Dimension>
+{
+ public:
+  constexpr static size_t max_degree = 23;
+
+ private:
+  void _buildPointAndWeightLists(const size_t degree);
+
+ public:
+  TensorialGaussLegendreQuadrature(TensorialGaussLegendreQuadrature&&)      = default;
+  TensorialGaussLegendreQuadrature(const TensorialGaussLegendreQuadrature&) = default;
+
+ private:
+  friend class QuadratureManager;
+
+  template <size_t D>
+  friend class TensorialGaussLegendreQuadrature;
+
+  explicit TensorialGaussLegendreQuadrature(const size_t degree) : QuadratureFormula<Dimension>(QuadratureType::Gauss)
+  {
+    this->_buildPointAndWeightLists(degree);
+  }
+
+  TensorialGaussLegendreQuadrature()  = delete;
+  ~TensorialGaussLegendreQuadrature() = default;
+};
+
+#endif   // TENSORIAL_GAUSS_LEGENDRE_QUADRATURE_HPP
diff --git a/src/analysis/TensorialGaussLobattoQuadrature.cpp b/src/analysis/TensorialGaussLobattoQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3ab97460b55dad3a2a48a24db56f5c575542361e
--- /dev/null
+++ b/src/analysis/TensorialGaussLobattoQuadrature.cpp
@@ -0,0 +1,181 @@
+#include <analysis/TensorialGaussLobattoQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+template <>
+void
+TensorialGaussLobattoQuadrature<1>::_buildPointAndWeightLists(const size_t degree)
+{
+  const size_t nb_points = degree / 2 + 2;
+
+  SmallArray<TinyVector<1>> point_list(nb_points);
+  SmallArray<double> weight_list(nb_points);
+
+  switch (degree) {
+  case 0:
+  case 1: {
+    point_list[0] = -1;
+    point_list[1] = +1;
+
+    weight_list[0] = 1;
+    weight_list[1] = 1;
+    break;
+  }
+  case 2:
+  case 3: {
+    point_list[0] = -1;
+    point_list[1] = 0;
+    point_list[2] = +1;
+
+    weight_list[0] = 0.3333333333333333333333333;
+    weight_list[1] = 1.3333333333333333333333333;
+    weight_list[2] = 0.3333333333333333333333333;
+    break;
+  }
+  case 4:
+  case 5: {
+    point_list[0] = -1;
+    point_list[1] = -0.4472135954999579392818347;
+    point_list[2] = +0.4472135954999579392818347;
+    point_list[3] = +1;
+
+    weight_list[0] = 0.1666666666666666666666667;
+    weight_list[1] = 0.8333333333333333333333333;
+    weight_list[2] = 0.8333333333333333333333333;
+    weight_list[3] = 0.1666666666666666666666667;
+    break;
+  }
+  case 6:
+  case 7: {
+    point_list[0] = -1;
+    point_list[1] = -0.6546536707079771437982925;
+    point_list[2] = 0;
+    point_list[3] = +0.6546536707079771437982925;
+    point_list[4] = +1;
+
+    weight_list[0] = 0.1;
+    weight_list[1] = 0.5444444444444444444444444;
+    weight_list[2] = 0.7111111111111111111111111;
+    weight_list[3] = 0.5444444444444444444444444;
+    weight_list[4] = 0.1;
+    break;
+  }
+  case 8:
+  case 9: {
+    point_list[0] = -1;
+    point_list[1] = -0.7650553239294646928510030;
+    point_list[2] = -0.2852315164806450963141510;
+    point_list[3] = +0.2852315164806450963141510;
+    point_list[4] = +0.7650553239294646928510030;
+    point_list[5] = +1;
+
+    weight_list[0] = 0.0666666666666666666666667;
+    weight_list[1] = 0.3784749562978469803166128;
+    weight_list[2] = 0.5548583770354863530167205;
+    weight_list[3] = 0.5548583770354863530167205;
+    weight_list[4] = 0.3784749562978469803166128;
+    weight_list[5] = 0.0666666666666666666666667;
+    break;
+  }
+  case 10:
+  case 11: {
+    point_list[0] = -1;
+    point_list[1] = -0.8302238962785669298720322;
+    point_list[2] = -0.4688487934707142138037719;
+    point_list[3] = 0;
+    point_list[4] = +0.4688487934707142138037719;
+    point_list[5] = +0.8302238962785669298720322;
+    point_list[6] = +1;
+
+    weight_list[0] = 0.0476190476190476190476190;
+    weight_list[1] = 0.2768260473615659480107004;
+    weight_list[2] = 0.4317453812098626234178710;
+    weight_list[3] = 0.4876190476190476190476190;
+    weight_list[4] = 0.4317453812098626234178710;
+    weight_list[5] = 0.2768260473615659480107004;
+    weight_list[6] = 0.0476190476190476190476190;
+    break;
+  }
+  case 12:
+  case 13: {
+    point_list[0] = -1;
+    point_list[1] = -0.8717401485096066153374457;
+    point_list[2] = -0.5917001814331423021445107;
+    point_list[3] = -0.2092992179024788687686573;
+    point_list[4] = +0.2092992179024788687686573;
+    point_list[5] = +0.5917001814331423021445107;
+    point_list[6] = +0.8717401485096066153374457;
+    point_list[7] = +1;
+
+    weight_list[0] = 0.0357142857142857142857143;
+    weight_list[1] = 0.2107042271435060393829921;
+    weight_list[2] = 0.3411226924835043647642407;
+    weight_list[3] = 0.4124587946587038815670530;
+    weight_list[4] = 0.4124587946587038815670530;
+    weight_list[5] = 0.3411226924835043647642407;
+    weight_list[6] = 0.2107042271435060393829921;
+    weight_list[7] = 0.0357142857142857142857143;
+    break;
+  }
+  default: {
+    throw NormalError("Gauss-Lobatto quadrature formulae handle degrees up to " +
+                      std::to_string(TensorialGaussLobattoQuadrature<1>::max_degree));
+  }
+  }
+
+  m_point_list  = point_list;
+  m_weight_list = weight_list;
+}
+
+template <>
+void
+TensorialGaussLobattoQuadrature<2>::_buildPointAndWeightLists(const size_t degree)
+{
+  const size_t nb_points_1d = degree / 2 + 2;
+  const size_t nb_points    = nb_points_1d * nb_points_1d;
+
+  SmallArray<TinyVector<2>> point_list(nb_points);
+  SmallArray<double> weight_list(nb_points);
+
+  TensorialGaussLobattoQuadrature<1> gauss_lobatto_1d(degree);
+  const auto& point_list_1d  = gauss_lobatto_1d.pointList();
+  const auto& weight_list_1d = gauss_lobatto_1d.weightList();
+
+  size_t l = 0;
+  for (size_t i = 0; i < nb_points_1d; ++i) {
+    for (size_t j = 0; j < nb_points_1d; ++j, ++l) {
+      point_list[l]  = TinyVector<2>{point_list_1d[i][0], point_list_1d[j][0]};
+      weight_list[l] = weight_list_1d[i] * weight_list_1d[j];
+    }
+  }
+
+  this->m_point_list  = point_list;
+  this->m_weight_list = weight_list;
+}
+
+template <>
+void
+TensorialGaussLobattoQuadrature<3>::_buildPointAndWeightLists(const size_t degree)
+{
+  const size_t nb_points_1d = degree / 2 + 2;
+  const size_t nb_points    = nb_points_1d * nb_points_1d * nb_points_1d;
+
+  SmallArray<TinyVector<3>> point_list(nb_points);
+  SmallArray<double> weight_list(nb_points);
+
+  TensorialGaussLobattoQuadrature<1> gauss_lobatto_1d(degree);
+  const auto& point_list_1d  = gauss_lobatto_1d.pointList();
+  const auto& weight_list_1d = gauss_lobatto_1d.weightList();
+
+  size_t l = 0;
+  for (size_t i = 0; i < nb_points_1d; ++i) {
+    for (size_t j = 0; j < nb_points_1d; ++j) {
+      for (size_t k = 0; k < nb_points_1d; ++k, ++l) {
+        point_list[l]  = TinyVector<3>{point_list_1d[i][0], point_list_1d[j][0], point_list_1d[k][0]};
+        weight_list[l] = weight_list_1d[i] * weight_list_1d[j] * weight_list_1d[k];
+      }
+    }
+  }
+
+  this->m_point_list  = point_list;
+  this->m_weight_list = weight_list;
+}
diff --git a/src/analysis/TensorialGaussLobattoQuadrature.hpp b/src/analysis/TensorialGaussLobattoQuadrature.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1a578bbbc1af1d7d4efdae4183cea1e3c6fb76ff
--- /dev/null
+++ b/src/analysis/TensorialGaussLobattoQuadrature.hpp
@@ -0,0 +1,40 @@
+#ifndef TENSORIAL_GAUSS_LOBATTO_QUADRATURE_HPP
+#define TENSORIAL_GAUSS_LOBATTO_QUADRATURE_HPP
+
+#include <analysis/QuadratureFormula.hpp>
+
+/**
+ * Defines Gauss Lobatto quadrature on the reference element
+ * \f$]-1,1[^d\f$, where \f$d\f$ denotes the \a Dimension.
+ *
+ * \note formulae are extracted from High-order Finite Element Method [2004 -  Chapman & Hall]
+ */
+template <size_t Dimension>
+class TensorialGaussLobattoQuadrature final : public QuadratureFormula<Dimension>
+{
+ public:
+  constexpr static size_t max_degree = 13;
+
+ private:
+  void _buildPointAndWeightLists(const size_t degree);
+
+ public:
+  TensorialGaussLobattoQuadrature(TensorialGaussLobattoQuadrature&&)      = default;
+  TensorialGaussLobattoQuadrature(const TensorialGaussLobattoQuadrature&) = default;
+
+ private:
+  friend class QuadratureManager;
+
+  template <size_t D>
+  friend class TensorialGaussLobattoQuadrature;
+
+  explicit TensorialGaussLobattoQuadrature(const size_t degree) : QuadratureFormula<Dimension>(QuadratureType::Gauss)
+  {
+    this->_buildPointAndWeightLists(degree);
+  }
+
+  TensorialGaussLobattoQuadrature()  = delete;
+  ~TensorialGaussLobattoQuadrature() = default;
+};
+
+#endif   // TENSORIAL_GAUSS_LOBATTO_QUADRATURE_HPP
diff --git a/src/analysis/TetrahedronGaussQuadrature.cpp b/src/analysis/TetrahedronGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..644e2e29387821a274fa2ae8dd2861c60f2c4abc
--- /dev/null
+++ b/src/analysis/TetrahedronGaussQuadrature.cpp
@@ -0,0 +1,688 @@
+#include <analysis/TetrahedronGaussQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+void
+TetrahedronGaussQuadrature::_buildPointAndWeightLists(const size_t degree)
+{
+  using R3 = TinyVector<3>;
+
+  struct Descriptor
+  {
+    int id;
+    double weight;
+    std::vector<double> lambda_list;
+  };
+
+  auto fill_quadrature_points = [](auto descriptor_list, auto& point_list, auto& weight_list) {
+    Assert(point_list.size() == weight_list.size());
+
+    const R3 A = {0, 0, 0};
+    const R3 B = {1, 0, 0};
+    const R3 C = {0, 1, 0};
+    const R3 D = {0, 0, 1};
+
+    size_t k = 0;
+    for (size_t i = 0; i < descriptor_list.size(); ++i) {
+      const auto [id, unit_weight, lambda_list] = descriptor_list[i];
+
+      const double w = (1. / 6) * unit_weight;
+
+      switch (id) {
+      case 1: {
+        Assert(lambda_list.size() == 0);
+        point_list[k]  = (1. / 4) * (A + B + C + D);
+        weight_list[k] = w;
+        ++k;
+        break;
+      }
+      case 2: {
+        Assert(lambda_list.size() == 1);
+        const double l0 = lambda_list[0];
+        const double l1 = 1 - 3 * l0;
+
+        point_list[k + 0] = l0 * A + l0 * B + l0 * C + l1 * D;
+        point_list[k + 1] = l0 * A + l0 * B + l1 * C + l0 * D;
+        point_list[k + 2] = l0 * A + l1 * B + l0 * C + l0 * D;
+        point_list[k + 3] = l1 * A + l0 * B + l0 * C + l0 * D;
+
+        for (size_t l = 0; l < 4; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 4;
+        break;
+      }
+      case 3: {
+        Assert(lambda_list.size() == 1);
+        const double l0 = lambda_list[0];
+        const double l1 = 0.5 - l0;
+
+        point_list[k + 0] = l0 * A + l0 * B + l1 * C + l1 * D;
+        point_list[k + 1] = l0 * A + l1 * B + l0 * C + l1 * D;
+        point_list[k + 2] = l0 * A + l1 * B + l1 * C + l0 * D;
+        point_list[k + 3] = l1 * A + l0 * B + l0 * C + l1 * D;
+        point_list[k + 4] = l1 * A + l0 * B + l1 * C + l0 * D;
+        point_list[k + 5] = l1 * A + l1 * B + l0 * C + l0 * D;
+
+        for (size_t l = 0; l < 6; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 6;
+        break;
+      }
+      case 4: {
+        Assert(lambda_list.size() == 2);
+        const double l0 = lambda_list[0];
+        const double l1 = lambda_list[1];
+        const double l2 = 1 - 2 * l0 - l1;
+
+        point_list[k + 0]  = l0 * A + l0 * B + l1 * C + l2 * D;
+        point_list[k + 1]  = l0 * A + l0 * B + l2 * C + l1 * D;
+        point_list[k + 2]  = l0 * A + l1 * B + l0 * C + l2 * D;
+        point_list[k + 3]  = l0 * A + l1 * B + l2 * C + l0 * D;
+        point_list[k + 4]  = l0 * A + l2 * B + l0 * C + l1 * D;
+        point_list[k + 5]  = l0 * A + l2 * B + l1 * C + l0 * D;
+        point_list[k + 6]  = l1 * A + l0 * B + l0 * C + l2 * D;
+        point_list[k + 7]  = l1 * A + l0 * B + l2 * C + l0 * D;
+        point_list[k + 8]  = l1 * A + l2 * B + l0 * C + l0 * D;
+        point_list[k + 9]  = l2 * A + l0 * B + l0 * C + l1 * D;
+        point_list[k + 10] = l2 * A + l0 * B + l1 * C + l0 * D;
+        point_list[k + 11] = l2 * A + l1 * B + l0 * C + l0 * D;
+
+        for (size_t l = 0; l < 12; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 12;
+        break;
+      }
+      case 5: {
+        Assert(lambda_list.size() == 3);
+        const double l0 = lambda_list[0];
+        const double l1 = lambda_list[1];
+        const double l2 = lambda_list[2];
+        const double l3 = 1 - l0 - l1 - l2;
+
+        point_list[k + 0]  = l0 * A + l1 * B + l2 * C + l3 * D;
+        point_list[k + 1]  = l0 * A + l1 * B + l3 * C + l2 * D;
+        point_list[k + 2]  = l0 * A + l2 * B + l1 * C + l3 * D;
+        point_list[k + 3]  = l0 * A + l2 * B + l3 * C + l1 * D;
+        point_list[k + 4]  = l0 * A + l3 * B + l1 * C + l2 * D;
+        point_list[k + 5]  = l0 * A + l3 * B + l2 * C + l1 * D;
+        point_list[k + 6]  = l1 * A + l0 * B + l2 * C + l3 * D;
+        point_list[k + 7]  = l1 * A + l0 * B + l3 * C + l2 * D;
+        point_list[k + 8]  = l1 * A + l2 * B + l0 * C + l3 * D;
+        point_list[k + 9]  = l1 * A + l2 * B + l3 * C + l0 * D;
+        point_list[k + 10] = l1 * A + l3 * B + l0 * C + l2 * D;
+        point_list[k + 11] = l1 * A + l3 * B + l2 * C + l0 * D;
+        point_list[k + 12] = l2 * A + l0 * B + l1 * C + l3 * D;
+        point_list[k + 13] = l2 * A + l0 * B + l3 * C + l1 * D;
+        point_list[k + 14] = l2 * A + l1 * B + l0 * C + l3 * D;
+        point_list[k + 15] = l2 * A + l1 * B + l3 * C + l0 * D;
+        point_list[k + 16] = l2 * A + l3 * B + l0 * C + l1 * D;
+        point_list[k + 17] = l2 * A + l3 * B + l1 * C + l0 * D;
+        point_list[k + 18] = l3 * A + l0 * B + l1 * C + l2 * D;
+        point_list[k + 19] = l3 * A + l0 * B + l2 * C + l1 * D;
+        point_list[k + 20] = l3 * A + l1 * B + l0 * C + l2 * D;
+        point_list[k + 21] = l3 * A + l1 * B + l2 * C + l0 * D;
+        point_list[k + 22] = l3 * A + l2 * B + l0 * C + l1 * D;
+        point_list[k + 23] = l3 * A + l2 * B + l1 * C + l0 * D;
+
+        for (size_t l = 0; l < 24; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 24;
+        break;
+      }
+        // LCOV_EXCL_START
+      default: {
+        throw UnexpectedError("invalid quadrature id");
+      }
+        // LCOV_EXCL_STOP
+      }
+    }
+  };
+
+  switch (degree) {
+  case 0:
+  case 1: {
+    constexpr size_t nb_points = 1;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.000000000000000e+00, {}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 2: {
+    constexpr size_t nb_points = 4;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.500000000000000e-01, {1.381966011250105e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 3: {
+    constexpr size_t nb_points = 8;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.274913115575064e-01, {3.286811466653490e-01}},
+       Descriptor{2, 1.225086884424936e-01, {1.119207275092916e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 4: {
+    constexpr size_t nb_points = 14;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 6.540916363445218e-02, {3.095821653179519e-01}},
+       Descriptor{2, 5.935526423100475e-02, {8.344277230397758e-02}},
+       Descriptor{3, 8.349038142302871e-02, {4.227790459299413e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 5: {
+    constexpr size_t nb_points = 14;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.126879257180158e-01, {3.108859192633006e-01}},
+       Descriptor{2, 7.349304311636196e-02, {9.273525031089122e-02}},
+       Descriptor{3, 4.254602077708147e-02, {4.544962958743504e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 6: {
+    constexpr size_t nb_points = 24;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.007721105532064e-02, {4.067395853461135e-02}},
+       Descriptor{2, 5.535718154365472e-02, {3.223378901422755e-01}},
+       Descriptor{2, 3.992275025816749e-02, {2.146028712591520e-01}},
+       Descriptor{4, 4.821428571428572e-02, {6.366100187501753e-02, 6.030056647916492e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 7: {
+    constexpr size_t nb_points = 35;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 9.548528946413085e-02, {}}, Descriptor{2, 4.232958120996703e-02, {3.157011497782028e-01}},
+       Descriptor{3, 3.189692783285758e-02, {4.495101774016036e-01}},
+       Descriptor{4, 8.110770829903342e-03, {2.126547254148325e-02, 8.108302410985485e-01}},
+       Descriptor{4, 3.720713072833462e-02, {1.888338310260010e-01, 5.751716375870001e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 8: {
+    constexpr size_t nb_points = 46;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 4.781298803430666e-02, {1.868127582707210e-01}},
+       Descriptor{2, 2.972785408679422e-02, {1.144624067612305e-01}},
+       Descriptor{2, 4.352732421897527e-02, {3.138065922724016e-01}},
+       Descriptor{2, 8.632736020814975e-03, {4.457737944884625e-02}},
+       Descriptor{3, 3.689054126986636e-02, {6.548349054384239e-02}},
+       Descriptor{4, 1.449702671085950e-02, {2.038931746621103e-01, 5.073320750421590e-03}},
+       Descriptor{4, 7.157401867243608e-03, {2.124777966957167e-02, 7.146769263933049e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 9: {
+    constexpr size_t nb_points = 59;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 5.686662425355173e-02, {}},
+       Descriptor{2, 2.569427668952318e-02, {1.679066052367428e-01}},
+       Descriptor{2, 2.298923353028370e-03, {9.143627051407625e-02}},
+       Descriptor{2, 3.045603866759101e-02, {3.218556648176533e-01}},
+       Descriptor{2, 7.123722340238881e-03, {4.183769590036560e-02}},
+       Descriptor{3, 3.684365548094388e-02, {1.067294385748464e-01}},
+       Descriptor{4, 1.032205678220985e-02, {3.395716818308889e-02, 7.183930939814244e-01}},
+       Descriptor{4, 7.634887153980855e-03, {4.606581810547776e-01, 7.868363447668793e-02}},
+       Descriptor{4, 2.035802261874757e-02, {1.843879435000152e-01, 5.972107227618499e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 10: {
+    constexpr size_t nb_points = 81;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 5.165089225609747e-02, {}},
+       Descriptor{2, 1.346797267166026e-02, {7.255175741743780e-02}},
+       Descriptor{2, 2.646691924968953e-02, {3.078959478669229e-01}},
+       Descriptor{3, 5.107047828143525e-03, {2.371913960722093e-02}},
+       Descriptor{3, 2.364999615824560e-02, {4.034202269308927e-01}},
+       Descriptor{4, 2.531736665096822e-02, {2.017386068476799e-01, 7.385418101848620e-02}},
+       Descriptor{4, 5.416263407447926e-03, {1.548397007438618e-01, 6.875048558830396e-01}},
+       Descriptor{4, 8.153659012054226e-03, {4.125991504993639e-01, 1.741437164939321e-01}},
+       Descriptor{4, 1.112597175961278e-02, {3.889072755780651e-02, 2.657575065627199e-01}},
+       Descriptor{4, 1.325678848264236e-03, {5.604847951021338e-03, 9.324134903525841e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 11: {
+    constexpr size_t nb_points = 110;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.468934358613075e-02, {2.993130923993425e-01}},
+       Descriptor{2, 3.380869530621681e-03, {3.263284396518180e-02}},
+       Descriptor{3, 1.676676867722563e-02, {1.742044154846892e-01}},
+       Descriptor{3, 1.932364034775851e-02, {4.012153758583749e-01}},
+       Descriptor{3, 1.653253610375797e-03, {4.942980948772996e-01}},
+       Descriptor{4, 9.799971247032874e-03, {1.231206549747224e-01, 7.273303463463835e-01}},
+       Descriptor{4, 2.036278491304826e-03, {1.300387048559024e-02, 1.702280734892642e-01}},
+       Descriptor{4, 8.207613752916718e-03, {4.293540117192574e-02, 6.182598097891158e-01}},
+       Descriptor{4, 1.510498736666349e-02, {1.844240320470778e-01, 5.399001168557016e-01}},
+       Descriptor{4, 1.274239266610639e-02, {2.591694074642826e-01, 4.554353196219595e-01}},
+       Descriptor{5, 5.273427059689129e-03, {5.351420009314595e-01, 1.097757236596031e-02, 3.414518661958190e-01}}
+
+      };
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 12: {
+    constexpr size_t nb_points = 168;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.761491553503257e-02, {2.135679944533018e-01}},
+       Descriptor{2, 8.351235933432746e-03, {8.080469951147343e-02}},
+       Descriptor{2, 1.777571920669439e-02, {1.460894685275485e-01}},
+       Descriptor{3, 1.016802439757214e-02, {4.359346229622011e-01}},
+       Descriptor{3, 1.574040002395383e-02, {3.723816950753983e-01}},
+       Descriptor{4, 1.511281145864287e-03, {1.481472606744865e-02, 6.940351772721454e-01}},
+       Descriptor{4, 8.726569887485608e-04, {4.406791967562980e-02, 5.787943875724942e-04}},
+       Descriptor{4, 3.712882661457528e-03, {2.900481455515819e-02, 7.927838364729656e-01}},
+       Descriptor{4, 2.358314255727376e-03, {1.384125788015036e-01, 7.217318354185758e-01}},
+       Descriptor{5, 4.490847036271700e-03, {1.155183527100142e-02, 2.002685156661767e-01, 4.459556015690982e-01}},
+       Descriptor{5, 7.549616048899519e-03, {7.295863195082626e-02, 2.545920450251540e-01, 4.229332866644536e-01}},
+       Descriptor{5, 1.850514589067692e-03, {5.001810761518710e-02, 3.937338659984053e-01, 5.538497716858680e-01}},
+       Descriptor{5, 9.780703581954099e-03, {2.518367824271116e-01, 3.608735666657864e-02, 5.985486290544021e-01}}
+
+      };
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 13: {
+    constexpr size_t nb_points = 172;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 8.606417789141471e-04, {2.023816786180974e-02}},
+       Descriptor{2, 2.260732681773922e-02, {2.890147352435263e-01}},
+       Descriptor{2, 9.680037326838918e-03, {9.402870008212708e-02}},
+       Descriptor{2, 7.170940325572750e-03, {1.976498544437255e-01}},
+       Descriptor{3, 7.784870979280401e-03, {4.046189676018364e-02}},
+       Descriptor{3, 5.120271481716123e-04, {4.999998048519694e-01}},
+       Descriptor{4, 8.880272171422971e-03, {6.752926495280243e-02, 6.118971302042935e-01}},
+       Descriptor{4, 1.062144779049374e-02, {1.360105145132029e-01, 4.868308338798159e-01}},
+       Descriptor{4, 5.873089331995857e-03, {2.780783459563702e-01, 4.273786080349000e-01}},
+       Descriptor{4, 9.235535397556015e-03, {1.967550197192861e-01, 5.667528680967985e-01}},
+       Descriptor{4, 1.456732207916522e-02, {3.862498629203497e-01, 6.201970724197543e-02}},
+       Descriptor{4, 2.658739862173390e-03, {2.353551905465277e-02, 1.086607820833199e-01}},
+       Descriptor{5, 3.221852775001763e-03, {3.362108930747719e-01, 5.446964977019162e-01, 1.155925493534321e-01}},
+       Descriptor{5, 1.464206741103990e-03, {6.859816301235152e-01, 2.886160494075550e-02, 2.745833302490004e-01}},
+       Descriptor{5, 2.268354927450138e-03, {7.356772467818598e-01, 1.545866468273804e-01, 1.103371356908743e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 14: {
+    constexpr size_t nb_points = 204;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 3.445433050323388e-03, {3.298151517846193e-01}},
+       Descriptor{2, 2.621255271870497e-03, {5.753828268975920e-02}},
+       Descriptor{2, 1.107383241893503e-04, {5.856168613783504e-03}},
+       Descriptor{2, 1.002858537247380e-02, {1.605554758479567e-01}},
+       Descriptor{2, 6.790408457809412e-03, {9.873964607404909e-02}},
+       Descriptor{2, 7.118148341899285e-03, {2.080531961597265e-01}},
+       Descriptor{3, 1.060052294564051e-03, {4.902479711877765e-01}},
+       Descriptor{3, 1.366983099498089e-02, {1.029394001155326e-01}},
+       Descriptor{3, 2.690741094676155e-03, {3.791346346121514e-02}},
+       Descriptor{3, 1.016460122584274e-02, {3.185735761838604e-01}},
+       Descriptor{4, 1.354439963772740e-02, {2.499092188634998e-01, 4.189387788376669e-01}},
+       Descriptor{4, 4.623340047669791e-03, {2.132633780618757e-01, 5.628766802497838e-01}},
+       Descriptor{4, 8.331498452473301e-04, {4.918128494015905e-02, 8.954689727140162e-01}},
+       Descriptor{4, 7.240296760857249e-03, {3.928626179700601e-01, 2.066357892967347e-02}},
+       Descriptor{4, 8.751807809978078e-04, {1.261760553257071e-02, 1.269022024074958e-01}},
+       Descriptor{5, 7.172536518098438e-03, {1.216187843486990e-01, 6.214000311762153e-02, 2.395005950785847e-01}},
+       Descriptor{5, 4.286856342790781e-03, {3.489022960470246e-01, 5.517941321184247e-01, 8.378145261134291e-02}},
+       Descriptor{5, 9.757527641506894e-04, {6.822383201682893e-01, 1.784164064765456e-02, 2.844407418708836e-01}},
+       Descriptor{5, 3.757936299766722e-03, {7.339982986585810e-01, 1.695395507001622e-01, 7.868554787111390e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 15: {
+    constexpr size_t nb_points = 264;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 5.112029771454935e-04, {1.692642158547220e-02}},
+       Descriptor{2, 1.620740592022658e-02, {2.836779254595722e-01}},
+       Descriptor{2, 1.552578602041314e-02, {1.821984139975859e-01}},
+       Descriptor{3, 1.219010259975421e-03, {1.450800515218459e-02}},
+       Descriptor{3, 8.472862853125917e-03, {1.427441437658564e-01}},
+       Descriptor{4, 1.797534305405733e-03, {7.321727256195354e-02, 7.766357493733004e-01}},
+       Descriptor{4, 8.198015220760061e-04, {1.056558410489699e-02, 2.225606554924344e-01}},
+       Descriptor{4, 7.681775272473012e-03, {2.637972626688146e-01, 5.575022240565973e-02}},
+       Descriptor{4, 4.479850385774781e-03, {4.354902702993817e-01, 1.112909267055329e-01}},
+       Descriptor{4, 5.615298608384355e-03, {5.347259364185193e-02, 5.448865322975603e-01}},
+       Descriptor{4, 1.769703685797290e-03, {1.105308939580100e-01, 7.694703779405406e-01}},
+       Descriptor{5, 1.131138329187022e-03, {4.104670514991076e-02, 6.030936440770986e-01, 2.328924415481402e-03}},
+       Descriptor{5, 8.404993407577487e-04, {9.201667327695258e-02, 2.939319332917082e-02, 8.672646051241815e-01}},
+       Descriptor{5, 3.011968607402684e-03, {6.822627868000052e-02, 7.077814873155971e-01, 2.200643755861662e-02}},
+       Descriptor{5, 5.084429327522668e-03, {1.890015556386176e-01, 1.295809597674534e-01, 6.807532978800637e-02}},
+       Descriptor{5, 7.139383728003996e-03, {3.054799663906012e-01, 4.695333003662676e-01, 7.251484323369149e-02}},
+       Descriptor{5, 3.378550568229294e-03, {1.509145500825105e-01, 5.778346261482753e-01, 1.112774031280626e-02}},
+       Descriptor{5, 2.201680777701462e-03, {3.368450658547645e-01, 1.082194753682293e-02, 4.176069528643034e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 16: {
+    constexpr size_t nb_points = 304;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 4.034878937238870e-03, {3.296714738440606e-01}},
+       Descriptor{2, 5.263428008047628e-03, {1.120421044173788e-01}},
+       Descriptor{2, 1.078639106857764e-02, {2.804460259110929e-01}},
+       Descriptor{2, 1.892340029030997e-03, {3.942164444076166e-02}},
+       Descriptor{3, 6.014511003000459e-03, {7.491741856476755e-02}},
+       Descriptor{3, 8.692191232873023e-03, {3.356931029556346e-01}},
+       Descriptor{4, 3.507623285784993e-03, {4.904898759556675e-02, 7.646870675801803e-01}},
+       Descriptor{4, 1.188662273319198e-03, {1.412609568309253e-02, 2.328268045894251e-01}},
+       Descriptor{4, 4.345225314466667e-03, {6.239652058154325e-02, 2.832417683077947e-01}},
+       Descriptor{4, 2.660686570203166e-03, {1.890959275696560e-01, 1.283187405611824e-02}},
+       Descriptor{4, 5.553174302124013e-03, {2.750176001295444e-01, 3.872709603194903e-01}},
+       Descriptor{4, 1.531431405395808e-04, {5.944898252569946e-03, 3.723805935523542e-02}},
+       Descriptor{4, 2.231204136715679e-03, {1.183058071099944e-01, 7.482941078308859e-01}},
+       Descriptor{5, 2.472377156249970e-03, {8.011846127872502e-02, 5.146357887888395e-01, 3.908021114187921e-01}},
+       Descriptor{5, 6.596122891817925e-03, {3.102585498627273e-01, 1.645739468379099e-01, 6.995093322963369e-02}},
+       Descriptor{5, 6.004349067697421e-04, {1.085240801928985e-01, 3.435867950145696e-02, 8.557156992205752e-01}},
+       Descriptor{5, 2.242330996876527e-03, {2.483824987814955e-01, 6.625317544850510e-01, 1.098323448764900e-02}},
+       Descriptor{5, 8.244422362471837e-04, {3.960091211067035e-01, 1.226898678006519e-02, 1.878187449597510e-02}},
+       Descriptor{5, 4.388015835267911e-03, {6.367516197137306e-02, 2.054604991324105e-01, 1.362449508885895e-01}},
+       Descriptor{5, 3.454278425740962e-03, {1.757650466139104e-01, 4.610678860796995e-01, 1.387535709612253e-01}},
+       Descriptor{5, 3.929289473335571e-03, {4.779942532006705e-01, 1.344788610299629e-02, 3.221398306538996e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 17: {
+    constexpr size_t nb_points = 364;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.943411054682655e-03, {1.092594110391400e-01}},
+       Descriptor{2, 4.821254562130973e-04, {1.706905335350991e-02}},
+       Descriptor{2, 1.633411882335971e-03, {6.030276440208671e-02}},
+       Descriptor{2, 8.937260544733967e-03, {1.802936778752487e-01}},
+       Descriptor{3, 3.539281041131444e-03, {2.075642494319923e-01}},
+       Descriptor{3, 5.127876775122913e-03, {6.350561875036179e-02}},
+       Descriptor{3, 8.505961705776655e-03, {1.425740948569834e-01}},
+       Descriptor{3, 1.362001059565090e-03, {1.525498961135818e-02}},
+       Descriptor{4, 1.952703594699083e-03, {2.817717468661823e-01, 6.605628545936354e-03}},
+       Descriptor{4, 3.552482332702156e-03, {2.532792896616394e-01, 3.473076755269789e-01}},
+       Descriptor{4, 4.735037872087881e-03, {1.194446836887992e-01, 5.129631932135305e-01}},
+       Descriptor{4, 4.076204052523780e-03, {2.796975892179978e-01, 4.040135150131379e-01}},
+       Descriptor{4, 4.397889616038591e-03, {5.914965001755916e-02, 2.771505768037979e-01}},
+       Descriptor{4, 8.159915418039479e-04, {1.156645687972039e-02, 3.372693231713962e-01}},
+       Descriptor{4, 6.436927403356441e-03, {2.546200946118619e-01, 8.899213511967391e-02}},
+       Descriptor{4, 2.290937029978224e-04, {6.672914677865158e-03, 8.335377000797282e-02}},
+       Descriptor{4, 3.188746694204980e-03, {5.954996366173630e-02, 1.488738514609226e-01}},
+       Descriptor{5, 1.991266259969879e-03, {9.563829425828949e-02, 1.308047471312760e-02, 1.683493064182302e-01}},
+       Descriptor{5, 1.209946636868781e-03, {6.035751534893870e-02, 6.376551993416253e-01, 1.062246299591423e-02}},
+       Descriptor{5, 2.943417982183902e-03, {5.720629662753272e-01, 1.575062824084584e-01, 1.289545879798304e-02}},
+       Descriptor{5, 3.454094554810564e-03, {5.982871618499548e-01, 1.530963612221368e-01, 6.489065131906320e-02}},
+       Descriptor{5, 8.241135414777571e-04, {8.721077921772252e-02, 1.165500297046612e-02, 4.707766223753580e-02}},
+       Descriptor{5, 1.383542378170185e-03, {4.137799680661914e-01, 4.085328910163256e-01, 1.048009320013415e-02}},
+       Descriptor{5, 1.781445235926538e-03, {3.904989583719425e-01, 1.167932299246964e-02, 7.360595669274642e-02}},
+       Descriptor{5, 6.969316351001401e-04, {7.735017403534717e-01, 8.163680371232981e-03, 2.545430962429988e-02}},
+       Descriptor{5, 5.722888401891606e-03, {5.603440494408158e-02, 1.529886055031752e-01, 3.226986469260043e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 18: {
+    constexpr size_t nb_points = 436;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.505388406750596e-03, {2.063721818681210e-01}},
+       Descriptor{2, 7.201441531528436e-03, {1.625387945176406e-01}},
+       Descriptor{2, 5.801801233694815e-03, {3.116266728255643e-01}},
+       Descriptor{2, 2.465088392849791e-03, {3.307279160890313e-01}},
+       Descriptor{2, 8.235271059110021e-04, {3.374408201299506e-02}},
+       Descriptor{2, 2.396153395824959e-03, {6.568138278425954e-02}},
+       Descriptor{2, 1.662026538062453e-04, {1.169072420259711e-02}},
+       Descriptor{3, 4.747626606883917e-05, {3.051729141214794e-01}},
+       Descriptor{3, 5.654960404175805e-04, {2.498171653415399e-01}},
+       Descriptor{3, 1.126644847587598e-03, {1.336652291605620e-02}},
+       Descriptor{3, 1.348246151880201e-03, {2.503423435758855e-01}},
+       Descriptor{3, 3.657069096677683e-04, {3.324517722542840e-01}},
+       Descriptor{3, 5.853171992565114e-03, {3.328935974705466e-01}},
+       Descriptor{3, 1.956691034821430e-03, {1.053889982941293e-01}},
+       Descriptor{3, 1.673244252179039e-03, {4.317721941366494e-02}},
+       Descriptor{4, 1.684409973125634e-03, {3.310306761504429e-02, 1.352754021303201e-01}},
+       Descriptor{4, 9.489733030971150e-04, {1.946185784467675e-01, 1.171879047047253e-03}},
+       Descriptor{4, 1.039083135931653e-03, {1.321252126131118e-02, 3.278960098149503e-01}},
+       Descriptor{4, 2.868558324240348e-03, {1.229320721770598e-01, 2.028399843152170e-02}},
+       Descriptor{4, 3.672202784572132e-03, {6.199506095168380e-02, 3.498961219623653e-01}},
+       Descriptor{4, 4.747155546596363e-03, {8.407187546991698e-02, 1.666830796952924e-01}},
+       Descriptor{4, 4.150884078212972e-04, {7.867048874453891e-03, 1.859179591228751e-01}},
+       Descriptor{4, 2.504990122247683e-03, {4.217582498871236e-02, 2.482707411503117e-01}},
+       Descriptor{4, 2.629919627710238e-04, {8.683491872138840e-03, 6.698102226768764e-02}},
+       Descriptor{4, 5.237948818645674e-03, {1.111185367576208e-01, 2.918417077981162e-01}},
+       Descriptor{5, 9.530517345502799e-04, {3.685463845606022e-03, 6.377078964518405e-02, 2.161982818015302e-01}},
+       Descriptor{5, 4.424079498028044e-04, {9.147563979493070e-02, 4.024098103223798e-03, 4.866034767929566e-02}},
+       Descriptor{5, 1.756179008422938e-03, {1.326563740184036e-01, 4.168774597385260e-01, 2.814320586294362e-02}},
+       Descriptor{5, 2.434266881750153e-03, {4.439198669278809e-01, 2.464330224801677e-01, 2.919443405619611e-02}},
+       Descriptor{5, 3.936511965938193e-03, {2.395450885895190e-01, 1.271207488070096e-01, 2.457574497117561e-01}},
+       Descriptor{5, 3.199791269106457e-03, {1.657130887168779e-01, 2.137049061861850e-01, 7.023964653694080e-02}},
+       Descriptor{5, 3.797610312158748e-03, {1.788776686026366e-01, 3.290353014877974e-01, 6.661063766146717e-02}},
+       Descriptor{5, 1.495938562630981e-03, {3.340176116726106e-01, 4.950315956949369e-03, 1.887999372824127e-01}},
+       Descriptor{5, 1.703996181224725e-03, {5.458773009126385e-01, 7.846288361681254e-03, 7.104964990519175e-02}},
+       Descriptor{5, 3.628775117699059e-03, {2.510439859007851e-02, 1.293557534662013e-01, 2.529882400374321e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 19: {
+    constexpr size_t nb_points = 487;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 9.477424964421555e-03, {}},
+       Descriptor{2, 2.309652046579032e-03, {9.623346794170115e-02}},
+       Descriptor{2, 4.091492762240734e-03, {2.840710971185555e-01}},
+       Descriptor{2, 1.173654667448287e-03, {4.201218049762310e-02}},
+       Descriptor{3, 5.797759302818667e-03, {1.293925012179526e-01}},
+       Descriptor{4, 2.028992639106921e-03, {5.339312125330306e-02, 7.679699947393867e-01}},
+       Descriptor{4, 2.005858485106433e-04, {5.250553906919603e-03, 1.660339253205299e-01}},
+       Descriptor{4, 2.908059972456899e-03, {2.128993323694659e-01, 1.034045995093328e-01}},
+       Descriptor{4, 2.532078463287134e-03, {4.034135325977229e-01, 1.333356346432022e-01}},
+       Descriptor{4, 7.343695736316139e-04, {1.188811930811271e-02, 5.532379678070896e-01}},
+       Descriptor{4, 1.076183574539400e-03, {8.903116551206627e-02, 8.080695151909102e-01}},
+       Descriptor{4, 1.986415677131464e-03, {5.151678695899160e-02, 4.873280365294901e-01}},
+       Descriptor{4, 5.046317364293947e-03, {1.179992852297671e-01, 2.466149943644083e-01}},
+       Descriptor{4, 2.692282460531053e-03, {4.608512334928590e-02, 2.781414318856127e-01}},
+       Descriptor{4, 1.330636288503801e-03, {1.410186708097898e-01, 8.174649948389451e-03}},
+       Descriptor{4, 1.782928900852855e-03, {1.411034408699267e-01, 5.855868767598970e-01}},
+       Descriptor{4, 1.263015826184454e-04, {7.911862204821108e-03, 3.035883809061935e-02}},
+       Descriptor{4, 3.244260943819173e-03, {1.761012114703174e-01, 2.461239587816849e-01}},
+       Descriptor{5, 1.048498185712524e-03, {4.531137441520981e-02, 7.694124041149815e-01, 1.162671237541303e-02}},
+       Descriptor{5, 3.605740745734604e-03, {1.902469770309511e-01, 1.139907343389768e-01, 4.902896398807803e-02}},
+       Descriptor{5, 2.561558407869844e-03, {2.025882107314621e-01, 5.045224197189542e-01, 5.200356315054191e-02}},
+       Descriptor{5, 1.498918467834416e-03, {6.280459865457699e-02, 3.899342137938394e-01, 9.651021927014570e-03}},
+       Descriptor{5, 1.814324572327483e-03, {1.689034611068301e-01, 1.025101977116908e-02, 2.542260337897459e-01}},
+       Descriptor{5, 2.883938073996170e-03, {1.282802298826546e-01, 3.714709314621824e-01, 2.826241348940526e-01}},
+       Descriptor{5, 3.191411305221261e-03, {3.753164302248334e-01, 2.323738231259893e-01, 5.648574604597608e-02}},
+       Descriptor{5, 3.955282336986757e-04, {8.176634693298535e-02, 2.823323910477210e-02, 8.840321921276471e-01}},
+       Descriptor{5, 1.642211231915762e-03, {3.727215008878210e-01, 4.691373442926859e-01, 1.052014400647361e-02}},
+       Descriptor{5, 4.640920897459689e-04, {2.881077012617455e-01, 6.241150434253934e-03, 2.040787180394686e-02}},
+       Descriptor{5, 1.210865339994028e-03, {8.483677030488673e-03, 2.548571009364550e-01, 8.369886684600136e-02}},
+       Descriptor{5, 3.653283017278065e-03, {1.193128036212033e-01, 5.081225906571617e-01, 5.013138003585081e-02}},
+       Descriptor{5, 1.744791238762615e-03, {4.198369201417426e-01, 1.094996106379021e-02, 3.155028856836414e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 20: {
+    constexpr size_t nb_points = 552;
+    SmallArray<R3> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 5.418454790084648e-03, {2.967759596934131e-01}},
+       Descriptor{2, 3.820897988248627e-03, {1.209047972112185e-01}},
+       Descriptor{2, 4.057458152334048e-03, {3.177959071881044e-01}},
+       Descriptor{2, 4.147670297750325e-03, {2.012655712414790e-01}},
+       Descriptor{2, 5.289566609934361e-03, {1.678838969272885e-01}},
+       Descriptor{2, 9.720418198623532e-04, {3.621493960968947e-02}},
+       Descriptor{3, 2.988176136780452e-03, {5.260244961748377e-02}},
+       Descriptor{3, 7.061746923528361e-03, {3.045785563313042e-01}},
+       Descriptor{4, 1.138014164865969e-03, {3.228230549839516e-02, 8.181532733687802e-01}},
+       Descriptor{4, 3.284328089439585e-04, {8.126514470519638e-03, 1.671963931828212e-01}},
+       Descriptor{4, 4.517305074533165e-03, {1.851459522994347e-01, 6.611011876662430e-02}},
+       Descriptor{4, 2.743957098139693e-03, {4.110775382017770e-01, 1.281831845252744e-01}},
+       Descriptor{4, 5.345104029866354e-04, {1.005979740685446e-02, 5.566366248307155e-01}},
+       Descriptor{4, 8.527602773365834e-04, {8.230161503723588e-02, 8.242515811428504e-01}},
+       Descriptor{4, 1.608673320950611e-03, {3.801282860266467e-02, 6.072922714391159e-01}},
+       Descriptor{4, 2.164021789623034e-03, {8.297816905794758e-02, 1.927591204214162e-01}},
+       Descriptor{4, 1.298964551990821e-03, {3.232830149045374e-02, 2.290899793113822e-01}},
+       Descriptor{4, 7.540221084980848e-04, {1.406521940177762e-01, 4.062459178585371e-03}},
+       Descriptor{4, 3.936392177197076e-03, {1.139719439112915e-01, 5.168656572693773e-01}},
+       Descriptor{4, 8.260437254311268e-05, {7.453571377174895e-03, 2.517856316811188e-02}},
+       Descriptor{4, 2.202593581265821e-03, {1.110171267302689e-01, 3.907070674791312e-01}},
+       Descriptor{5, 1.142912288312416e-03, {5.313810503913767e-02, 5.333767142737392e-01, 9.768492667580009e-03}},
+       Descriptor{5, 8.037341179884985e-04, {9.439207428728114e-02, 9.450517146275739e-02, 7.609480923624367e-01}},
+       Descriptor{5, 6.441055085633393e-04, {5.505445164272958e-02, 7.653955692270024e-01, 5.660747947619223e-03}},
+       Descriptor{5, 2.284803061568028e-03, {1.798637301234305e-01, 9.751720382220438e-02, 3.466187077541209e-02}},
+       Descriptor{5, 1.768215173403588e-03, {2.121920547752147e-01, 4.095036586170944e-01, 4.613078410418590e-02}},
+       Descriptor{5, 1.364093116604035e-03, {1.290161512351960e-01, 4.765557207500070e-01, 9.956030373782836e-03}},
+       Descriptor{5, 1.409513020242023e-03, {1.614873223066114e-01, 1.376084748677020e-02, 2.264813194055542e-01}},
+       Descriptor{5, 5.352196172820926e-03, {1.059283078939860e-01, 4.143500741402470e-01, 1.944202417194029e-01}},
+       Descriptor{5, 2.357251753798956e-03, {2.046229328456809e-01, 2.864083155254953e-01, 3.607794123452725e-02}},
+       Descriptor{5, 2.335500281519085e-04, {7.441796869877018e-02, 2.361583233015298e-02, 8.995240292632410e-01}},
+       Descriptor{5, 8.299191110448388e-04, {2.938352359686784e-01, 5.078745759286403e-01, 4.379772363790332e-03}},
+       Descriptor{5, 3.480327275620159e-04, {2.899004919931303e-01, 2.388614940511808e-03, 2.095834844884252e-02}},
+       Descriptor{5, 1.081306497459309e-03, {6.920395527608953e-03, 2.780090233488638e-01, 8.666224435610116e-02}},
+       Descriptor{5, 3.363592992643566e-03, {1.053191068972152e-01, 5.431130574905816e-01, 4.625008859127536e-02}},
+       Descriptor{5, 1.138819523953005e-03, {3.387615700292962e-01, 8.792325219258773e-03, 4.002505388571689e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  default: {
+    throw NormalError("Gauss quadrature formulae handle degrees up to " +
+                      std::to_string(TetrahedronGaussQuadrature::max_degree) + " on tetrahedra");
+  }
+  }
+}
diff --git a/src/analysis/TetrahedronGaussQuadrature.hpp b/src/analysis/TetrahedronGaussQuadrature.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..352ff079a409952d99af6931bf75b8b84b06d622
--- /dev/null
+++ b/src/analysis/TetrahedronGaussQuadrature.hpp
@@ -0,0 +1,38 @@
+#ifndef TETRAHEDRON_GAUSS_QUADRATURE_HPP
+#define TETRAHEDRON_GAUSS_QUADRATURE_HPP
+
+#include <analysis/QuadratureFormula.hpp>
+
+/**
+ * Defines Gauss quadrature on the P1 reference tetrahedron element
+ *
+ * \note formulae are provided by
+ *
+ * 'High-order symmetric cubature rules for tetrahedra and pyramids'
+ * Jan Jasˁkowiec & N. Sukumar (2020).
+ */
+class TetrahedronGaussQuadrature final : public QuadratureFormula<3>
+{
+ public:
+  constexpr static size_t max_degree = 20;
+
+ private:
+  void _buildPointAndWeightLists(const size_t degree);
+
+ public:
+  TetrahedronGaussQuadrature(TetrahedronGaussQuadrature&&)      = default;
+  TetrahedronGaussQuadrature(const TetrahedronGaussQuadrature&) = default;
+
+ private:
+  friend class QuadratureManager;
+
+  explicit TetrahedronGaussQuadrature(const size_t degree) : QuadratureFormula<3>(QuadratureType::Gauss)
+  {
+    this->_buildPointAndWeightLists(degree);
+  }
+
+  TetrahedronGaussQuadrature()  = delete;
+  ~TetrahedronGaussQuadrature() = default;
+};
+
+#endif   // TETRAHEDRON_GAUSS_QUADRATURE_HPP
diff --git a/src/analysis/TriangleGaussQuadrature.cpp b/src/analysis/TriangleGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..df6a9a1ced3aa15d098aaa7696eb67af41c6090b
--- /dev/null
+++ b/src/analysis/TriangleGaussQuadrature.cpp
@@ -0,0 +1,490 @@
+#include <analysis/TriangleGaussQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+void
+TriangleGaussQuadrature::_buildPointAndWeightLists(const size_t degree)
+{
+  using R2 = TinyVector<2>;
+
+  struct Descriptor
+  {
+    int id;
+    double weight;
+    std::vector<double> lambda_list;
+  };
+
+  auto fill_quadrature_points = [](auto descriptor_list, auto& point_list, auto& weight_list) {
+    Assert(point_list.size() == weight_list.size());
+
+    const R2 A = {0, 0};
+    const R2 B = {1, 0};
+    const R2 C = {0, 1};
+
+    size_t k = 0;
+    for (size_t i = 0; i < descriptor_list.size(); ++i) {
+      const auto [id, w, position_list] = descriptor_list[i];
+
+      switch (id) {
+      case 1: {
+        Assert(position_list.size() == 0);
+
+        point_list[k]  = (1. / 3) * (A + B + C);
+        weight_list[k] = w;
+
+        k += 1;
+        break;
+      }
+      case 2: {
+        Assert(position_list.size() == 1);
+        const double& l0 = position_list[0];
+        const double& l1 = 1 - 2 * l0;
+
+        point_list[k + 0] = l0 * A + l0 * B + l1 * C;
+        point_list[k + 1] = l0 * A + l1 * B + l0 * C;
+        point_list[k + 2] = l1 * A + l0 * B + l0 * C;
+
+        for (size_t l = 0; l < 3; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 3;
+        break;
+      }
+      case 3: {
+        Assert(position_list.size() == 2);
+        const double& l0 = position_list[0];
+        const double& l1 = position_list[1];
+        const double& l2 = 1 - l0 - l1;
+
+        point_list[k + 0] = l0 * A + l1 * B + l2 * C;
+        point_list[k + 1] = l0 * A + l2 * B + l1 * C;
+        point_list[k + 2] = l1 * A + l0 * B + l2 * C;
+        point_list[k + 3] = l1 * A + l2 * B + l0 * C;
+        point_list[k + 4] = l2 * A + l0 * B + l1 * C;
+        point_list[k + 5] = l2 * A + l1 * B + l0 * C;
+
+        for (size_t l = 0; l < 6; ++l) {
+          weight_list[k + l] = w;
+        }
+
+        k += 6;
+        break;
+      }
+        // LCOV_EXCL_START
+      default: {
+        throw UnexpectedError("invalid quadrature id");
+      }
+        // LCOV_EXCL_STOP
+      }
+    }
+
+    Assert(k == point_list.size(), "invalid number of quadrature points");
+  };
+
+  switch (degree) {
+  case 0:
+  case 1: {
+    constexpr size_t nb_points = 1;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 5.000000000000000e-01, {}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 2: {
+    constexpr size_t nb_points = 3;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.666666666666667e-01, {1.666666666666667e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 3:
+  case 4: {
+    constexpr size_t nb_points = 6;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.116907948390057e-01, {4.459484909159649e-01}},
+       Descriptor{2, 5.497587182766094e-02, {9.157621350977074e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 5: {
+    constexpr size_t nb_points = 7;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.125000000000000e-01, {}}, Descriptor{2, 6.296959027241357e-02, {1.012865073234563e-01}},
+       Descriptor{2, 6.619707639425310e-02, {4.701420641051151e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 6: {
+    constexpr size_t nb_points = 12;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.542245318510341e-02, {6.308901449150223e-02}},
+       Descriptor{2, 5.839313786318968e-02, {2.492867451709104e-01}},
+       Descriptor{3, 4.142553780918679e-02, {3.103524510337844e-01, 5.314504984481695e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 7: {
+    constexpr size_t nb_points = 15;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 8.272525055396066e-03, {3.373064855458785e-02}},
+       Descriptor{2, 6.397208561507779e-02, {2.415773825954036e-01}},
+       Descriptor{2, 3.854332309299303e-02, {4.743096925047182e-01}},
+       Descriptor{3, 2.793936645159989e-02, {1.986833147973516e-01, 4.703664465259523e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 8: {
+    constexpr size_t nb_points = 16;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 7.215780383889359e-02, {}}, Descriptor{2, 4.754581713364231e-02, {4.592925882927232e-01}},
+       Descriptor{2, 5.160868526735912e-02, {1.705693077517602e-01}},
+       Descriptor{2, 1.622924881159904e-02, {5.054722831703098e-02}},
+       Descriptor{3, 1.361515708721750e-02, {2.631128296346381e-01, 8.394777409957605e-03}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 9: {
+    constexpr size_t nb_points = 19;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 4.856789814139942e-02, {}},
+       Descriptor{2, 3.891377050238714e-02, {4.370895914929366e-01}},
+       Descriptor{2, 3.982386946360512e-02, {1.882035356190327e-01}},
+       Descriptor{2, 1.566735011356954e-02, {4.896825191987376e-01}},
+       Descriptor{2, 1.278883782934902e-02, {4.472951339445271e-02}},
+       Descriptor{3, 2.164176968864469e-02, {2.219629891607657e-01, 3.683841205473629e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 10: {
+    constexpr size_t nb_points = 25;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 4.087166457314299e-02, {}},
+       Descriptor{2, 6.676484406574783e-03, {3.205537321694351e-02}},
+       Descriptor{2, 2.297898180237237e-02, {1.421611010565644e-01}},
+       Descriptor{3, 3.195245319821202e-02, {1.481328857838206e-01, 3.218129952888354e-01}},
+       Descriptor{3, 1.709232408147971e-02, {3.691467818278110e-01, 2.961988948872977e-02}},
+       Descriptor{3, 1.264887885364419e-02, {1.637017337371825e-01, 2.836766533993844e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 11: {
+    constexpr size_t nb_points = 28;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 4.288058986611211e-02, {}},
+       Descriptor{2, 5.215935256447348e-03, {2.848541761437191e-02}},
+       Descriptor{2, 3.525784205585829e-02, {2.102199567031783e-01}},
+       Descriptor{2, 1.931537961850966e-02, {1.026354827122464e-01}},
+       Descriptor{2, 8.303136527292684e-03, {4.958919009658909e-01}},
+       Descriptor{2, 3.365807703973415e-02, {4.384659267643522e-01}},
+       Descriptor{3, 5.145144786476639e-03, {7.325427686064452e-03, 1.493247886520824e-01}},
+       Descriptor{3, 2.016623832025028e-02, {2.895811256377058e-01, 4.601050016542996e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 12: {
+    constexpr size_t nb_points = 33;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.213341904072602e-02, {4.882037509455415e-01}},
+       Descriptor{2, 1.424302603443877e-02, {1.092578276593543e-01}},
+       Descriptor{2, 3.127060659795138e-02, {2.714625070149261e-01}},
+       Descriptor{2, 3.965821254986819e-03, {2.464636343633559e-02}},
+       Descriptor{2, 2.495916746403047e-02, {4.401116486585931e-01}},
+       Descriptor{3, 1.089179251930378e-02, {2.303415635526714e-02, 2.916556797383409e-01}},
+       Descriptor{3, 2.161368182970710e-02, {2.554542286385174e-01, 1.162960196779266e-01}},
+       Descriptor{3, 7.541838788255719e-03, {2.138249025617059e-02, 8.513377925102400e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 13: {
+    constexpr size_t nb_points = 37;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 3.398001829341582e-02, {}},
+       Descriptor{2, 1.199720096444737e-02, {4.890769464525394e-01}},
+       Descriptor{2, 2.913924255959999e-02, {2.213722862918329e-01}},
+       Descriptor{2, 2.780098376522666e-02, {4.269414142598004e-01}},
+       Descriptor{2, 3.026168551769586e-03, {2.150968110884318e-02}},
+       Descriptor{3, 1.208951990579691e-02, {1.635974010678505e-01, 8.789548303219732e-02}},
+       Descriptor{3, 7.482700552582834e-03, {2.437018690109383e-02, 1.109220428034634e-01}},
+       Descriptor{3, 1.732063807042419e-02, {6.801224355420665e-02, 3.084417608921178e-01}},
+       Descriptor{3, 4.795340501771632e-03, {2.725158177734296e-01, 5.126389102382369e-03}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 14: {
+    constexpr size_t nb_points = 42;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 2.108129436849651e-02, {1.772055324125434e-01}},
+       Descriptor{2, 1.639417677206267e-02, {4.176447193404539e-01}},
+       Descriptor{2, 7.216849834888334e-03, {6.179988309087260e-02}},
+       Descriptor{2, 1.094179068471444e-02, {4.889639103621786e-01}},
+       Descriptor{2, 2.588705225364579e-02, {2.734775283088386e-01}},
+       Descriptor{2, 2.461701801200041e-03, {1.939096124870105e-02}},
+       Descriptor{3, 7.218154056766920e-03, {1.464695005565441e-02, 2.983728821362577e-01}},
+       Descriptor{3, 1.233287660628184e-02, {1.722666878213556e-01, 5.712475740364794e-02}},
+       Descriptor{3, 1.928575539353034e-02, {9.291624935697182e-02, 3.368614597963450e-01}},
+       Descriptor{3, 2.505114419250336e-03, {1.189744976969569e-01, 1.268330932872025e-03}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 15: {
+    constexpr size_t nb_points = 49;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 2.216769369109204e-02, {}},
+       Descriptor{2, 2.135689078573028e-02, {4.053622141339755e-01}},
+       Descriptor{2, 8.222368781312581e-03, {7.017355289998602e-02}},
+       Descriptor{2, 8.698074000381707e-03, {4.741706814380198e-01}},
+       Descriptor{2, 2.339168086435481e-02, {2.263787134203496e-01}},
+       Descriptor{2, 4.786923091230043e-03, {4.949969567691262e-01}},
+       Descriptor{2, 1.480387318952688e-03, {1.581172625098864e-02}},
+       Descriptor{3, 7.801286415287982e-03, {3.146482428124508e-01, 1.837611238568109e-02}},
+       Descriptor{3, 2.014926686009050e-03, {7.094860523645553e-02, 9.139237037308396e-03}},
+       Descriptor{3, 1.436029346260067e-02, {9.424205359215536e-02, 1.905355894763939e-01}},
+       Descriptor{3, 5.836310590787923e-03, {1.863871372816638e-02, 1.680686452224144e-01}},
+       Descriptor{3, 1.565773814248464e-02, {9.579672364760859e-02, 3.389506114752772e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 16: {
+    constexpr size_t nb_points = 55;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 2.263228303690940e-02, {}},
+       Descriptor{2, 2.054646157184948e-02, {2.459900704671417e-01}},
+       Descriptor{2, 2.035591665621268e-02, {4.155848968854205e-01}},
+       Descriptor{2, 7.390817345112202e-03, {8.535556658670032e-02}},
+       Descriptor{2, 1.470920484949405e-02, {1.619186441912712e-01}},
+       Descriptor{2, 2.209273156075285e-03, {5.000000000000000e-01}},
+       Descriptor{2, 1.298716664913858e-02, {4.752807275459421e-01}},
+       Descriptor{3, 9.469136232207850e-03, {1.910747636405291e-01, 5.475517491470312e-02}},
+       Descriptor{3, 8.272333574175241e-04, {8.552204200227611e-03, 2.320342776881371e-02}},
+       Descriptor{3, 7.504300892142903e-03, {3.317645234741477e-01, 1.893177828040591e-02}},
+       Descriptor{3, 3.973796966696249e-03, {8.069616698587292e-02, 1.903012974369745e-02}},
+       Descriptor{3, 1.599180503968503e-02, {3.082449691963540e-01, 1.026061902393981e-01}},
+       Descriptor{3, 2.695593558424406e-03, {1.874417824837821e-01, 5.936350016822270e-03}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 17: {
+    constexpr size_t nb_points = 60;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{2, 1.365546326405105e-02, {4.171034443615992e-01}},
+       Descriptor{2, 1.386943788818821e-03, {1.475549166075395e-02}},
+       Descriptor{2, 1.250972547524868e-02, {4.655978716188903e-01}},
+       Descriptor{2, 1.315631529400899e-02, {1.803581162663706e-01}},
+       Descriptor{2, 6.229500401152721e-03, {6.665406347959693e-02}},
+       Descriptor{2, 1.885811857639764e-02, {2.857065024365866e-01}},
+       Descriptor{3, 3.989150102964797e-03, {1.591922874727927e-01, 1.601764236211930e-02}},
+       Descriptor{3, 1.124388627334553e-02, {6.734937786736120e-02, 3.062815917461865e-01}},
+       Descriptor{3, 5.199219977919768e-03, {4.154754592952291e-01, 1.322967276008689e-02}},
+       Descriptor{3, 1.027894916022726e-02, {1.687225134952595e-01, 7.804234056828242e-02}},
+       Descriptor{3, 4.346107250500596e-03, {2.717918700553548e-01, 1.313587083400269e-02}},
+       Descriptor{3, 2.292174200867934e-03, {7.250547079900242e-02, 1.157517590318062e-02}},
+       Descriptor{3, 1.308581296766849e-02, {2.992189424769703e-01, 1.575054779268699e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 18: {
+    constexpr size_t nb_points = 67;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.817786765071333e-02, {}},
+       Descriptor{2, 1.665223501669507e-02, {3.999556280675762e-01}},
+       Descriptor{2, 6.023323816999855e-03, {4.875803015748695e-01}},
+       Descriptor{2, 9.474585753389433e-03, {4.618095064064492e-01}},
+       Descriptor{2, 1.823754470447182e-02, {2.422647025142720e-01}},
+       Descriptor{2, 3.564663009859485e-03, {3.883025608868559e-02}},
+       Descriptor{2, 8.279579976001624e-03, {9.194774212164319e-02}},
+       Descriptor{3, 6.879808117471103e-03, {1.838227079254640e-01, 4.580491585986078e-02}},
+       Descriptor{3, 1.189095545007642e-02, {1.226967573719275e-01, 2.063492574338379e-01}},
+       Descriptor{3, 2.265267251128533e-03, {3.956834343322697e-01, 3.897611033473383e-03}},
+       Descriptor{3, 3.420055059803591e-03, {1.081957937910333e-01, 1.346201674144499e-02}},
+       Descriptor{3, 8.873744551010202e-03, {3.197516245253774e-01, 4.026028346990806e-02}},
+       Descriptor{3, 2.505330437289861e-03, {2.357721849581917e-01, 5.298335186609765e-03}},
+       Descriptor{3, 6.114740634805449e-04, {2.709091099516201e-02, 5.483600420423190e-04}},
+       Descriptor{3, 1.274108765591222e-02, {3.334935294498808e-01, 1.205876951639246e-01}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 19: {
+    constexpr size_t nb_points = 73;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.723469885200617e-02, {}},
+       Descriptor{2, 3.554628298899065e-03, {5.252389035120897e-02}},
+       Descriptor{2, 5.160877571472141e-03, {4.925126750413369e-01}},
+       Descriptor{2, 7.617175546509150e-03, {1.114488733230214e-01}},
+       Descriptor{2, 1.149179501337080e-02, {4.591942010395437e-01}},
+       Descriptor{2, 1.576876744657749e-02, {4.039697225519012e-01}},
+       Descriptor{2, 1.232595742409543e-02, {1.781701047817643e-01}},
+       Descriptor{2, 8.826613882214238e-04, {1.163946118378945e-02}},
+       Descriptor{2, 1.587650968300154e-02, {2.551616329136077e-01}},
+       Descriptor{3, 4.847742243427523e-03, {3.914585933169222e-02, 1.306976762680324e-01}},
+       Descriptor{3, 1.317316098869537e-02, {1.293125644701578e-01, 3.113176298095413e-01}},
+       Descriptor{3, 1.641038275917910e-03, {3.646177809746111e-01, 2.068925896604807e-03}},
+       Descriptor{3, 9.053972465606226e-03, {2.214348854323312e-01, 7.456029460162668e-02}},
+       Descriptor{3, 1.463157551735100e-03, {1.424257573657563e-01, 5.007288257354491e-03}},
+       Descriptor{3, 8.051081382012054e-03, {3.540280097352752e-01, 4.088801119601688e-02}},
+       Descriptor{3, 4.227943749768248e-03, {1.492405208198407e-02, 2.418945789605796e-01}},
+       Descriptor{3, 1.663600681429694e-03, {9.776025800888155e-03, 6.008627532230670e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 20: {
+    constexpr size_t nb_points = 79;
+    SmallArray<R2> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    std::array descriptor_list =   //
+      {Descriptor{1, 1.391011070145312e-02, {}},
+       Descriptor{2, 1.408320130752025e-02, {2.545792676733391e-01}},
+       Descriptor{2, 7.988407910666199e-04, {1.097614102839776e-02}},
+       Descriptor{2, 7.830230776074533e-03, {1.093835967117146e-01}},
+       Descriptor{2, 9.173462974252915e-03, {1.862949977445409e-01}},
+       Descriptor{2, 9.452399933232448e-03, {4.455510569559248e-01}},
+       Descriptor{2, 2.161275410665577e-03, {3.731088059888470e-02}},
+       Descriptor{2, 1.378805062907046e-02, {3.934253478170999e-01}},
+       Descriptor{2, 7.101825303408441e-03, {4.762456115404990e-01}},
+       Descriptor{3, 2.202897418558497e-03, {1.591337076570672e-01, 7.570780504696529e-03}},
+       Descriptor{3, 5.986398578954690e-03, {1.985181322287882e-01, 4.656036490766432e-02}},
+       Descriptor{3, 1.129869602125866e-03, {4.854937607623754e-03, 6.409058560843406e-02}},
+       Descriptor{3, 8.667225567219333e-03, {3.331348173095875e-01, 5.498747914298681e-02}},
+       Descriptor{3, 4.145711527613858e-03, {3.836368477537459e-02, 9.995229628813866e-02}},
+       Descriptor{3, 7.722607822099230e-03, {2.156070573900944e-01, 1.062272047202700e-01}},
+       Descriptor{3, 3.695681500255298e-03, {9.831548292802561e-03, 4.200237588162241e-01}},
+       Descriptor{3, 1.169174573182774e-02, {1.398080719917999e-01, 3.178601238357720e-01}},
+       Descriptor{3, 3.578200238457685e-03, {2.805814114236652e-01, 1.073721285601109e-02}}};
+
+    fill_quadrature_points(descriptor_list, point_list, weight_list);
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  default: {
+    throw NormalError("Gauss quadrature formulae handle degrees up to " +
+                      std::to_string(TriangleGaussQuadrature::max_degree) + " on triangles");
+  }
+  }
+}
diff --git a/src/analysis/TriangleGaussQuadrature.hpp b/src/analysis/TriangleGaussQuadrature.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9e1edd3dedc01392f5cfae1ed8c0ab277b3ab2ba
--- /dev/null
+++ b/src/analysis/TriangleGaussQuadrature.hpp
@@ -0,0 +1,37 @@
+#ifndef TRIANGLE_GAUSS_QUADRATURE_HPP
+#define TRIANGLE_GAUSS_QUADRATURE_HPP
+
+#include <analysis/QuadratureFormula.hpp>
+
+/**
+ * Defines Gauss quadrature on the P1 reference triangle
+ *
+ * \note formulae are provided by
+ *
+ * ‘On the identification of symmetric quadrature rules for finite
+ * element methods‘ by F.D. Witherden & P.E. Vincent (2015).
+ */
+class TriangleGaussQuadrature final : public QuadratureFormula<2>
+{
+ public:
+  constexpr static size_t max_degree = 20;
+
+ private:
+  void _buildPointAndWeightLists(const size_t degree);
+
+ public:
+  TriangleGaussQuadrature(TriangleGaussQuadrature&&)      = default;
+  TriangleGaussQuadrature(const TriangleGaussQuadrature&) = default;
+
+ private:
+  friend class QuadratureManager;
+  explicit TriangleGaussQuadrature(const size_t degree) : QuadratureFormula<2>(QuadratureType::Gauss)
+  {
+    this->_buildPointAndWeightLists(degree);
+  }
+
+  TriangleGaussQuadrature()  = delete;
+  ~TriangleGaussQuadrature() = default;
+};
+
+#endif   // TRIANGLE_GAUSS_QUADRATURE_HPP
diff --git a/src/geometry/CubeTransformation.hpp b/src/geometry/CubeTransformation.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7bc12c324e4a55750b41b1584633cc789f8a7b2d
--- /dev/null
+++ b/src/geometry/CubeTransformation.hpp
@@ -0,0 +1,79 @@
+#ifndef CUBE_TRANSFORMATION_HPP
+#define CUBE_TRANSFORMATION_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+#include <array>
+
+class CubeTransformation
+{
+ public:
+  constexpr static size_t Dimension      = 3;
+  constexpr static size_t NumberOfPoints = 8;
+
+ private:
+  TinyMatrix<Dimension, NumberOfPoints - 1> m_coefficients;
+  TinyVector<Dimension> m_shift;
+
+ public:
+  PUGS_INLINE
+  TinyVector<Dimension>
+  operator()(const TinyVector<Dimension>& x) const
+  {
+    const TinyVector<NumberOfPoints - 1> X =
+      {x[0], x[1], x[2], x[0] * x[1], x[1] * x[2], x[0] * x[2], x[0] * x[1] * x[2]};
+    return m_coefficients * X + m_shift;
+  }
+
+  PUGS_INLINE double
+  jacobianDeterminant(const TinyVector<Dimension>& X) const
+  {
+    static_assert(Dimension == 3, "invalid dimension");
+    const auto& T   = m_coefficients;
+    const double& x = X[0];
+    const double& y = X[1];
+    const double& z = X[2];
+
+    const TinyMatrix<Dimension, Dimension> J = {T(0, 0) + T(0, 3) * y + T(0, 5) * z + T(0, 6) * y * z,
+                                                T(0, 1) + T(0, 3) * x + T(0, 4) * z + T(0, 6) * x * z,
+                                                T(0, 2) + T(0, 4) * y + T(0, 5) * x + T(0, 6) * x * y,
+                                                //
+                                                T(1, 0) + T(1, 3) * y + T(1, 5) * z + T(1, 6) * y * z,
+                                                T(1, 1) + T(1, 3) * x + T(1, 4) * z + T(1, 6) * x * z,
+                                                T(1, 2) + T(1, 4) * y + T(1, 5) * x + T(1, 6) * x * y,
+                                                //
+                                                T(2, 0) + T(2, 3) * y + T(2, 5) * z + T(2, 6) * y * z,
+                                                T(2, 1) + T(2, 3) * x + T(2, 4) * z + T(2, 6) * x * z,
+                                                T(2, 2) + T(2, 4) * y + T(2, 5) * x + T(2, 6) * x * y};
+
+    return det(J);
+  }
+
+  PUGS_INLINE
+  CubeTransformation(const TinyVector<Dimension>& a,
+                     const TinyVector<Dimension>& b,
+                     const TinyVector<Dimension>& c,
+                     const TinyVector<Dimension>& d,
+                     const TinyVector<Dimension>& e,
+                     const TinyVector<Dimension>& f,
+                     const TinyVector<Dimension>& g,
+                     const TinyVector<Dimension>& h)
+  {
+    for (size_t i = 0; i < Dimension; ++i) {
+      m_coefficients(i, 0) = 0.125 * (-a[i] + b[i] + c[i] - d[i] - e[i] + f[i] + g[i] - h[i]);
+      m_coefficients(i, 1) = 0.125 * (-a[i] - b[i] + c[i] + d[i] - e[i] - f[i] + g[i] + h[i]);
+      m_coefficients(i, 2) = 0.125 * (-a[i] - b[i] - c[i] - d[i] + e[i] + f[i] + g[i] + h[i]);
+      m_coefficients(i, 3) = 0.125 * (+a[i] - b[i] + c[i] - d[i] + e[i] - f[i] + g[i] - h[i]);
+      m_coefficients(i, 4) = 0.125 * (+a[i] + b[i] - c[i] - d[i] - e[i] - f[i] + g[i] + h[i]);
+      m_coefficients(i, 5) = 0.125 * (+a[i] - b[i] - c[i] + d[i] - e[i] + f[i] + g[i] - h[i]);
+      m_coefficients(i, 6) = 0.125 * (-a[i] + b[i] - c[i] + d[i] + e[i] - f[i] + g[i] - h[i]);
+
+      m_shift[i] = 0.125 * (a[i] + b[i] + c[i] + d[i] + e[i] + f[i] + g[i] + h[i]);
+    }
+  }
+
+  ~CubeTransformation() = default;
+};
+
+#endif   // CUBE_TRANSFORMATION_HPP
diff --git a/src/geometry/LineTransformation.hpp b/src/geometry/LineTransformation.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ae0b00a8a1dce7e4b6b6bb13a737ee1114a202f1
--- /dev/null
+++ b/src/geometry/LineTransformation.hpp
@@ -0,0 +1,78 @@
+#ifndef LINE_TRANSFORMATION_HPP
+#define LINE_TRANSFORMATION_HPP
+
+#include <algebra/TinyVector.hpp>
+
+template <size_t GivenDimension>
+class LineTransformation;
+
+template <>
+class LineTransformation<1>
+{
+ public:
+  constexpr static size_t Dimension = 1;
+
+ private:
+  double m_jacobian;
+  TinyVector<Dimension> m_shift;
+
+ public:
+  PUGS_INLINE
+  TinyVector<Dimension>
+  operator()(const TinyVector<1>& x) const
+  {
+    return m_jacobian * x + m_shift;
+  }
+
+  double
+  jacobianDeterminant() const
+  {
+    return m_jacobian;
+  }
+
+  PUGS_INLINE
+  LineTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b)
+  {
+    m_jacobian = 0.5 * (b[0] - a[0]);
+    m_shift    = 0.5 * (a + b);
+  }
+
+  ~LineTransformation() = default;
+};
+
+template <size_t GivenDimension>
+class LineTransformation
+{
+ public:
+  constexpr static size_t Dimension = GivenDimension;
+
+ private:
+  TinyVector<Dimension> m_velocity;
+  const double m_velocity_norm;
+  TinyVector<Dimension> m_shift;
+
+ public:
+  PUGS_INLINE
+  TinyVector<Dimension>
+  operator()(const TinyVector<1>& x) const
+  {
+    return x[0] * m_velocity + m_shift;
+  }
+
+  double
+  velocityNorm() const
+  {
+    return m_velocity_norm;
+  }
+
+  PUGS_INLINE
+  LineTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b)
+    : m_velocity{0.5 * (b - a)}, m_velocity_norm{l2Norm(m_velocity)}, m_shift{0.5 * (a + b)}
+  {
+    static_assert(Dimension > 1);
+  }
+
+  ~LineTransformation() = default;
+};
+
+#endif   // LINE_TRANSFORMATION_HPP
diff --git a/src/geometry/PrismTransformation.hpp b/src/geometry/PrismTransformation.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e6e509ed0c92023b81ea13a3e686392dec52fcd
--- /dev/null
+++ b/src/geometry/PrismTransformation.hpp
@@ -0,0 +1,71 @@
+#ifndef PRISM_TRANSFORMATION_HPP
+#define PRISM_TRANSFORMATION_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+class PrismTransformation
+{
+ private:
+  constexpr static size_t Dimension      = 3;
+  constexpr static size_t NumberOfPoints = 6;
+
+  TinyMatrix<Dimension, NumberOfPoints - 1> m_coefficients;
+  TinyVector<Dimension> m_shift;
+
+ public:
+  PUGS_INLINE
+  TinyVector<Dimension>
+  operator()(const TinyVector<Dimension>& x) const
+  {
+    const TinyVector<NumberOfPoints - 1> X = {x[0], x[1], x[2], x[0] * x[2], x[1] * x[2]};
+    return m_coefficients * X + m_shift;
+  }
+
+  double
+  jacobianDeterminant(const TinyVector<Dimension>& X) const
+  {
+    const auto& T   = m_coefficients;
+    const double& x = X[0];
+    const double& y = X[1];
+    const double& z = X[2];
+
+    const TinyMatrix<Dimension, Dimension> J = {T(0, 0) + T(0, 3) * z,   //
+                                                T(0, 1) + T(0, 4) * z,   //
+                                                T(0, 2) + T(0, 3) * x + T(0, 4) * y,
+                                                //
+                                                T(1, 0) + T(1, 3) * z,   //
+                                                T(1, 1) + T(1, 4) * z,   //
+                                                T(1, 2) + T(1, 3) * x + T(1, 4) * y,
+                                                //
+                                                T(2, 0) + T(2, 3) * z,   //
+                                                T(2, 1) + T(2, 4) * z,   //
+                                                T(2, 2) + T(2, 3) * x + T(2, 4) * y};
+    return det(J);
+  }
+
+  PUGS_INLINE
+  PrismTransformation(const TinyVector<Dimension>& a,
+                      const TinyVector<Dimension>& b,
+                      const TinyVector<Dimension>& c,
+                      const TinyVector<Dimension>& d,
+                      const TinyVector<Dimension>& e,
+                      const TinyVector<Dimension>& f)
+  {
+    static_assert(Dimension == 3, "invalid dimension");
+
+    for (size_t i = 0; i < Dimension; ++i) {
+      m_coefficients(i, 0) = 0.5 * (b[i] - a[i] + e[i] - d[i]);
+      m_coefficients(i, 1) = 0.5 * (c[i] + f[i] - a[i] - d[i]);
+      m_coefficients(i, 2) = 0.5 * (d[i] - a[i]);
+      m_coefficients(i, 3) = 0.5 * (a[i] - b[i] + e[i] - d[i]);
+      m_coefficients(i, 4) = 0.5 * (f[i] - c[i] + a[i] - d[i]);
+    }
+
+    m_shift = 0.5 * (a + d);
+  }
+
+  ~PrismTransformation() = default;
+};
+
+#endif   // PRISM_TRANSFORMATION_HPP
diff --git a/src/geometry/PyramidTransformation.hpp b/src/geometry/PyramidTransformation.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2fa3eaa8d8b010dda5ac3d591b1183db707ea3f8
--- /dev/null
+++ b/src/geometry/PyramidTransformation.hpp
@@ -0,0 +1,68 @@
+#ifndef PYRAMID_TRANSFORMATION_HPP
+#define PYRAMID_TRANSFORMATION_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+class PyramidTransformation
+{
+ private:
+  constexpr static size_t Dimension      = 3;
+  constexpr static size_t NumberOfPoints = 5;
+
+  TinyMatrix<Dimension, NumberOfPoints - 1> m_coefficients;
+  TinyVector<Dimension> m_shift;
+
+ public:
+  PUGS_INLINE
+  TinyVector<Dimension>
+  operator()(const TinyVector<Dimension>& x) const
+  {
+    const TinyVector<NumberOfPoints - 1> X = {x[0], x[1], x[2], x[0] * x[1]};
+    return m_coefficients * X + m_shift;
+  }
+
+  double
+  jacobianDeterminant(const TinyVector<Dimension>& X) const
+  {
+    const auto& T   = m_coefficients;
+    const double& x = X[0];
+    const double& y = X[1];
+
+    const TinyMatrix<Dimension, Dimension> J = {T(0, 0) + T(0, 3) * y,   //
+                                                T(0, 1) + T(0, 3) * x,   //
+                                                T(0, 2),
+                                                //
+                                                T(1, 0) + T(1, 3) * y,   //
+                                                T(1, 1) + T(1, 3) * x,   //
+                                                T(1, 2),
+                                                //
+                                                T(2, 0) + T(2, 3) * y,   //
+                                                T(2, 1) + T(2, 3) * x,   //
+                                                T(2, 2)};
+    return det(J);
+  }
+
+  PUGS_INLINE
+  PyramidTransformation(const TinyVector<Dimension>& a,
+                        const TinyVector<Dimension>& b,
+                        const TinyVector<Dimension>& c,
+                        const TinyVector<Dimension>& d,
+                        const TinyVector<Dimension>& e)
+  {
+    static_assert(Dimension == 3, "invalid dimension");
+
+    m_shift = 0.25 * (a + b + c + d);
+
+    for (size_t i = 0; i < Dimension; ++i) {
+      m_coefficients(i, 0) = 0.5 * (b[i] + c[i]) - m_shift[i];
+      m_coefficients(i, 1) = 0.5 * (c[i] + d[i]) - m_shift[i];
+      m_coefficients(i, 2) = e[i] - m_shift[i];
+      m_coefficients(i, 3) = 0.5 * (a[i] + c[i]) - m_shift[i];
+    }
+  }
+
+  ~PyramidTransformation() = default;
+};
+
+#endif   // PYRAMID_TRANSFORMATION_HPP
diff --git a/src/geometry/SquareTransformation.hpp b/src/geometry/SquareTransformation.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d79ae12c96c696f6be0bcf6245ede0b16c5f2a83
--- /dev/null
+++ b/src/geometry/SquareTransformation.hpp
@@ -0,0 +1,123 @@
+#ifndef SQUARE_TRANSFORMATION_HPP
+#define SQUARE_TRANSFORMATION_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+#include <array>
+
+template <size_t GivenDimension>
+class SquareTransformation;
+
+template <>
+class SquareTransformation<2>
+{
+ public:
+  constexpr static size_t Dimension      = 2;
+  constexpr static size_t NumberOfPoints = 4;
+
+ private:
+  TinyMatrix<Dimension, NumberOfPoints - 1> m_coefficients;
+  TinyVector<Dimension> m_shift;
+
+ public:
+  PUGS_INLINE
+  TinyVector<Dimension>
+  operator()(const TinyVector<2>& x) const
+  {
+    const TinyVector<NumberOfPoints - 1> X = {x[0], x[1], x[0] * x[1]};
+    return m_coefficients * X + m_shift;
+  }
+
+  PUGS_INLINE double
+  jacobianDeterminant(const TinyVector<Dimension>& X) const
+  {
+    const auto& T   = m_coefficients;
+    const double& x = X[0];
+    const double& y = X[1];
+
+    const TinyMatrix<Dimension, Dimension> J = {T(0, 0) + T(0, 2) * y,   //
+                                                T(0, 1) + T(0, 2) * x,
+                                                //
+                                                T(1, 0) + T(1, 2) * y,   //
+                                                T(1, 1) + T(1, 2) * x};
+    return det(J);
+  }
+
+  PUGS_INLINE
+  SquareTransformation(const TinyVector<Dimension>& a,
+                       const TinyVector<Dimension>& b,
+                       const TinyVector<Dimension>& c,
+                       const TinyVector<Dimension>& d)
+  {
+    for (size_t i = 0; i < Dimension; ++i) {
+      m_coefficients(i, 0) = 0.25 * (-a[i] + b[i] + c[i] - d[i]);
+      m_coefficients(i, 1) = 0.25 * (-a[i] - b[i] + c[i] + d[i]);
+      m_coefficients(i, 2) = 0.25 * (+a[i] - b[i] + c[i] - d[i]);
+
+      m_shift[i] = 0.25 * (a[i] + b[i] + c[i] + d[i]);
+    }
+  }
+
+  ~SquareTransformation() = default;
+};
+
+template <size_t GivenDimension>
+class SquareTransformation
+{
+ public:
+  constexpr static size_t Dimension = GivenDimension;
+  static_assert(Dimension == 3, "Square transformation is defined in dimension 2 or 3");
+
+  constexpr static size_t NumberOfPoints = 4;
+
+ private:
+  TinyMatrix<Dimension, NumberOfPoints - 1> m_coefficients;
+  TinyVector<Dimension> m_shift;
+
+ public:
+  PUGS_INLINE
+  TinyVector<Dimension>
+  operator()(const TinyVector<2>& x) const
+  {
+    const TinyVector<NumberOfPoints - 1> X = {x[0], x[1], x[0] * x[1]};
+    return m_coefficients * X + m_shift;
+  }
+
+  PUGS_INLINE double
+  areaVariationNorm(const TinyVector<2>& X) const
+  {
+    const auto& T   = m_coefficients;
+    const double& x = X[0];
+    const double& y = X[1];
+
+    const TinyVector<Dimension> dxT = {T(0, 0) + T(0, 2) * y,   //
+                                       T(1, 0) + T(1, 2) * y,   //
+                                       T(2, 0) + T(2, 2) * y};
+
+    const TinyVector<Dimension> dyT = {T(0, 1) + T(0, 2) * x,   //
+                                       T(1, 1) + T(1, 2) * x,   //
+                                       T(2, 1) + T(2, 2) * x};
+
+    return l2Norm(crossProduct(dxT, dyT));
+  }
+
+  PUGS_INLINE
+  SquareTransformation(const TinyVector<Dimension>& a,
+                       const TinyVector<Dimension>& b,
+                       const TinyVector<Dimension>& c,
+                       const TinyVector<Dimension>& d)
+  {
+    for (size_t i = 0; i < Dimension; ++i) {
+      m_coefficients(i, 0) = 0.25 * (-a[i] + b[i] + c[i] - d[i]);
+      m_coefficients(i, 1) = 0.25 * (-a[i] - b[i] + c[i] + d[i]);
+      m_coefficients(i, 2) = 0.25 * (+a[i] - b[i] + c[i] - d[i]);
+
+      m_shift[i] = 0.25 * (a[i] + b[i] + c[i] + d[i]);
+    }
+  }
+
+  ~SquareTransformation() = default;
+};
+
+#endif   // SQUARE_TRANSFORMATION_HPP
diff --git a/src/geometry/TetrahedronTransformation.hpp b/src/geometry/TetrahedronTransformation.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..64244266957a350e8be8f78c2eab3d5fead629e1
--- /dev/null
+++ b/src/geometry/TetrahedronTransformation.hpp
@@ -0,0 +1,53 @@
+#ifndef TETRAHEDRON_TRANSFORMATION_HPP
+#define TETRAHEDRON_TRANSFORMATION_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+class TetrahedronTransformation
+{
+ public:
+  constexpr static size_t Dimension = 3;
+
+ private:
+  constexpr static size_t NumberOfPoints = Dimension + 1;
+
+  TinyMatrix<Dimension> m_jacobian;
+  TinyVector<Dimension> m_shift;
+  double m_jacobian_determinant;
+
+ public:
+  PUGS_INLINE
+  TinyVector<Dimension>
+  operator()(const TinyVector<Dimension>& x) const
+  {
+    return m_jacobian * x + m_shift;
+  }
+
+  double
+  jacobianDeterminant() const
+  {
+    return m_jacobian_determinant;
+  }
+
+  PUGS_INLINE
+  TetrahedronTransformation(const TinyVector<Dimension>& a,
+                            const TinyVector<Dimension>& b,
+                            const TinyVector<Dimension>& c,
+                            const TinyVector<Dimension>& d)
+  {
+    for (size_t i = 0; i < Dimension; ++i) {
+      m_jacobian(i, 0) = b[i] - a[i];
+      m_jacobian(i, 1) = c[i] - a[i];
+      m_jacobian(i, 2) = d[i] - a[i];
+    }
+
+    m_shift = a;
+
+    m_jacobian_determinant = det(m_jacobian);
+  }
+
+  ~TetrahedronTransformation() = default;
+};
+
+#endif   // TETRAHEDRON_TRANSFORMATION_HPP
diff --git a/src/geometry/TriangleTransformation.hpp b/src/geometry/TriangleTransformation.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9210e0bda55bb19dfdfd877e509801fa87f0a4f0
--- /dev/null
+++ b/src/geometry/TriangleTransformation.hpp
@@ -0,0 +1,93 @@
+#ifndef TRIANGLE_TRANSFORMATION_HPP
+#define TRIANGLE_TRANSFORMATION_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+template <size_t GivenDimension>
+class TriangleTransformation;
+
+template <>
+class TriangleTransformation<2>
+{
+ public:
+  constexpr static size_t Dimension = 2;
+
+ private:
+  TinyMatrix<Dimension> m_jacobian;
+  TinyVector<Dimension> m_shift;
+  double m_jacobian_determinant;
+
+ public:
+  PUGS_INLINE
+  TinyVector<Dimension>
+  operator()(const TinyVector<2>& x) const
+  {
+    return m_jacobian * x + m_shift;
+  }
+
+  double
+  jacobianDeterminant() const
+  {
+    return m_jacobian_determinant;
+  }
+
+  PUGS_INLINE
+  TriangleTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b, const TinyVector<Dimension>& c)
+  {
+    for (size_t i = 0; i < Dimension; ++i) {
+      m_jacobian(i, 0) = b[i] - a[i];
+      m_jacobian(i, 1) = c[i] - a[i];
+    }
+
+    m_shift = a;
+
+    m_jacobian_determinant = det(m_jacobian);
+  }
+
+  ~TriangleTransformation() = default;
+};
+
+template <size_t GivenDimension>
+class TriangleTransformation
+{
+ public:
+  constexpr static size_t Dimension = GivenDimension;
+  static_assert(Dimension == 3, "Triangle transformation is defined in dimension 2 or 3");
+
+ private:
+  TinyMatrix<Dimension, 2> m_jacobian;
+  TinyVector<Dimension> m_shift;
+  double m_area_variation_norm;
+
+ public:
+  PUGS_INLINE
+  TinyVector<Dimension>
+  operator()(const TinyVector<2>& x) const
+  {
+    return m_jacobian * x + m_shift;
+  }
+
+  double
+  areaVariationNorm() const
+  {
+    return m_area_variation_norm;
+  }
+
+  PUGS_INLINE
+  TriangleTransformation(const TinyVector<Dimension>& a, const TinyVector<Dimension>& b, const TinyVector<Dimension>& c)
+  {
+    for (size_t i = 0; i < Dimension; ++i) {
+      m_jacobian(i, 0) = b[i] - a[i];
+      m_jacobian(i, 1) = c[i] - a[i];
+    }
+
+    m_shift = a;
+
+    m_area_variation_norm = l2Norm(crossProduct(b - a, c - a));
+  }
+
+  ~TriangleTransformation() = default;
+};
+
+#endif   // TRIANGLE_TRANSFORMATION_HPP
diff --git a/src/language/modules/MeshModule.cpp b/src/language/modules/MeshModule.cpp
index 61767078aeaa0f8362393196a01ce9cde167a064..fe0e438a80009cf18c15b3f2793361c931d12d68 100644
--- a/src/language/modules/MeshModule.cpp
+++ b/src/language/modules/MeshModule.cpp
@@ -4,7 +4,6 @@
 #include <language/node_processor/ExecutionPolicy.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/FunctionTable.hpp>
-#include <language/utils/PugsFunctionAdapter.hpp>
 #include <language/utils/SymbolTable.hpp>
 #include <language/utils/TypeDescriptor.hpp>
 #include <mesh/CartesianMeshBuilder.hpp>
@@ -12,7 +11,7 @@
 #include <mesh/DiamondDualMeshManager.hpp>
 #include <mesh/GmshReader.hpp>
 #include <mesh/Mesh.hpp>
-#include <mesh/MeshInterpoler.hpp>
+#include <mesh/MeshRelaxer.hpp>
 #include <mesh/MeshTransformer.hpp>
 #include <utils/Exceptions.hpp>
 
@@ -43,7 +42,7 @@ MeshModule::MeshModule()
 
                               ));
 
-  this->_addBuiltinFunction("interpolate",
+  this->_addBuiltinFunction("relax",
                             std::make_shared<BuiltinFunctionEmbedder<
                               std::shared_ptr<const IMesh>(const std::shared_ptr<const IMesh>&,
                                                            const std::shared_ptr<const IMesh>&, const double&)>>(
@@ -51,7 +50,7 @@ MeshModule::MeshModule()
                               [](const std::shared_ptr<const IMesh>& source_mesh,
                                  const std::shared_ptr<const IMesh>& destination_mesh,
                                  const double& theta) -> std::shared_ptr<const IMesh> {
-                                return MeshInterpoler{}.interpolate(source_mesh, destination_mesh, theta);
+                                return MeshRelaxer{}.relax(source_mesh, destination_mesh, theta);
                               }
 
                               ));
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index d989bff45dffa0c0f361607c1488f75f4dc9f0f4..fcebda3dedc014d935a7e73a96785762f4cb00e1 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -1,5 +1,8 @@
 #include <language/modules/SchemeModule.hpp>
 
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussLobattoQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
 #include <language/modules/BinaryOperatorRegisterForVh.hpp>
 #include <language/modules/MathFunctionRegisterForVh.hpp>
 #include <language/modules/UnaryOperatorRegisterForVh.hpp>
@@ -17,9 +20,11 @@
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
 #include <scheme/DiscreteFunctionDescriptorP0.hpp>
 #include <scheme/DiscreteFunctionDescriptorP0Vector.hpp>
+#include <scheme/DiscreteFunctionIntegrator.hpp>
 #include <scheme/DiscreteFunctionInterpoler.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
 #include <scheme/DiscreteFunctionUtils.hpp>
+#include <scheme/DiscreteFunctionVectorIntegrator.hpp>
 #include <scheme/DiscreteFunctionVectorInterpoler.hpp>
 #include <scheme/FixedBoundaryConditionDescriptor.hpp>
 #include <scheme/FourierBoundaryConditionDescriptor.hpp>
@@ -38,6 +43,7 @@ SchemeModule::SchemeModule()
 {
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IDiscreteFunction>>);
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IDiscreteFunctionDescriptor>>);
+  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IQuadratureDescriptor>>);
 
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IBoundaryDescriptor>>);
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IBoundaryConditionDescriptor>>);
@@ -59,6 +65,64 @@ SchemeModule::SchemeModule()
 
                               ));
 
+  this->_addBuiltinFunction("Gauss", std::make_shared<
+                                       BuiltinFunctionEmbedder<std::shared_ptr<const IQuadratureDescriptor>(uint64_t)>>(
+                                       [](uint64_t degree) -> std::shared_ptr<const IQuadratureDescriptor> {
+                                         return std::make_shared<GaussQuadratureDescriptor>(degree);
+                                       }
+
+                                       ));
+
+  this->_addBuiltinFunction("GaussLobatto",
+                            std::make_shared<
+                              BuiltinFunctionEmbedder<std::shared_ptr<const IQuadratureDescriptor>(uint64_t)>>(
+                              [](uint64_t degree) -> std::shared_ptr<const IQuadratureDescriptor> {
+                                return std::make_shared<GaussLobattoQuadratureDescriptor>(degree);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("GaussLegendre",
+                            std::make_shared<
+                              BuiltinFunctionEmbedder<std::shared_ptr<const IQuadratureDescriptor>(uint64_t)>>(
+                              [](uint64_t degree) -> std::shared_ptr<const IQuadratureDescriptor> {
+                                return std::make_shared<GaussLegendreQuadratureDescriptor>(degree);
+                              }
+
+                              ));
+
+  this
+    ->_addBuiltinFunction("integrate",
+                          std::make_shared<BuiltinFunctionEmbedder<
+                            std::shared_ptr<const IDiscreteFunction>(std::shared_ptr<const IMesh>,
+                                                                     std::shared_ptr<const IQuadratureDescriptor>,
+                                                                     std::shared_ptr<const IDiscreteFunctionDescriptor>,
+                                                                     const std::vector<FunctionSymbolId>&)>>(
+                            [](std::shared_ptr<const IMesh> mesh,
+                               std::shared_ptr<const IQuadratureDescriptor> quadrature_descriptor,
+                               std::shared_ptr<const IDiscreteFunctionDescriptor> discrete_function_descriptor,
+                               const std::vector<FunctionSymbolId>& function_id_list)
+                              -> std::shared_ptr<const IDiscreteFunction> {
+                              return DiscreteFunctionVectorIntegrator{mesh, quadrature_descriptor,
+                                                                      discrete_function_descriptor, function_id_list}
+                                .integrate();
+                            }
+
+                            ));
+
+  this->_addBuiltinFunction("integrate",
+                            std::make_shared<BuiltinFunctionEmbedder<
+                              std::shared_ptr<const IDiscreteFunction>(std::shared_ptr<const IMesh>,
+                                                                       std::shared_ptr<const IQuadratureDescriptor>,
+                                                                       const FunctionSymbolId&)>>(
+                              [](std::shared_ptr<const IMesh> mesh,
+                                 std::shared_ptr<const IQuadratureDescriptor> quadrature_descriptor,
+                                 const FunctionSymbolId& function_id) -> std::shared_ptr<const IDiscreteFunction> {
+                                return DiscreteFunctionIntegrator{mesh, quadrature_descriptor, function_id}.integrate();
+                              }
+
+                              ));
+
   this->_addBuiltinFunction(
     "interpolate",
     std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
diff --git a/src/language/modules/SchemeModule.hpp b/src/language/modules/SchemeModule.hpp
index 758b5f9c56fcdc928adfc4678a0a02a8dcfa5bd8..52fc163ee5b6bfcec2cbeceb6d618e9ce3d9c213 100644
--- a/src/language/modules/SchemeModule.hpp
+++ b/src/language/modules/SchemeModule.hpp
@@ -25,6 +25,11 @@ template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IDiscreteFunctionDescriptor>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("Vh_type");
 
+class IQuadratureDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IQuadratureDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("quadrature");
+
 class SchemeModule : public BuiltinModule
 {
   friend class MathFunctionRegisterForVh;
diff --git a/src/language/utils/EvaluateAtPoints.hpp b/src/language/utils/EvaluateAtPoints.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d842dd44b96b02caf23515d4c35da0a0ee277865
--- /dev/null
+++ b/src/language/utils/EvaluateAtPoints.hpp
@@ -0,0 +1,68 @@
+#ifndef EVALUATE_AT_POINTS_HPP
+#define EVALUATE_AT_POINTS_HPP
+
+#include <language/utils/PugsFunctionAdapter.hpp>
+#include <utils/Array.hpp>
+
+class FunctionSymbolId;
+
+template <typename T>
+class EvaluateAtPoints;
+template <typename OutputType, typename InputType>
+class EvaluateAtPoints<OutputType(InputType)> : public PugsFunctionAdapter<OutputType(InputType)>
+{
+  using Adapter = PugsFunctionAdapter<OutputType(InputType)>;
+
+ public:
+  template <typename InputArrayT, typename OutputArrayT>
+  static PUGS_INLINE void
+  evaluateTo(const FunctionSymbolId& function_symbol_id, const InputArrayT& position, OutputArrayT& value)
+  {
+    static_assert(std::is_same_v<std::remove_const_t<typename InputArrayT::data_type>, InputType>,
+                  "invalid input data type");
+    static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>,
+                  "invalid output data type");
+    Assert(size(value) == size(position));
+
+    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
+    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
+
+    auto context_list = Adapter::getContextList(expression);
+
+    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
+    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
+
+    if constexpr (std::is_arithmetic_v<OutputType>) {
+      value.fill(0);
+    } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) {
+      value.fill(zero);
+    } else {
+      static_assert(std::is_same_v<OutputType, double>, "unexpected output type");
+    }
+
+    parallel_for(size(position), [=, &expression, &tokens](typename InputArrayT::index_type i) {
+      const int32_t t = tokens.acquire();
+
+      auto& execution_policy = context_list[t];
+
+      Adapter::convertArgs(execution_policy.currentContext(), position[i]);
+      auto result = expression.execute(execution_policy);
+      value[i]    = convert_result(std::move(result));
+
+      tokens.release(t);
+    });
+  }
+
+  template <class InputArrayT>
+  static PUGS_INLINE Array<OutputType>
+  evaluate(const FunctionSymbolId& function_symbol_id, const InputArrayT& position)
+  {
+    static_assert(std::is_same_v<std::remove_const_t<typename InputArrayT::data_type>, InputType>,
+                  "invalid input data type");
+    Array<OutputType> value(size(position));
+    evaluateTo(function_symbol_id, position, value);
+    return value;
+  }
+};
+
+#endif   // EVALUATE_AT_POINTS_HPP
diff --git a/src/language/utils/IntegrateCellArray.hpp b/src/language/utils/IntegrateCellArray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..41450de3b9675fbe929fcaf0fde855ccb1be2fe4
--- /dev/null
+++ b/src/language/utils/IntegrateCellArray.hpp
@@ -0,0 +1,69 @@
+#ifndef INTEGRATE_CELL_ARRAY_HPP
+#define INTEGRATE_CELL_ARRAY_HPP
+
+#include <language/utils/IntegrateCellValue.hpp>
+#include <mesh/CellType.hpp>
+#include <mesh/ItemArray.hpp>
+
+template <typename T>
+class IntegrateCellArray;
+template <typename OutputType, typename InputType>
+class IntegrateCellArray<OutputType(InputType)>
+{
+  static constexpr size_t Dimension = OutputType::Dimension;
+
+ public:
+  template <typename MeshType>
+  PUGS_INLINE static CellArray<OutputType>
+  integrate(const std::vector<FunctionSymbolId>& function_symbol_id_list,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh)
+  {
+    CellArray<OutputType> cell_array{mesh.connectivity(), function_symbol_id_list.size()};
+
+    for (size_t i_function_symbol = 0; i_function_symbol < function_symbol_id_list.size(); ++i_function_symbol) {
+      const FunctionSymbolId& function_symbol_id = function_symbol_id_list[i_function_symbol];
+      CellValue<OutputType> cell_value =
+        IntegrateCellValue<OutputType(InputType)>::integrate(function_symbol_id, quadrature_descriptor, mesh);
+      parallel_for(
+        cell_value.numberOfItems(),
+        PUGS_LAMBDA(CellId cell_id) { cell_array[cell_id][i_function_symbol] = cell_value[cell_id]; });
+    }
+
+    return cell_array;
+  }
+
+  template <typename MeshType>
+  PUGS_INLINE static Table<OutputType>
+  integrate(const std::vector<FunctionSymbolId>& function_symbol_id_list,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const Array<const CellId>& list_of_cells)
+  {
+    Table<OutputType> table{list_of_cells.size(), function_symbol_id_list.size()};
+
+    for (size_t i_function_symbol = 0; i_function_symbol < function_symbol_id_list.size(); ++i_function_symbol) {
+      const FunctionSymbolId& function_symbol_id = function_symbol_id_list[i_function_symbol];
+      Array<OutputType> array =
+        IntegrateCellValue<OutputType(InputType)>::integrate(function_symbol_id, quadrature_descriptor, mesh,
+                                                             list_of_cells);
+
+      parallel_for(
+        array.size(), PUGS_LAMBDA(size_t i) { table[i][i_function_symbol] = array[i]; });
+    }
+
+    return table;
+  }
+
+  template <typename MeshType>
+  PUGS_INLINE static Table<OutputType>
+  integrate(const std::vector<FunctionSymbolId>& function_symbol_id_list,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const Array<CellId>& list_of_cells)
+  {
+    return integrate(function_symbol_id_list, quadrature_descriptor, mesh, Array<const CellId>{list_of_cells});
+  }
+};
+
+#endif   // INTEGRATE_CELL_ARRAY_HPP
diff --git a/src/language/utils/IntegrateCellValue.hpp b/src/language/utils/IntegrateCellValue.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c4eafc151712219101a42d28ba2e8eb774ab0060
--- /dev/null
+++ b/src/language/utils/IntegrateCellValue.hpp
@@ -0,0 +1,52 @@
+#ifndef INTEGRATE_CELL_VALUE_HPP
+#define INTEGRATE_CELL_VALUE_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <language/utils/IntegrateOnCells.hpp>
+#include <mesh/ItemType.hpp>
+#include <mesh/ItemValue.hpp>
+#include <mesh/Mesh.hpp>
+
+template <typename T>
+class IntegrateCellValue;
+template <typename OutputType, typename InputType>
+class IntegrateCellValue<OutputType(InputType)>
+{
+ public:
+  template <typename MeshType>
+  PUGS_INLINE static CellValue<OutputType>
+  integrate(const FunctionSymbolId& function_symbol_id,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh)
+  {
+    CellValue<OutputType> value(mesh.connectivity());
+    IntegrateOnCells<OutputType(const InputType)>::template integrateTo<MeshType>(function_symbol_id,
+                                                                                  quadrature_descriptor, mesh, value);
+
+    return value;
+  }
+
+  template <typename MeshType>
+  PUGS_INLINE static Array<OutputType>
+  integrate(const FunctionSymbolId& function_symbol_id,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const Array<const CellId>& list_of_cells)
+  {
+    return IntegrateOnCells<OutputType(const InputType)>::integrate(function_symbol_id, quadrature_descriptor, mesh,
+                                                                    list_of_cells);
+  }
+
+  template <typename MeshType>
+  PUGS_INLINE static Array<OutputType>
+  integrate(const FunctionSymbolId& function_symbol_id,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const Array<CellId>& list_of_cells)
+  {
+    return IntegrateOnCells<OutputType(const InputType)>::integrate(function_symbol_id, quadrature_descriptor, mesh,
+                                                                    Array<const CellId>{list_of_cells});
+  }
+};
+
+#endif   // INTEGRATE_CELL_VALUE_HPP
diff --git a/src/language/utils/IntegrateOnCells.hpp b/src/language/utils/IntegrateOnCells.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8b6008d73672a24ee5473369250f00ce05342d7e
--- /dev/null
+++ b/src/language/utils/IntegrateOnCells.hpp
@@ -0,0 +1,603 @@
+#ifndef INTEGRATE_ON_CELLS_HPP
+#define INTEGRATE_ON_CELLS_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/CubeTransformation.hpp>
+#include <geometry/LineTransformation.hpp>
+#include <geometry/PrismTransformation.hpp>
+#include <geometry/PyramidTransformation.hpp>
+#include <geometry/SquareTransformation.hpp>
+#include <geometry/TetrahedronTransformation.hpp>
+#include <geometry/TriangleTransformation.hpp>
+#include <language/utils/PugsFunctionAdapter.hpp>
+#include <mesh/CellType.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemId.hpp>
+#include <utils/Array.hpp>
+
+class FunctionSymbolId;
+
+template <typename T>
+class IntegrateOnCells;
+template <typename OutputType, typename InputType>
+class IntegrateOnCells<OutputType(InputType)> : public PugsFunctionAdapter<OutputType(InputType)>
+{
+ private:
+  using Adapter = PugsFunctionAdapter<OutputType(InputType)>;
+
+  template <size_t Dimension>
+  class AllCells
+  {
+   private:
+    const Connectivity<Dimension>& m_connectivity;
+
+   public:
+    using index_type = CellId;
+
+    PUGS_INLINE
+    CellId
+    cellId(const CellId cell_id) const
+    {
+      return cell_id;
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const
+    {
+      return m_connectivity.numberOfCells();
+    }
+
+    PUGS_INLINE
+    AllCells(const Connectivity<Dimension>& connectivity) : m_connectivity{connectivity} {}
+  };
+
+  class CellList
+  {
+   private:
+    const Array<CellId>& m_cell_list;
+
+   public:
+    using index_type = Array<CellId>::index_type;
+
+    PUGS_INLINE
+    CellId
+    cellId(const index_type index) const
+    {
+      return m_cell_list[index];
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const
+    {
+      return m_cell_list.size();
+    }
+
+    PUGS_INLINE
+    CellList(const Array<CellId>& cell_list) : m_cell_list{cell_list} {}
+  };
+
+  template <typename MeshType, typename OutputArrayT, typename ListTypeT>
+  static PUGS_INLINE void
+  _tensorialIntegrateTo(const FunctionSymbolId& function_symbol_id,
+                        const IQuadratureDescriptor& quadrature_descriptor,
+                        const MeshType& mesh,
+                        const ListTypeT& cell_list,
+                        OutputArrayT& value)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    static_assert(std::is_same_v<TinyVector<Dimension>, InputType>, "invalid input data type");
+    static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>,
+                  "invalid output data type");
+
+    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
+    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
+
+    auto context_list = Adapter::getContextList(expression);
+
+    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
+    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
+
+    if constexpr (std::is_arithmetic_v<OutputType>) {
+      value.fill(0);
+    } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) {
+      value.fill(zero);
+    } else {
+      static_assert(std::is_same_v<OutputType, double>, "unexpected output type");
+    }
+
+    const auto& connectivity = mesh.connectivity();
+
+    const auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+    const auto cell_type           = connectivity.cellType();
+    const auto xr                  = mesh.xr();
+
+    auto invalid_cell = std::make_pair(false, CellId{});
+
+    const auto qf = [&] {
+      if constexpr (Dimension == 1) {
+        return QuadratureManager::instance().getLineFormula(quadrature_descriptor);
+      } else if constexpr (Dimension == 2) {
+        return QuadratureManager::instance().getSquareFormula(quadrature_descriptor);
+      } else {
+        static_assert(Dimension == 3);
+        return QuadratureManager::instance().getCubeFormula(quadrature_descriptor);
+      }
+    }();
+
+    parallel_for(cell_list.size(), [=, &expression, &tokens, &invalid_cell, &qf,
+                                    &cell_list](typename ListTypeT::index_type index) {
+      const int32_t t        = tokens.acquire();
+      auto& execution_policy = context_list[t];
+
+      const CellId cell_id = cell_list.cellId(index);
+
+      const auto cell_to_node = cell_to_node_matrix[cell_id];
+
+      if constexpr (Dimension == 1) {
+        switch (cell_type[cell_id]) {
+        case CellType::Line: {
+          const LineTransformation<1> T(xr[cell_to_node[0]], xr[cell_to_node[1]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      } else if constexpr (Dimension == 2) {
+        switch (cell_type[cell_id]) {
+        case CellType::Triangle: {
+          const SquareTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                          xr[cell_to_node[2]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Quadrangle: {
+          const SquareTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                          xr[cell_to_node[3]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      } else {
+        static_assert(Dimension == 3);
+
+        switch (cell_type[cell_id]) {
+        case CellType::Tetrahedron: {
+          const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                     xr[cell_to_node[2]],   //
+                                     xr[cell_to_node[3]], xr[cell_to_node[3]], xr[cell_to_node[3]],
+                                     xr[cell_to_node[3]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Pyramid: {
+          if (cell_to_node.size() == 5) {
+            const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],   //
+                                       xr[cell_to_node[3]],                                             //
+                                       xr[cell_to_node[4]], xr[cell_to_node[4]], xr[cell_to_node[4]],
+                                       xr[cell_to_node[4]]);
+
+            for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+              const auto xi = qf.point(i_point);
+              Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+              auto result = expression.execute(execution_policy);
+              value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+            }
+          } else {
+            throw NotImplementedError("integration on pyramid with non-quadrangular base (number of nodes " +
+                                      std::to_string(cell_to_node.size()) + ")");
+          }
+          break;
+        }
+        case CellType::Diamond: {
+          if (cell_to_node.size() == 5) {
+            {   // top tetrahedron
+              const CubeTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                          xr[cell_to_node[3]],   //
+                                          xr[cell_to_node[4]], xr[cell_to_node[4]], xr[cell_to_node[4]],
+                                          xr[cell_to_node[4]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T0(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+            {   // bottom tetrahedron
+              const CubeTransformation T1(xr[cell_to_node[3]], xr[cell_to_node[3]], xr[cell_to_node[2]],
+                                          xr[cell_to_node[1]],   //
+                                          xr[cell_to_node[0]], xr[cell_to_node[0]], xr[cell_to_node[0]],
+                                          xr[cell_to_node[0]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T1(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+          } else if (cell_to_node.size() == 6) {
+            {   // top pyramid
+              const CubeTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                          xr[cell_to_node[4]],   //
+                                          xr[cell_to_node[5]], xr[cell_to_node[5]], xr[cell_to_node[5]],
+                                          xr[cell_to_node[5]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T0(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+            {   // bottom pyramid
+              const CubeTransformation T1(xr[cell_to_node[4]], xr[cell_to_node[3]], xr[cell_to_node[2]],
+                                          xr[cell_to_node[1]],   //
+                                          xr[cell_to_node[0]], xr[cell_to_node[0]], xr[cell_to_node[0]],
+                                          xr[cell_to_node[0]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T1(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+          } else {
+            throw NotImplementedError("integration on diamond with non-quadrangular base (" +
+                                      std::to_string(cell_to_node.size()) + ")");
+          }
+          break;
+        }
+        case CellType::Prism: {
+          const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]],   //
+                                     xr[cell_to_node[2]], xr[cell_to_node[2]],   //
+                                     xr[cell_to_node[3]], xr[cell_to_node[4]],   //
+                                     xr[cell_to_node[5]], xr[cell_to_node[5]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Hexahedron: {
+          const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                     xr[cell_to_node[4]], xr[cell_to_node[5]], xr[cell_to_node[6]],
+                                     xr[cell_to_node[7]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      }
+
+      tokens.release(t);
+    });
+
+    if (invalid_cell.first) {
+      std::ostringstream os;
+      os << "invalid cell type for integration: " << name(cell_type[invalid_cell.second]);
+      throw UnexpectedError(os.str());
+    }
+  }
+
+  template <typename MeshType, typename OutputArrayT, typename ListTypeT>
+  static PUGS_INLINE void
+  _directIntegrateTo(const FunctionSymbolId& function_symbol_id,
+                     const IQuadratureDescriptor& quadrature_descriptor,
+                     const MeshType& mesh,
+                     const ListTypeT& cell_list,
+                     OutputArrayT& value)
+  {
+    Assert(not quadrature_descriptor.isTensorial());
+
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    static_assert(std::is_same_v<TinyVector<Dimension>, InputType>, "invalid input data type");
+    static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>,
+                  "invalid output data type");
+
+    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
+    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
+
+    auto context_list = Adapter::getContextList(expression);
+
+    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
+    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
+
+    if constexpr (std::is_arithmetic_v<OutputType>) {
+      value.fill(0);
+    } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) {
+      value.fill(zero);
+    } else {
+      static_assert(std::is_same_v<OutputType, double>, "unexpected output type");
+    }
+
+    const auto& connectivity = mesh.connectivity();
+
+    const auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+    const auto cell_type           = connectivity.cellType();
+    const auto xr                  = mesh.xr();
+
+    auto invalid_cell = std::make_pair(false, CellId{});
+
+    parallel_for(cell_list.size(), [=, &expression, &tokens, &invalid_cell, &quadrature_descriptor,
+                                    &cell_list](const typename ListTypeT::index_type& index) {
+      const int32_t t        = tokens.acquire();
+      auto& execution_policy = context_list[t];
+
+      const CellId cell_id = cell_list.cellId(index);
+
+      const auto cell_to_node = cell_to_node_matrix[cell_id];
+
+      if constexpr (Dimension == 1) {
+        switch (cell_type[cell_id]) {
+        case CellType::Line: {
+          const LineTransformation<1> T(xr[cell_to_node[0]], xr[cell_to_node[1]]);
+          const auto qf = QuadratureManager::instance().getLineFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      } else if constexpr (Dimension == 2) {
+        switch (cell_type[cell_id]) {
+        case CellType::Triangle: {
+          const TriangleTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]]);
+          const auto qf = QuadratureManager::instance().getTriangleFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Quadrangle: {
+          const SquareTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                          xr[cell_to_node[3]]);
+          const auto qf = QuadratureManager::instance().getSquareFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      } else {
+        static_assert(Dimension == 3);
+
+        switch (cell_type[cell_id]) {
+        case CellType::Tetrahedron: {
+          const TetrahedronTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]],   //
+                                            xr[cell_to_node[2]], xr[cell_to_node[3]]);
+          const auto qf = QuadratureManager::instance().getTetrahedronFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Pyramid: {
+          if (cell_to_node.size() == 5) {
+            const PyramidTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],   //
+                                          xr[cell_to_node[3]], xr[cell_to_node[4]]);
+            const auto qf = QuadratureManager::instance().getPyramidFormula(quadrature_descriptor);
+
+            for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+              const auto xi = qf.point(i_point);
+              Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+              auto result = expression.execute(execution_policy);
+              value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+            }
+          } else {
+            throw NotImplementedError("integration on pyramid with non-quadrangular base");
+          }
+          break;
+        }
+        case CellType::Diamond: {
+          if (cell_to_node.size() == 5) {
+            const auto qf = QuadratureManager::instance().getTetrahedronFormula(quadrature_descriptor);
+            {   // top tetrahedron
+              const TetrahedronTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],   //
+                                                 xr[cell_to_node[4]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T0(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant() * convert_result(std::move(result));
+              }
+            }
+            {   // bottom tetrahedron
+              const TetrahedronTransformation T1(xr[cell_to_node[3]], xr[cell_to_node[2]], xr[cell_to_node[1]],   //
+                                                 xr[cell_to_node[0]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T1(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant() * convert_result(std::move(result));
+              }
+            }
+          } else if (cell_to_node.size() == 6) {
+            const auto qf = QuadratureManager::instance().getPyramidFormula(quadrature_descriptor);
+            {   // top pyramid
+              const PyramidTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                             xr[cell_to_node[4]], xr[cell_to_node[5]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T0(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+            {   // bottom pyramid
+              const PyramidTransformation T1(xr[cell_to_node[4]], xr[cell_to_node[3]], xr[cell_to_node[2]],
+                                             xr[cell_to_node[1]], xr[cell_to_node[0]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T1(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+          } else {
+            throw NotImplementedError("integration on diamond with non-quadrangular base (" +
+                                      std::to_string(cell_to_node.size()) + ")");
+          }
+          break;
+        }
+        case CellType::Prism: {
+          const PrismTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                      xr[cell_to_node[3]], xr[cell_to_node[4]], xr[cell_to_node[5]]);
+          const auto qf = QuadratureManager::instance().getPrismFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Hexahedron: {
+          const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                     xr[cell_to_node[4]], xr[cell_to_node[5]], xr[cell_to_node[6]],
+                                     xr[cell_to_node[7]]);
+          const auto qf = QuadratureManager::instance().getCubeFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      }
+
+      tokens.release(t);
+    });
+
+    if (invalid_cell.first) {
+      std::ostringstream os;
+      os << "invalid cell type for integration: " << name(cell_type[invalid_cell.second]);
+      throw UnexpectedError(os.str());
+    }
+  }
+
+ public:
+  template <typename MeshType, typename ArrayT>
+  static PUGS_INLINE void
+  integrateTo(const FunctionSymbolId& function_symbol_id,
+              const IQuadratureDescriptor& quadrature_descriptor,
+              const MeshType& mesh,
+              ArrayT& value)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    if (quadrature_descriptor.isTensorial()) {
+      _tensorialIntegrateTo<MeshType, ArrayT>(function_symbol_id, quadrature_descriptor, mesh,
+                                              AllCells<Dimension>{mesh.connectivity()}, value);
+    } else {
+      _directIntegrateTo<MeshType, ArrayT>(function_symbol_id, quadrature_descriptor, mesh,
+                                           AllCells<Dimension>{mesh.connectivity()}, value);
+    }
+  }
+
+  template <typename MeshType, template <typename DataType> typename ArrayT>
+  static PUGS_INLINE ArrayT<OutputType>
+  integrate(const FunctionSymbolId& function_symbol_id,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const ArrayT<CellId>& cell_list)
+  {
+    ArrayT<OutputType> value(size(cell_list));
+    if (quadrature_descriptor.isTensorial()) {
+      _tensorialIntegrateTo<MeshType, ArrayT<OutputType>>(function_symbol_id, quadrature_descriptor, mesh,
+                                                          CellList{cell_list}, value);
+    } else {
+      _directIntegrateTo<MeshType, ArrayT<OutputType>>(function_symbol_id, quadrature_descriptor, mesh,
+                                                       CellList{cell_list}, value);
+    }
+
+    return value;
+  }
+};
+
+#endif   // INTEGRATE_ON_CELLS_HPP
diff --git a/src/language/utils/InterpolateItemArray.hpp b/src/language/utils/InterpolateItemArray.hpp
index c507c527ca9f1c59070410c09f802f524a3de083..17b72de6d62e0ac68062343010154257c43d3006 100644
--- a/src/language/utils/InterpolateItemArray.hpp
+++ b/src/language/utils/InterpolateItemArray.hpp
@@ -2,17 +2,15 @@
 #define INTERPOLATE_ITEM_ARRAY_HPP
 
 #include <language/utils/InterpolateItemValue.hpp>
-#include <language/utils/PugsFunctionAdapter.hpp>
 #include <mesh/ItemArray.hpp>
 #include <mesh/ItemType.hpp>
 
 template <typename T>
 class InterpolateItemArray;
 template <typename OutputType, typename InputType>
-class InterpolateItemArray<OutputType(InputType)> : public PugsFunctionAdapter<OutputType(InputType)>
+class InterpolateItemArray<OutputType(InputType)>
 {
   static constexpr size_t Dimension = OutputType::Dimension;
-  using Adapter                     = PugsFunctionAdapter<OutputType(InputType)>;
 
  public:
   template <ItemType item_type>
diff --git a/src/language/utils/InterpolateItemValue.hpp b/src/language/utils/InterpolateItemValue.hpp
index de12b563a19ded640f0b4bbae3184f383d90a2f3..dc3ef056b0693d4d32e695a33b7772aa11264b73 100644
--- a/src/language/utils/InterpolateItemValue.hpp
+++ b/src/language/utils/InterpolateItemValue.hpp
@@ -1,46 +1,23 @@
 #ifndef INTERPOLATE_ITEM_VALUE_HPP
 #define INTERPOLATE_ITEM_VALUE_HPP
 
-#include <language/utils/PugsFunctionAdapter.hpp>
+#include <language/utils/EvaluateAtPoints.hpp>
 #include <mesh/ItemType.hpp>
 #include <mesh/ItemValue.hpp>
 
 template <typename T>
 class InterpolateItemValue;
 template <typename OutputType, typename InputType>
-class InterpolateItemValue<OutputType(InputType)> : public PugsFunctionAdapter<OutputType(InputType)>
+class InterpolateItemValue<OutputType(InputType)>
 {
-  static constexpr size_t Dimension = OutputType::Dimension;
-  using Adapter                     = PugsFunctionAdapter<OutputType(InputType)>;
-
  public:
   template <ItemType item_type>
   PUGS_INLINE static ItemValue<OutputType, item_type>
   interpolate(const FunctionSymbolId& function_symbol_id, const ItemValue<const InputType, item_type>& position)
   {
-    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
-    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
-
-    Array<ExecutionPolicy> context_list = Adapter::getContextList(expression);
-
-    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
-    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
     const IConnectivity& connectivity = *position.connectivity_ptr();
-
     ItemValue<OutputType, item_type> value(connectivity);
-    using ItemId = ItemIdT<item_type>;
-
-    parallel_for(connectivity.template numberOf<item_type>(), [=, &expression, &tokens](ItemId i) {
-      const int32_t t = tokens.acquire();
-
-      auto& execution_policy = context_list[t];
-
-      Adapter::convertArgs(execution_policy.currentContext(), position[i]);
-      auto result = expression.execute(execution_policy);
-      value[i]    = convert_result(std::move(result));
-
-      tokens.release(t);
-    });
+    EvaluateAtPoints<OutputType(const InputType)>::evaluateTo(function_symbol_id, position, value);
 
     return value;
   }
@@ -51,31 +28,15 @@ class InterpolateItemValue<OutputType(InputType)> : public PugsFunctionAdapter<O
               const ItemValue<const InputType, item_type>& position,
               const Array<const ItemIdT<item_type>>& list_of_items)
   {
-    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
-    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
-
-    Array<ExecutionPolicy> context_list = Adapter::getContextList(expression);
-
-    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
-    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
-
-    Array<OutputType> value{list_of_items.size()};
+    Array<InputType> item_position{list_of_items.size()};
     using ItemId = ItemIdT<item_type>;
+    parallel_for(
+      list_of_items.size(), PUGS_LAMBDA(size_t i_item) {
+        ItemId item_id        = list_of_items[i_item];
+        item_position[i_item] = position[item_id];
+      });
 
-    parallel_for(list_of_items.size(), [=, &expression, &tokens](size_t i_item) {
-      ItemId item_id  = list_of_items[i_item];
-      const int32_t t = tokens.acquire();
-
-      auto& execution_policy = context_list[t];
-
-      Adapter::convertArgs(execution_policy.currentContext(), position[item_id]);
-      auto result   = expression.execute(execution_policy);
-      value[i_item] = convert_result(std::move(result));
-
-      tokens.release(t);
-    });
-
-    return value;
+    return EvaluateAtPoints<OutputType(const InputType)>::evaluate(function_symbol_id, item_position);
   }
 
   template <ItemType item_type>
diff --git a/src/language/utils/PugsFunctionAdapter.hpp b/src/language/utils/PugsFunctionAdapter.hpp
index 3047652e3017fb2cd193b799a8edf454e3e4c22b..d39e0f4233df70709d3d765146447727f7541ae4 100644
--- a/src/language/utils/PugsFunctionAdapter.hpp
+++ b/src/language/utils/PugsFunctionAdapter.hpp
@@ -6,9 +6,9 @@
 #include <language/utils/ASTNodeDataType.hpp>
 #include <language/utils/ASTNodeDataTypeTraits.hpp>
 #include <language/utils/SymbolTable.hpp>
-#include <utils/Array.hpp>
 #include <utils/Exceptions.hpp>
 #include <utils/PugsMacros.hpp>
+#include <utils/SmallArray.hpp>
 
 #include <Kokkos_Core.hpp>
 
@@ -128,7 +128,7 @@ class PugsFunctionAdapter<OutputType(InputType...)>
   [[nodiscard]] PUGS_INLINE static auto
   getContextList(const ASTNode& expression)
   {
-    Array<ExecutionPolicy> context_list(Kokkos::DefaultExecutionSpace::impl_thread_pool_size());
+    SmallArray<ExecutionPolicy> context_list(Kokkos::DefaultExecutionSpace::impl_thread_pool_size());
     auto& context = expression.m_symbol_table->context();
 
     for (size_t i = 0; i < context_list.size(); ++i) {
diff --git a/src/main.cpp b/src/main.cpp
index cf382d69b491e85968d977e9023ac094d81c3bc2..4b9dfffd985bc5f400ace5986355f55a591ff9f2 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,3 +1,4 @@
+#include <analysis/QuadratureManager.hpp>
 #include <language/PugsParser.hpp>
 #include <mesh/DiamondDualConnectivityManager.hpp>
 #include <mesh/DiamondDualMeshManager.hpp>
@@ -13,6 +14,7 @@ main(int argc, char* argv[])
 
   SynchronizerManager::create();
   RandomEngine::create();
+  QuadratureManager::create();
   MeshDataManager::create();
   DiamondDualConnectivityManager::create();
   DiamondDualMeshManager::create();
@@ -23,6 +25,7 @@ main(int argc, char* argv[])
   DiamondDualConnectivityManager::destroy();
   MeshDataManager::destroy();
   RandomEngine::destroy();
+  QuadratureManager::destroy();
   SynchronizerManager::destroy();
 
   finalize();
diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt
index f536bae7afe8b6d1bc979e4abd9feccaf7c86a7d..309e3323a435571b14d0e0d0f631675558c6314b 100644
--- a/src/mesh/CMakeLists.txt
+++ b/src/mesh/CMakeLists.txt
@@ -20,7 +20,7 @@ add_library(
   MeshFaceBoundary.cpp
   MeshFlatFaceBoundary.cpp
   MeshFlatNodeBoundary.cpp
-  MeshInterpoler.cpp
+  MeshRelaxer.cpp
   MeshLineNodeBoundary.cpp
   MeshNodeBoundary.cpp
   MeshRandomizer.cpp
diff --git a/src/mesh/ConnectivityBuilderBase.cpp b/src/mesh/ConnectivityBuilderBase.cpp
index 60c38bfb84fb975c0e2cdc3b07d1d97e39eb0021..3da4837ee005fffaf7744165e1470f6ac5730183 100644
--- a/src/mesh/ConnectivityBuilderBase.cpp
+++ b/src/mesh/ConnectivityBuilderBase.cpp
@@ -116,10 +116,36 @@ ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities(ConnectivityD
         face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
         break;
       }
+      case CellType::Prism: {
+        // face 0
+        Face f0({cell_nodes[2], cell_nodes[1], cell_nodes[0]}, node_number_vector);
+        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
+
+        // face 1
+        Face f1({cell_nodes[3], cell_nodes[4], cell_nodes[5]}, node_number_vector);
+        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
+
+        // face 2
+        Face f2({cell_nodes[1], cell_nodes[2], cell_nodes[5], cell_nodes[4]}, node_number_vector);
+        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+
+        // face 3
+        Face f3({cell_nodes[0], cell_nodes[1], cell_nodes[4], cell_nodes[3]}, node_number_vector);
+        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+
+        // face 4
+        Face f4({cell_nodes[2], cell_nodes[0], cell_nodes[3], cell_nodes[5]}, node_number_vector);
+        face_cells_map[f4].emplace_back(std::make_tuple(j, 4, f4.reversed()));
+
+        cell_nb_faces[j] = 5;
+        break;
+      }
       case CellType::Pyramid: {
         cell_nb_faces[j] = cell_nodes.size();
-        std::vector<unsigned int> base_nodes;
-        std::copy_n(cell_nodes.begin(), cell_nodes.size() - 1, std::back_inserter(base_nodes));
+        std::vector<unsigned int> base_nodes(cell_nodes.size() - 1);
+        for (size_t i = 0; i < base_nodes.size(); ++i) {
+          base_nodes[base_nodes.size() - 1 - i] = cell_nodes[i];
+        }
 
         // base face
         {
@@ -332,6 +358,22 @@ ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities(Co
         descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
         break;
       }
+      case CellType::Prism: {
+        constexpr int local_edge[12][2] = {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}, {0, 3}, {1, 4}, {2, 5}};
+        std::vector<unsigned int> cell_edge_vector;
+        cell_edge_vector.reserve(9);
+        for (int i_edge = 0; i_edge < 9; ++i_edge) {
+          const auto e = local_edge[i_edge];
+          Edge edge{{cell_nodes[e[0]], cell_nodes[e[1]]}, node_number_vector};
+          auto i = edge_id_map.find(edge);
+          if (i == edge_id_map.end()) {
+            throw NormalError("could not find this edge");
+          }
+          cell_edge_vector.push_back(i->second);
+        }
+        descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
+        break;
+      }
       case CellType::Pyramid: {
         const size_t number_of_edges = 2 * cell_nodes.size();
         std::vector<unsigned int> base_nodes;
diff --git a/src/mesh/DiamondDualConnectivityBuilder.cpp b/src/mesh/DiamondDualConnectivityBuilder.cpp
index 31d54be8997e9571958399f17c44dbcede1c35e4..bb9c9b3b643c87c4a71de31cf6a2a5c60e53b0e2 100644
--- a/src/mesh/DiamondDualConnectivityBuilder.cpp
+++ b/src/mesh/DiamondDualConnectivityBuilder.cpp
@@ -8,7 +8,6 @@
 #include <mesh/Mesh.hpp>
 #include <mesh/RefId.hpp>
 #include <utils/Array.hpp>
-#include <utils/ArrayUtils.hpp>
 #include <utils/Messenger.hpp>
 
 #include <vector>
@@ -87,21 +86,27 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityDescriptor(const Connec
   diamond_descriptor.cell_type_vector.resize(diamond_number_of_cells);
 
   const auto& primal_face_to_cell_matrix = primal_connectivity.faceToCellMatrix();
+  const auto& primal_face_to_node_matrix = primal_connectivity.faceToNodeMatrix();
 
+  static_assert(Dimension > 1);
   parallel_for(primal_number_of_faces, [&](FaceId face_id) {
     const size_t i_cell               = face_id;
     const auto& primal_face_cell_list = primal_face_to_cell_matrix[face_id];
 
-    if (primal_face_cell_list.size() == 1) {
-      diamond_descriptor.cell_type_vector[i_cell] = CellType::Triangle;
-    } else {
-      Assert(primal_face_cell_list.size() == 2);
-      diamond_descriptor.cell_type_vector[i_cell] = CellType::Quadrangle;
-    }
-
-    if constexpr (Dimension == 3) {
+    if constexpr (Dimension == 2) {
+      if (primal_face_cell_list.size() == 1) {
+        diamond_descriptor.cell_type_vector[i_cell] = CellType::Triangle;
+      } else {
+        Assert(primal_face_cell_list.size() == 2);
+        diamond_descriptor.cell_type_vector[i_cell] = CellType::Quadrangle;
+      }
+    } else if constexpr (Dimension == 3) {
       if (primal_face_cell_list.size() == 1) {
-        diamond_descriptor.cell_type_vector[i_cell] = CellType::Pyramid;
+        if (primal_face_to_node_matrix[face_id].size() == 3) {
+          diamond_descriptor.cell_type_vector[i_cell] = CellType::Tetrahedron;
+        } else {
+          diamond_descriptor.cell_type_vector[i_cell] = CellType::Pyramid;
+        }
       } else {
         Assert(primal_face_cell_list.size() == 2);
         diamond_descriptor.cell_type_vector[i_cell] = CellType::Diamond;
@@ -111,7 +116,6 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityDescriptor(const Connec
 
   diamond_descriptor.cell_to_node_vector.resize(diamond_number_of_cells);
 
-  const auto& primal_face_to_node_matrix              = primal_connectivity.faceToNodeMatrix();
   const auto& primal_face_local_number_in_their_cells = primal_connectivity.faceLocalNumbersInTheirCells();
   const auto& cell_face_is_reversed                   = primal_connectivity.cellFaceIsReversed();
   parallel_for(primal_number_of_faces, [&](FaceId face_id) {
@@ -130,12 +134,17 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityDescriptor(const Connec
       diamond_descriptor.cell_to_node_vector[i_diamond_cell][primal_face_node_list.size()] =
         primal_number_of_nodes + cell_id;
 
-      if (cell_face_is_reversed(cell_id, i_face_in_cell)) {
-        if constexpr (Dimension == 2) {
+      if constexpr (Dimension == 2) {
+        if (cell_face_is_reversed(cell_id, i_face_in_cell)) {
           std::swap(diamond_descriptor.cell_to_node_vector[i_diamond_cell][0],
                     diamond_descriptor.cell_to_node_vector[i_diamond_cell][1]);
-
-        } else {
+        }
+      } else {
+        if (not cell_face_is_reversed(cell_id, i_face_in_cell)) {
+          // In 3D the basis of the pyramid is described in the
+          // indirect way IF the face is not reversed. In other words
+          // the "topological normal" must point to the "top" of the
+          // pyramid.
           for (size_t i_node = 0; i_node < primal_face_node_list.size() / 2; ++i_node) {
             std::swap(diamond_descriptor.cell_to_node_vector[i_diamond_cell][i_node],
                       diamond_descriptor
diff --git a/src/mesh/GmshReader.cpp b/src/mesh/GmshReader.cpp
index 3a83ef5c3f49d068fbfd8e62d4ff002df50a8845..e91638131b70113a768ba5398869d4d729144b0d 100644
--- a/src/mesh/GmshReader.cpp
+++ b/src/mesh/GmshReader.cpp
@@ -9,7 +9,6 @@
 #include <mesh/ItemValueUtils.hpp>
 #include <mesh/Mesh.hpp>
 #include <mesh/RefItemList.hpp>
-#include <utils/ArrayUtils.hpp>
 #include <utils/Exceptions.hpp>
 
 #include <rang.hpp>
@@ -249,9 +248,20 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
     descriptor.cell_number_vector[jh] = gmsh_data.__hexahedra_number[j];
   }
 
+  const size_t nb_prisms = gmsh_data.__prisms.size();
+  for (size_t j = 0; j < nb_prisms; ++j) {
+    const size_t jp = nb_tetrahedra + nb_hexahedra + j;
+    descriptor.cell_to_node_vector[jp].resize(6);
+    for (int r = 0; r < 6; ++r) {
+      descriptor.cell_to_node_vector[jp][r] = gmsh_data.__prisms[j][r];
+    }
+    descriptor.cell_type_vector[jp]   = CellType::Prism;
+    descriptor.cell_number_vector[jp] = gmsh_data.__prisms_number[j];
+  }
+
   const size_t nb_pyramids = gmsh_data.__pyramids.size();
   for (size_t j = 0; j < nb_pyramids; ++j) {
-    const size_t jh = nb_tetrahedra + nb_hexahedra + j;
+    const size_t jh = nb_tetrahedra + nb_hexahedra + nb_prisms + j;
     descriptor.cell_to_node_vector[jh].resize(5);
     for (int r = 0; r < 5; ++r) {
       descriptor.cell_to_node_vector[jh][r] = gmsh_data.__pyramids[j][r];
@@ -504,6 +514,8 @@ GmshReader::GmshReader(const std::string& filename) : m_filename(filename)
     __keywordList["$EndElements"]      = ENDELEMENTS;
     __keywordList["$PhysicalNames"]    = PHYSICALNAMES;
     __keywordList["$EndPhysicalNames"] = ENDPHYSICALNAMES;
+    __keywordList["$Periodic"]         = PERIODIC;
+    __keywordList["$EndPeriodic"]      = ENDPERIODIC;
 
     __numberOfPrimitiveNodes.resize(16);
     __numberOfPrimitiveNodes[0]  = 2;    // edge
@@ -533,7 +545,7 @@ GmshReader::GmshReader(const std::string& filename) : m_filename(filename)
     __primitivesNames[4]      = "hexahedra";
     __supportedPrimitives[4]  = true;
     __primitivesNames[5]      = "prisms";
-    __supportedPrimitives[5]  = false;
+    __supportedPrimitives[5]  = true;
     __primitivesNames[6]      = "pyramids";
     __supportedPrimitives[6]  = true;
     __primitivesNames[7]      = "second order edges";
@@ -719,6 +731,24 @@ GmshReader::__readPhysicalNames2_2()
   }
 }
 
+void
+GmshReader::__readPeriodic2_2()
+{
+  // This is just a compatibility reading, periodic information is not
+  // used
+  const int number_of_periodic = this->_getInteger();
+  for (int i = 0; i < number_of_periodic; ++i) {
+    [[maybe_unused]] const int dimension              = this->_getInteger();
+    [[maybe_unused]] const int id                     = this->_getInteger();
+    [[maybe_unused]] const int master_id              = this->_getInteger();
+    [[maybe_unused]] const int nb_corresponding_nodes = this->_getInteger();
+    for (int i_node = 0; i_node < nb_corresponding_nodes; ++i_node) {
+      [[maybe_unused]] const int node_id   = this->_getInteger();
+      [[maybe_unused]] const int master_id = this->_getInteger();
+    }
+  }
+}
+
 // std::shared_ptr<IConnectivity>
 // GmshReader::_buildConnectivity3D(const size_t nb_cells)
 // {
@@ -1027,6 +1057,12 @@ GmshReader::__proceedData()
           m_mesh_data.__hexahedra_number.resize(elementNumber[i]);
           break;
         }
+        case 5: {   // prism
+          m_mesh_data.__prisms = Array<GmshData::Prism>(elementNumber[i]);
+          m_mesh_data.__prisms_ref.resize(elementNumber[i]);
+          m_mesh_data.__prisms_number.resize(elementNumber[i]);
+          break;
+        }
         case 6: {   // pyramid
           m_mesh_data.__pyramids = Array<GmshData::Pyramid>(elementNumber[i]);
           m_mesh_data.__pyramids_ref.resize(elementNumber[i]);
@@ -1040,7 +1076,6 @@ GmshReader::__proceedData()
           break;
         }
           // Unsupported entities
-        case 5:    // prism
         case 7:    // second order edge
         case 8:    // second order triangle
         case 9:    // second order quadrangle
@@ -1170,6 +1205,24 @@ GmshReader::__proceedData()
       hexahedronNumber++;   // one more hexahedron
       break;
     }
+    case 5: {   // prism
+      int& prism_number = elementNumber[5];
+      const int a       = m_mesh_data.__verticesCorrepondance[elementVertices[0]];
+      const int b       = m_mesh_data.__verticesCorrepondance[elementVertices[1]];
+      const int c       = m_mesh_data.__verticesCorrepondance[elementVertices[2]];
+      const int d       = m_mesh_data.__verticesCorrepondance[elementVertices[3]];
+      const int e       = m_mesh_data.__verticesCorrepondance[elementVertices[4]];
+      const int f       = m_mesh_data.__verticesCorrepondance[elementVertices[5]];
+      if ((a < 0) or (b < 0) or (c < 0) or (d < 0) or (e < 0) or (f < 0)) {
+        throw NormalError("reading file '" + m_filename + "': error reading element " + std::to_string(i) +
+                          " [bad vertices definition]");
+      }
+      m_mesh_data.__prisms[prism_number]        = GmshData::Prism(a, b, c, d, e, f);
+      m_mesh_data.__prisms_ref[prism_number]    = m_mesh_data.__references[i];
+      m_mesh_data.__prisms_number[prism_number] = m_mesh_data.__elementNumber[i];
+      prism_number++;   // one more prism
+      break;
+    }
     case 6: {   // pyramid
       int& pyramid_number = elementNumber[6];
 
@@ -1182,7 +1235,7 @@ GmshReader::__proceedData()
         throw NormalError("reading file '" + m_filename + "': error reading element " + std::to_string(i) +
                           " [bad vertices definition]");
       }
-      m_mesh_data.__pyramids[pyramid_number]        = GmshData::Pyramid(d, c, b, a, e);
+      m_mesh_data.__pyramids[pyramid_number]        = GmshData::Pyramid(a, b, c, d, e);
       m_mesh_data.__pyramids_ref[pyramid_number]    = m_mesh_data.__references[i];
       m_mesh_data.__pyramids_number[pyramid_number] = m_mesh_data.__elementNumber[i];
       pyramid_number++;   // one more hexahedron
@@ -1198,7 +1251,6 @@ GmshReader::__proceedData()
       point_number++;
       break;
     }
-    case 5:      // prism
     case 7:      // second order edge
     case 8:      // second order triangle
     case 9:      // second order quadrangle
@@ -1353,7 +1405,15 @@ GmshReader::__readGmshFormat2_2()
     case PHYSICALNAMES: {
       this->__readPhysicalNames2_2();
       if (this->__nextKeyword().second != ENDPHYSICALNAMES) {
-        throw NormalError("reading file '" + m_filename + "': expecting $EndNodes, '" + kw.first + "' was found");
+        throw NormalError("reading file '" + m_filename + "': expecting $EndPhysicalNames, '" + kw.first +
+                          "' was found");
+      }
+      break;
+    }
+    case PERIODIC: {
+      this->__readPeriodic2_2();
+      if (this->__nextKeyword().second != ENDPERIODIC) {
+        throw NormalError("reading file '" + m_filename + "': expecting $EndPeriodic, '" + kw.first + "' was found");
       }
       break;
     }
diff --git a/src/mesh/GmshReader.hpp b/src/mesh/GmshReader.hpp
index 36e21508f10eda492ec1128816215d5a4d8c624d..860695ef57143b1b67575f54da95c12f8c7c2a01 100644
--- a/src/mesh/GmshReader.hpp
+++ b/src/mesh/GmshReader.hpp
@@ -109,6 +109,11 @@ class GmshReader : public MeshBuilderBase
     std::vector<int> __pyramids_ref;
     std::vector<int> __pyramids_number;
 
+    using Prism = TinyVector<6, unsigned int>;
+    Array<Prism> __prisms;
+    std::vector<int> __prisms_ref;
+    std::vector<int> __prisms_number;
+
     using Hexahedron = TinyVector<8, unsigned int>;
     Array<Hexahedron> __hexahedra;
     std::vector<int> __hexahedra_ref;
@@ -212,6 +217,8 @@ class GmshReader : public MeshBuilderBase
     ENDELEMENTS,
     PHYSICALNAMES,
     ENDPHYSICALNAMES,
+    PERIODIC,
+    ENDPERIODIC,
 
     Unknown,
     EndOfFile
@@ -268,6 +275,12 @@ class GmshReader : public MeshBuilderBase
    */
   void __readPhysicalNames2_2();
 
+  /**
+   * Reads periodic
+   *
+   */
+  void __readPeriodic2_2();
+
  public:
   GmshReader(const std::string& filename);
   ~GmshReader() = default;
diff --git a/src/mesh/ItemValue.hpp b/src/mesh/ItemValue.hpp
index ebc5f40511a0be6d1eeb519a0660b494d268f150..d48468f3931a2dc0dc546cb2a2dfee630df9e87e 100644
--- a/src/mesh/ItemValue.hpp
+++ b/src/mesh/ItemValue.hpp
@@ -175,6 +175,13 @@ class ItemValue
   ~ItemValue() = default;
 };
 
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+PUGS_INLINE size_t
+size(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
+{
+  return item_value.numberOfItems();
+}
+
 template <typename DataType>
 using NodeValue = ItemValue<DataType, ItemType::node>;
 
diff --git a/src/mesh/MeshInterpoler.hpp b/src/mesh/MeshInterpoler.hpp
deleted file mode 100644
index f8a79eea4918bf71b6b7586683a14f2a67194df9..0000000000000000000000000000000000000000
--- a/src/mesh/MeshInterpoler.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef MESH_INTERPOLER_HPP
-#define MESH_INTERPOLER_HPP
-
-class IMesh;
-
-template <typename ConnectivityType>
-class Mesh;
-
-#include <memory>
-
-class MeshInterpoler
-{
- private:
-  template <typename ConnectivityType>
-  std::shared_ptr<const Mesh<ConnectivityType>> _interpolate(const Mesh<ConnectivityType>& source_mesh,
-                                                             const Mesh<ConnectivityType>& destination_mesh,
-                                                             const double& theta) const;
-
- public:
-  std::shared_ptr<const IMesh> interpolate(const std::shared_ptr<const IMesh>& p_source_mesh,
-                                           const std::shared_ptr<const IMesh>& p_destination_mesh,
-                                           const double& theta) const;
-  MeshInterpoler()  = default;
-  ~MeshInterpoler() = default;
-};
-
-#endif   // MESH_INTERPOLER_HPP
diff --git a/src/mesh/MeshInterpoler.cpp b/src/mesh/MeshRelaxer.cpp
similarity index 57%
rename from src/mesh/MeshInterpoler.cpp
rename to src/mesh/MeshRelaxer.cpp
index 26cba4bff5eeca4b75690b47e30126bac070646b..bda3cc7d17680bf8e2b43596d2e9ad2c7d9759ed 100644
--- a/src/mesh/MeshInterpoler.cpp
+++ b/src/mesh/MeshRelaxer.cpp
@@ -1,13 +1,13 @@
-#include <mesh/MeshInterpoler.hpp>
+#include <mesh/MeshRelaxer.hpp>
 
 #include <mesh/Connectivity.hpp>
 #include <mesh/Mesh.hpp>
 
 template <typename ConnectivityType>
 std::shared_ptr<const Mesh<ConnectivityType>>
-MeshInterpoler::_interpolate(const Mesh<ConnectivityType>& source_mesh,
-                             const Mesh<ConnectivityType>& destination_mesh,
-                             const double& theta) const
+MeshRelaxer::_relax(const Mesh<ConnectivityType>& source_mesh,
+                    const Mesh<ConnectivityType>& destination_mesh,
+                    const double& theta) const
 {
   if (source_mesh.shared_connectivity() == destination_mesh.shared_connectivity()) {
     const ConnectivityType& connectivity = source_mesh.connectivity();
@@ -23,14 +23,14 @@ MeshInterpoler::_interpolate(const Mesh<ConnectivityType>& source_mesh,
 
     return std::make_shared<Mesh<ConnectivityType>>(source_mesh.shared_connectivity(), theta_xr);
   } else {
-    throw NormalError("interpolated meshes must share the same connectivity");
+    throw NormalError("relaxed meshes must share the same connectivity");
   }
 }
 
 std::shared_ptr<const IMesh>
-MeshInterpoler::interpolate(const std::shared_ptr<const IMesh>& p_source_mesh,
-                            const std::shared_ptr<const IMesh>& p_destination_mesh,
-                            const double& theta) const
+MeshRelaxer::relax(const std::shared_ptr<const IMesh>& p_source_mesh,
+                   const std::shared_ptr<const IMesh>& p_destination_mesh,
+                   const double& theta) const
 {
   if (p_source_mesh->dimension() != p_destination_mesh->dimension()) {
     throw NormalError("incompatible mesh dimensions");
@@ -38,18 +38,18 @@ MeshInterpoler::interpolate(const std::shared_ptr<const IMesh>& p_source_mesh,
     switch (p_source_mesh->dimension()) {
     case 1: {
       using MeshType = Mesh<Connectivity<1>>;
-      return this->_interpolate(dynamic_cast<const MeshType&>(*p_source_mesh),
-                                dynamic_cast<const MeshType&>(*p_destination_mesh), theta);
+      return this->_relax(dynamic_cast<const MeshType&>(*p_source_mesh),
+                          dynamic_cast<const MeshType&>(*p_destination_mesh), theta);
     }
     case 2: {
       using MeshType = Mesh<Connectivity<2>>;
-      return this->_interpolate(dynamic_cast<const MeshType&>(*p_source_mesh),
-                                dynamic_cast<const MeshType&>(*p_destination_mesh), theta);
+      return this->_relax(dynamic_cast<const MeshType&>(*p_source_mesh),
+                          dynamic_cast<const MeshType&>(*p_destination_mesh), theta);
     }
     case 3: {
       using MeshType = Mesh<Connectivity<3>>;
-      return this->_interpolate(dynamic_cast<const MeshType&>(*p_source_mesh),
-                                dynamic_cast<const MeshType&>(*p_destination_mesh), theta);
+      return this->_relax(dynamic_cast<const MeshType&>(*p_source_mesh),
+                          dynamic_cast<const MeshType&>(*p_destination_mesh), theta);
     }
     default: {
       throw UnexpectedError("invalid mesh dimension");
diff --git a/src/mesh/MeshRelaxer.hpp b/src/mesh/MeshRelaxer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..96846cbadfefe61902199e927c99ecf96aaf176a
--- /dev/null
+++ b/src/mesh/MeshRelaxer.hpp
@@ -0,0 +1,27 @@
+#ifndef MESH_RELAXER_HPP
+#define MESH_RELAXER_HPP
+
+class IMesh;
+
+template <typename ConnectivityType>
+class Mesh;
+
+#include <memory>
+
+class MeshRelaxer
+{
+ private:
+  template <typename ConnectivityType>
+  std::shared_ptr<const Mesh<ConnectivityType>> _relax(const Mesh<ConnectivityType>& source_mesh,
+                                                       const Mesh<ConnectivityType>& destination_mesh,
+                                                       const double& theta) const;
+
+ public:
+  std::shared_ptr<const IMesh> relax(const std::shared_ptr<const IMesh>& p_source_mesh,
+                                     const std::shared_ptr<const IMesh>& p_destination_mesh,
+                                     const double& theta) const;
+  MeshRelaxer()  = default;
+  ~MeshRelaxer() = default;
+};
+
+#endif   // MESH_RELAXER_HPP
diff --git a/src/mesh/MeshTransformer.cpp b/src/mesh/MeshTransformer.cpp
index 67b3688926dcfb765c44799cff4674955515eb8b..167c4f337cd5c48147594623f3ae436874a47a22 100644
--- a/src/mesh/MeshTransformer.cpp
+++ b/src/mesh/MeshTransformer.cpp
@@ -3,15 +3,12 @@
 #include <mesh/Connectivity.hpp>
 #include <mesh/Mesh.hpp>
 
-#include <language/utils/FunctionTable.hpp>
-#include <language/utils/PugsFunctionAdapter.hpp>
-#include <language/utils/SymbolTable.hpp>
+#include <language/utils/EvaluateAtPoints.hpp>
 
 template <typename OutputType, typename InputType>
-class MeshTransformer::MeshTransformation<OutputType(InputType)> : public PugsFunctionAdapter<OutputType(InputType)>
+class MeshTransformer::MeshTransformation<OutputType(InputType)>
 {
   static constexpr size_t Dimension = OutputType::Dimension;
-  using Adapter                     = PugsFunctionAdapter<OutputType(InputType)>;
 
  public:
   static inline std::shared_ptr<Mesh<Connectivity<Dimension>>>
@@ -20,28 +17,9 @@ class MeshTransformer::MeshTransformation<OutputType(InputType)> : public PugsFu
     using MeshType             = Mesh<Connectivity<Dimension>>;
     const MeshType& given_mesh = dynamic_cast<const MeshType&>(*p_mesh);
 
-    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
-    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
-
-    Array<ExecutionPolicy> context_list = Adapter::getContextList(expression);
-
-    NodeValue<const InputType> given_xr = given_mesh.xr();
     NodeValue<OutputType> xr(given_mesh.connectivity());
-
-    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
-    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
-
-    parallel_for(given_mesh.numberOfNodes(), [=, &expression, &tokens](NodeId r) {
-      const int32_t t = tokens.acquire();
-
-      auto& execution_policy = context_list[t];
-
-      Adapter::convertArgs(execution_policy.currentContext(), given_xr[r]);
-      auto result = expression.execute(execution_policy);
-      xr[r]       = convert_result(std::move(result));
-
-      tokens.release(t);
-    });
+    NodeValue<const InputType> given_xr = given_mesh.xr();
+    EvaluateAtPoints<OutputType(InputType)>::evaluateTo(function_symbol_id, given_xr, xr);
 
     return std::make_shared<MeshType>(given_mesh.shared_connectivity(), xr);
   }
diff --git a/src/output/VTKWriter.cpp b/src/output/VTKWriter.cpp
index d01476cac3da67c1400efef7e3603e9bb91e096d..4f40e9af371333d2879ca28a2671278c365d6bbb 100644
--- a/src/output/VTKWriter.cpp
+++ b/src/output/VTKWriter.cpp
@@ -356,7 +356,6 @@ VTKWriter::_write(const std::shared_ptr<const MeshType>& mesh,
   // Adding basic mesh information
   output_named_item_data_set.add(NamedItemData{"cell_number", mesh->connectivity().cellNumber()});
   output_named_item_data_set.add(NamedItemData{"node_number", mesh->connectivity().nodeNumber()});
-  output_named_item_data_set.add(NamedItemData{"cell_center", MeshDataManager::instance().getMeshData(*mesh).xj()});
 
   if (parallel::rank() == 0) {   // write PVTK file
     std::ofstream fout(_getFilenamePVTU());
diff --git a/src/scheme/CMakeLists.txt b/src/scheme/CMakeLists.txt
index 2131f2fec0748407ea7bb21a3375d398e1f6242a..115d7bc08ce9aa72dced807b9120431f17543864 100644
--- a/src/scheme/CMakeLists.txt
+++ b/src/scheme/CMakeLists.txt
@@ -3,6 +3,8 @@
 add_library(
   PugsScheme
   AcousticSolver.cpp
+  DiscreteFunctionIntegrator.cpp
   DiscreteFunctionInterpoler.cpp
   DiscreteFunctionUtils.cpp
+  DiscreteFunctionVectorIntegrator.cpp
   DiscreteFunctionVectorInterpoler.cpp)
diff --git a/src/scheme/CellIntegrator.hpp b/src/scheme/CellIntegrator.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6960901d335f894e00a86239772f33f3abc50c67
--- /dev/null
+++ b/src/scheme/CellIntegrator.hpp
@@ -0,0 +1,574 @@
+#ifndef CELL_INTEGRATOR_HPP
+#define CELL_INTEGRATOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/CubeTransformation.hpp>
+#include <geometry/LineTransformation.hpp>
+#include <geometry/PrismTransformation.hpp>
+#include <geometry/PyramidTransformation.hpp>
+#include <geometry/SquareTransformation.hpp>
+#include <geometry/TetrahedronTransformation.hpp>
+#include <geometry/TriangleTransformation.hpp>
+#include <mesh/CellType.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemId.hpp>
+#include <utils/Array.hpp>
+
+#include <functional>
+
+class CellIntegrator
+{
+ private:
+  template <size_t Dimension>
+  class AllCells
+  {
+   private:
+    const Connectivity<Dimension>& m_connectivity;
+
+   public:
+    using index_type = CellId;
+
+    PUGS_INLINE
+    CellId
+    cellId(const CellId cell_id) const
+    {
+      return cell_id;
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const
+    {
+      return m_connectivity.numberOfCells();
+    }
+
+    PUGS_INLINE
+    AllCells(const Connectivity<Dimension>& connectivity) : m_connectivity{connectivity} {}
+  };
+
+  template <template <typename T> typename ArrayT>
+  class CellList
+  {
+   private:
+    const ArrayT<CellId>& m_cell_list;
+
+   public:
+    using index_type = typename ArrayT<CellId>::index_type;
+
+    PUGS_INLINE
+    CellId
+    cellId(const index_type index) const
+    {
+      return m_cell_list[index];
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const
+    {
+      return m_cell_list.size();
+    }
+
+    PUGS_INLINE
+    CellList(const ArrayT<CellId>& cell_list) : m_cell_list{cell_list} {}
+  };
+
+  template <typename MeshType, typename OutputArrayT, typename ListTypeT, typename OutputType, typename InputType>
+  static PUGS_INLINE void
+  _tensorialIntegrateTo(std::function<OutputType(InputType)> function,
+                        const IQuadratureDescriptor& quadrature_descriptor,
+                        const MeshType& mesh,
+                        const ListTypeT& cell_list,
+                        OutputArrayT& value)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    static_assert(std::is_same_v<TinyVector<Dimension>, std::decay_t<InputType>>, "invalid input data type");
+    static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>,
+                  "invalid output data type");
+
+    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
+    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
+
+    if constexpr (std::is_arithmetic_v<OutputType>) {
+      value.fill(0);
+    } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) {
+      value.fill(zero);
+    } else {
+      static_assert(std::is_same_v<OutputType, double>, "unexpected output type");
+    }
+
+    const auto& connectivity = mesh.connectivity();
+
+    const auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+    const auto cell_type           = connectivity.cellType();
+    const auto xr                  = mesh.xr();
+
+    auto invalid_cell = std::make_pair(false, CellId{});
+
+    const auto qf = [&] {
+      if constexpr (Dimension == 1) {
+        return QuadratureManager::instance().getLineFormula(quadrature_descriptor);
+      } else if constexpr (Dimension == 2) {
+        return QuadratureManager::instance().getSquareFormula(quadrature_descriptor);
+      } else {
+        static_assert(Dimension == 3);
+        return QuadratureManager::instance().getCubeFormula(quadrature_descriptor);
+      }
+    }();
+
+    parallel_for(cell_list.size(), [=, &invalid_cell, &qf, &cell_list](typename ListTypeT::index_type index) {
+      const CellId cell_id = cell_list.cellId(index);
+
+      const auto cell_to_node = cell_to_node_matrix[cell_id];
+
+      if constexpr (Dimension == 1) {
+        switch (cell_type[cell_id]) {
+        case CellType::Line: {
+          const LineTransformation<1> T(xr[cell_to_node[0]], xr[cell_to_node[1]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * function(T(xi));
+          }
+          break;
+        }
+          // LCOV_EXCL_START
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+          // LCOV_EXCL_STOP
+        }
+      } else if constexpr (Dimension == 2) {
+        switch (cell_type[cell_id]) {
+        case CellType::Triangle: {
+          const SquareTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                          xr[cell_to_node[2]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi));
+          }
+          break;
+        }
+        case CellType::Quadrangle: {
+          const SquareTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                          xr[cell_to_node[3]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi));
+          }
+          break;
+        }
+          // LCOV_EXCL_START
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+          // LCOV_EXCL_STOP
+        }
+      } else {
+        static_assert(Dimension == 3);
+
+        switch (cell_type[cell_id]) {
+        case CellType::Tetrahedron: {
+          const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                     xr[cell_to_node[2]],   //
+                                     xr[cell_to_node[3]], xr[cell_to_node[3]], xr[cell_to_node[3]],
+                                     xr[cell_to_node[3]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi));
+          }
+          break;
+        }
+        case CellType::Pyramid: {
+          if (cell_to_node.size() == 5) {
+            const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],   //
+                                       xr[cell_to_node[3]],                                             //
+                                       xr[cell_to_node[4]], xr[cell_to_node[4]], xr[cell_to_node[4]],
+                                       xr[cell_to_node[4]]);
+
+            for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+              const auto xi = qf.point(i_point);
+              value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi));
+            }
+          } else {
+            // LCOV_EXCL_START
+            throw NotImplementedError("integration on pyramid with non-quadrangular base (number of nodes " +
+                                      std::to_string(cell_to_node.size()) + ")");
+            // LCOV_EXCL_STOP
+          }
+          break;
+        }
+        case CellType::Diamond: {
+          if (cell_to_node.size() == 5) {
+            {   // top tetrahedron
+              const CubeTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                          xr[cell_to_node[3]],   //
+                                          xr[cell_to_node[4]], xr[cell_to_node[4]], xr[cell_to_node[4]],
+                                          xr[cell_to_node[4]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * function(T0(xi));
+              }
+            }
+            {   // bottom tetrahedron
+              const CubeTransformation T1(xr[cell_to_node[3]], xr[cell_to_node[3]], xr[cell_to_node[2]],
+                                          xr[cell_to_node[1]],   //
+                                          xr[cell_to_node[0]], xr[cell_to_node[0]], xr[cell_to_node[0]],
+                                          xr[cell_to_node[0]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * function(T1(xi));
+              }
+            }
+          } else if (cell_to_node.size() == 6) {
+            {   // top pyramid
+              const CubeTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                          xr[cell_to_node[4]],   //
+                                          xr[cell_to_node[5]], xr[cell_to_node[5]], xr[cell_to_node[5]],
+                                          xr[cell_to_node[5]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * function(T0(xi));
+              }
+            }
+            {   // bottom pyramid
+              const CubeTransformation T1(xr[cell_to_node[4]], xr[cell_to_node[3]], xr[cell_to_node[2]],
+                                          xr[cell_to_node[1]],   //
+                                          xr[cell_to_node[0]], xr[cell_to_node[0]], xr[cell_to_node[0]],
+                                          xr[cell_to_node[0]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * function(T1(xi));
+              }
+            }
+          } else {
+            // LCOV_EXCL_START
+            throw NotImplementedError("integration on diamond with non-quadrangular base (" +
+                                      std::to_string(cell_to_node.size()) + ")");
+            // LCOV_EXCL_STOP
+          }
+          break;
+        }
+        case CellType::Prism: {
+          const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]],   //
+                                     xr[cell_to_node[2]], xr[cell_to_node[2]],   //
+                                     xr[cell_to_node[3]], xr[cell_to_node[4]],   //
+                                     xr[cell_to_node[5]], xr[cell_to_node[5]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi));
+          }
+          break;
+        }
+        case CellType::Hexahedron: {
+          const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                     xr[cell_to_node[4]], xr[cell_to_node[5]], xr[cell_to_node[6]],
+                                     xr[cell_to_node[7]]);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi));
+          }
+          break;
+        }
+          // LCOV_EXCL_START
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+          // LCOV_EXCL_STOP
+        }
+      }
+    });
+
+    // LCOV_EXCL_START
+    if (invalid_cell.first) {
+      std::ostringstream os;
+      os << "invalid cell type for integration: " << name(cell_type[invalid_cell.second]);
+      throw UnexpectedError(os.str());
+    }
+    // LCOV_EXCL_STOP
+  }
+
+  template <typename MeshType, typename OutputArrayT, typename ListTypeT, typename OutputType, typename InputType>
+  static PUGS_INLINE void
+  _directIntegrateTo(std::function<OutputType(InputType)> function,
+                     const IQuadratureDescriptor& quadrature_descriptor,
+                     const MeshType& mesh,
+                     const ListTypeT& cell_list,
+                     OutputArrayT& value)
+  {
+    Assert(not quadrature_descriptor.isTensorial());
+
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    static_assert(std::is_same_v<TinyVector<Dimension>, std::decay_t<InputType>>, "invalid input data type");
+    static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>,
+                  "invalid output data type");
+
+    if constexpr (std::is_arithmetic_v<OutputType>) {
+      value.fill(0);
+    } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) {
+      value.fill(zero);
+    } else {
+      static_assert(std::is_same_v<OutputType, double>, "unexpected output type");
+    }
+
+    const auto& connectivity = mesh.connectivity();
+
+    const auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+    const auto cell_type           = connectivity.cellType();
+    const auto xr                  = mesh.xr();
+
+    auto invalid_cell = std::make_pair(false, CellId{});
+
+    parallel_for(cell_list.size(), [=, &invalid_cell, &quadrature_descriptor,
+                                    &cell_list](const typename ListTypeT::index_type& index) {
+      const CellId cell_id = cell_list.cellId(index);
+
+      const auto cell_to_node = cell_to_node_matrix[cell_id];
+
+      if constexpr (Dimension == 1) {
+        switch (cell_type[cell_id]) {
+        case CellType::Line: {
+          const LineTransformation<1> T(xr[cell_to_node[0]], xr[cell_to_node[1]]);
+          const auto qf = QuadratureManager::instance().getLineFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * function(T(xi));
+          }
+          break;
+        }
+          // LCOV_EXCL_START
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+          // LCOV_EXCL_STOP
+        }
+      } else if constexpr (Dimension == 2) {
+        switch (cell_type[cell_id]) {
+        case CellType::Triangle: {
+          const TriangleTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]]);
+          const auto qf = QuadratureManager::instance().getTriangleFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * function(T(xi));
+          }
+          break;
+        }
+        case CellType::Quadrangle: {
+          const SquareTransformation<2> T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                          xr[cell_to_node[3]]);
+          const auto qf = QuadratureManager::instance().getSquareFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi));
+          }
+          break;
+        }
+          // LCOV_EXCL_START
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+          // LCOV_EXCL_STOP
+        }
+      } else {
+        static_assert(Dimension == 3);
+
+        switch (cell_type[cell_id]) {
+        case CellType::Tetrahedron: {
+          const TetrahedronTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]],   //
+                                            xr[cell_to_node[2]], xr[cell_to_node[3]]);
+          const auto qf = QuadratureManager::instance().getTetrahedronFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * function(T(xi));
+          }
+          break;
+        }
+        case CellType::Pyramid: {
+          if (cell_to_node.size() == 5) {
+            const PyramidTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],   //
+                                          xr[cell_to_node[3]], xr[cell_to_node[4]]);
+            const auto qf = QuadratureManager::instance().getPyramidFormula(quadrature_descriptor);
+
+            for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+              const auto xi = qf.point(i_point);
+              value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi));
+            }
+          } else {
+            // LCOV_EXCL_START
+            throw NotImplementedError("integration on pyramid with non-quadrangular base");
+            // LCOV_EXCL_STOP
+          }
+          break;
+        }
+        case CellType::Diamond: {
+          if (cell_to_node.size() == 5) {
+            const auto qf = QuadratureManager::instance().getTetrahedronFormula(quadrature_descriptor);
+            {   // top tetrahedron
+              const TetrahedronTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],   //
+                                                 xr[cell_to_node[4]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant() * function(T0(xi));
+              }
+            }
+            {   // bottom tetrahedron
+              const TetrahedronTransformation T1(xr[cell_to_node[3]], xr[cell_to_node[2]], xr[cell_to_node[1]],   //
+                                                 xr[cell_to_node[0]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant() * function(T1(xi));
+              }
+            }
+          } else if (cell_to_node.size() == 6) {
+            const auto qf = QuadratureManager::instance().getPyramidFormula(quadrature_descriptor);
+            {   // top pyramid
+              const PyramidTransformation T0(xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                             xr[cell_to_node[4]], xr[cell_to_node[5]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * function(T0(xi));
+              }
+            }
+            {   // bottom pyramid
+              const PyramidTransformation T1(xr[cell_to_node[4]], xr[cell_to_node[3]], xr[cell_to_node[2]],
+                                             xr[cell_to_node[1]], xr[cell_to_node[0]]);
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * function(T1(xi));
+              }
+            }
+          } else {
+            // LCOV_EXCL_START
+            throw NotImplementedError("integration on diamond with non-quadrangular base (" +
+                                      std::to_string(cell_to_node.size()) + ")");
+            // LCOV_EXCL_STOP
+          }
+          break;
+        }
+        case CellType::Prism: {
+          const PrismTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                      xr[cell_to_node[3]], xr[cell_to_node[4]], xr[cell_to_node[5]]);
+          const auto qf = QuadratureManager::instance().getPrismFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi));
+          }
+          break;
+        }
+        case CellType::Hexahedron: {
+          const CubeTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                     xr[cell_to_node[4]], xr[cell_to_node[5]], xr[cell_to_node[6]],
+                                     xr[cell_to_node[7]]);
+          const auto qf = QuadratureManager::instance().getCubeFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * function(T(xi));
+          }
+          break;
+        }
+          // LCOV_EXCL_START
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+          // LCOV_EXCL_STOP
+        }
+      }
+    });
+
+    // LCOV_EXCL_START
+    if (invalid_cell.first) {
+      std::ostringstream os;
+      os << "invalid cell type for integration: " << name(cell_type[invalid_cell.second]);
+      throw UnexpectedError(os.str());
+    }
+    // LCOV_EXCL_STOP
+  }
+
+ public:
+  template <typename MeshType, typename OutputArrayT, typename OutputType, typename InputType>
+  static PUGS_INLINE void
+  integrateTo(const std::function<OutputType(InputType)>& function,
+              const IQuadratureDescriptor& quadrature_descriptor,
+              const MeshType& mesh,
+              OutputArrayT& value)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    if (quadrature_descriptor.isTensorial()) {
+      _tensorialIntegrateTo<MeshType, OutputArrayT>(function, quadrature_descriptor, mesh,
+                                                    AllCells<Dimension>{mesh.connectivity()}, value);
+    } else {
+      _directIntegrateTo<MeshType, OutputArrayT>(function, quadrature_descriptor, mesh,
+                                                 AllCells<Dimension>{mesh.connectivity()}, value);
+    }
+  }
+
+  template <typename MeshType, typename OutputArrayT, typename FunctionType>
+  static PUGS_INLINE void
+  integrateTo(const FunctionType& function,
+              const IQuadratureDescriptor& quadrature_descriptor,
+              const MeshType& mesh,
+              OutputArrayT& value)
+  {
+    integrateTo(std::function(function), quadrature_descriptor, mesh, value);
+  }
+
+  template <typename MeshType, typename OutputType, typename InputType, template <typename DataType> typename ArrayT>
+  static PUGS_INLINE ArrayT<OutputType>
+  integrate(const std::function<OutputType(InputType)>& function,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const ArrayT<CellId>& cell_list)
+  {
+    ArrayT<OutputType> value(size(cell_list));
+    if (quadrature_descriptor.isTensorial()) {
+      _tensorialIntegrateTo<MeshType, ArrayT<OutputType>>(function, quadrature_descriptor, mesh, CellList{cell_list},
+                                                          value);
+    } else {
+      _directIntegrateTo<MeshType, ArrayT<OutputType>>(function, quadrature_descriptor, mesh, CellList{cell_list},
+                                                       value);
+    }
+
+    return value;
+  }
+
+  template <typename MeshType, typename FunctionType, template <typename DataType> typename ArrayT>
+  static PUGS_INLINE auto
+  integrate(const FunctionType& function,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const ArrayT<CellId>& cell_list)
+  {
+    return integrate(std::function(function), quadrature_descriptor, mesh, cell_list);
+  }
+};
+
+#endif   // CELL_INTEGRATOR_HPP
diff --git a/src/scheme/DiscreteFunctionIntegrator.cpp b/src/scheme/DiscreteFunctionIntegrator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..91cf921df2764ad13f4470687fc4cc5f4d6bced1
--- /dev/null
+++ b/src/scheme/DiscreteFunctionIntegrator.cpp
@@ -0,0 +1,132 @@
+#include <scheme/DiscreteFunctionIntegrator.hpp>
+
+#include <language/utils/IntegrateCellValue.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <utils/Exceptions.hpp>
+
+template <size_t Dimension, typename DataType, typename ValueType>
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionIntegrator::_integrate() const
+{
+  using MeshType       = Mesh<Connectivity<Dimension>>;
+  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(m_mesh);
+
+  if constexpr (std::is_same_v<DataType, ValueType>) {
+    return std::make_shared<
+      DiscreteFunctionP0<Dimension, ValueType>>(mesh,
+                                                IntegrateCellValue<DataType(TinyVector<Dimension>)>::template integrate<
+                                                  MeshType>(m_function_id, *m_quadrature_descriptor, *mesh));
+  } else {
+    static_assert(std::is_convertible_v<DataType, ValueType>);
+
+    CellValue<DataType> cell_data =
+      IntegrateCellValue<DataType(TinyVector<Dimension>)>::template integrate<MeshType>(m_function_id,
+                                                                                        *m_quadrature_descriptor,
+                                                                                        *mesh);
+
+    CellValue<ValueType> cell_value{mesh->connectivity()};
+
+    parallel_for(
+      mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_data[cell_id]; });
+
+    return std::make_shared<DiscreteFunctionP0<Dimension, ValueType>>(mesh, cell_value);
+  }
+}
+
+template <size_t Dimension>
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionIntegrator::_integrate() const
+{
+  const auto& function_descriptor = m_function_id.descriptor();
+  Assert(function_descriptor.domainMappingNode().children[1]->m_data_type == ASTNodeDataType::typename_t);
+
+  const ASTNodeDataType& data_type = function_descriptor.domainMappingNode().children[1]->m_data_type.contentType();
+
+  switch (data_type) {
+  case ASTNodeDataType::bool_t: {
+    return this->_integrate<Dimension, bool, double>();
+  }
+  case ASTNodeDataType::unsigned_int_t: {
+    return this->_integrate<Dimension, uint64_t, double>();
+  }
+  case ASTNodeDataType::int_t: {
+    return this->_integrate<Dimension, int64_t, double>();
+  }
+  case ASTNodeDataType::double_t: {
+    return this->_integrate<Dimension, double>();
+  }
+  case ASTNodeDataType::vector_t: {
+    switch (data_type.dimension()) {
+    case 1: {
+      return this->_integrate<Dimension, TinyVector<1>>();
+    }
+    case 2: {
+      return this->_integrate<Dimension, TinyVector<2>>();
+    }
+    case 3: {
+      return this->_integrate<Dimension, TinyVector<3>>();
+    }
+      // LCOV_EXCL_START
+    default: {
+      std::ostringstream os;
+      os << "invalid vector dimension " << rang::fgB::red << data_type.dimension() << rang::style::reset;
+
+      throw UnexpectedError(os.str());
+    }
+      // LCOV_EXCL_STOP
+    }
+  }
+  case ASTNodeDataType::matrix_t: {
+    Assert(data_type.numberOfColumns() == data_type.numberOfRows(), "undefined matrix type");
+    switch (data_type.numberOfColumns()) {
+    case 1: {
+      return this->_integrate<Dimension, TinyMatrix<1>>();
+    }
+    case 2: {
+      return this->_integrate<Dimension, TinyMatrix<2>>();
+    }
+    case 3: {
+      return this->_integrate<Dimension, TinyMatrix<3>>();
+    }
+      // LCOV_EXCL_START
+    default: {
+      std::ostringstream os;
+      os << "invalid vector dimension " << rang::fgB::red << data_type.dimension() << rang::style::reset;
+
+      throw UnexpectedError(os.str());
+    }
+      // LCOV_EXCL_STOP
+    }
+  }
+    // LCOV_EXCL_START
+  default: {
+    std::ostringstream os;
+    os << "invalid integrate value type: " << rang::fgB::red << dataTypeName(data_type) << rang::style::reset;
+
+    throw UnexpectedError(os.str());
+  }
+    // LCOV_EXCL_STOP
+  }
+}
+
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionIntegrator::integrate() const
+{
+  std::shared_ptr<IDiscreteFunction> discrete_function;
+  switch (m_mesh->dimension()) {
+  case 1: {
+    return this->_integrate<1>();
+  }
+  case 2: {
+    return this->_integrate<2>();
+  }
+  case 3: {
+    return this->_integrate<3>();
+  }
+    // LCOV_EXCL_START
+  default: {
+    throw UnexpectedError("invalid dimension");
+  }
+    // LCOV_EXCL_STOP
+  }
+}
diff --git a/src/scheme/DiscreteFunctionIntegrator.hpp b/src/scheme/DiscreteFunctionIntegrator.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1784f891eaa6fd7471b17006b3c9f277c31e51dd
--- /dev/null
+++ b/src/scheme/DiscreteFunctionIntegrator.hpp
@@ -0,0 +1,39 @@
+#ifndef DISCRETE_FUNCTION_INTEGRATOR_HPP
+#define DISCRETE_FUNCTION_INTEGRATOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IMesh.hpp>
+#include <scheme/IDiscreteFunction.hpp>
+
+#include <memory>
+
+class DiscreteFunctionIntegrator
+{
+ private:
+  std::shared_ptr<const IMesh> m_mesh;
+  std::shared_ptr<const IQuadratureDescriptor> m_quadrature_descriptor;
+  const FunctionSymbolId m_function_id;
+
+  template <size_t Dimension, typename DataType, typename ValueType = DataType>
+  std::shared_ptr<IDiscreteFunction> _integrate() const;
+
+  template <size_t Dimension>
+  std::shared_ptr<IDiscreteFunction> _integrate() const;
+
+ public:
+  std::shared_ptr<IDiscreteFunction> integrate() const;
+
+  DiscreteFunctionIntegrator(const std::shared_ptr<const IMesh>& mesh,
+                             const std::shared_ptr<const IQuadratureDescriptor>& quadrature_descriptor,
+                             const FunctionSymbolId& function_id)
+    : m_mesh{mesh}, m_quadrature_descriptor{quadrature_descriptor}, m_function_id{function_id}
+  {}
+
+  DiscreteFunctionIntegrator(const DiscreteFunctionIntegrator&) = delete;
+  DiscreteFunctionIntegrator(DiscreteFunctionIntegrator&&)      = delete;
+
+  ~DiscreteFunctionIntegrator() = default;
+};
+
+#endif   // DISCRETE_FUNCTION_INTEGRATOR_HPP
diff --git a/src/scheme/DiscreteFunctionVectorIntegrator.cpp b/src/scheme/DiscreteFunctionVectorIntegrator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6d8937f84aeba6ae96ac5fc6e5ba523e5781f239
--- /dev/null
+++ b/src/scheme/DiscreteFunctionVectorIntegrator.cpp
@@ -0,0 +1,70 @@
+#include <scheme/DiscreteFunctionVectorIntegrator.hpp>
+
+#include <language/utils/IntegrateCellArray.hpp>
+
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <utils/Exceptions.hpp>
+
+template <size_t Dimension, typename DataType>
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVectorIntegrator::_integrate() const
+{
+  std::shared_ptr mesh = std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(m_mesh);
+
+  return std::make_shared<
+    DiscreteFunctionP0Vector<Dimension, DataType>>(mesh, IntegrateCellArray<DataType(TinyVector<Dimension>)>::
+                                                           template integrate(m_function_id_list,
+                                                                              *m_quadrature_descriptor, *mesh));
+}
+
+template <size_t Dimension>
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVectorIntegrator::_integrate() const
+{
+  for (const auto& function_id : m_function_id_list) {
+    const auto& function_descriptor = function_id.descriptor();
+    Assert(function_descriptor.domainMappingNode().children[1]->m_data_type == ASTNodeDataType::typename_t);
+    const ASTNodeDataType& data_type = function_descriptor.domainMappingNode().children[1]->m_data_type.contentType();
+
+    switch (data_type) {
+    case ASTNodeDataType::bool_t:
+    case ASTNodeDataType::unsigned_int_t:
+    case ASTNodeDataType::int_t:
+    case ASTNodeDataType::double_t: {
+      break;
+    }
+    default: {
+      std::ostringstream os;
+      os << "vector functions require scalar value type.\n"
+         << "Invalid interpolation value type: " << rang::fgB::red << dataTypeName(data_type) << rang::style::reset;
+      throw NormalError(os.str());
+    }
+    }
+  }
+  return this->_integrate<Dimension, double>();
+}
+
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVectorIntegrator::integrate() const
+{
+  if (m_discrete_function_descriptor->type() != DiscreteFunctionType::P0Vector) {
+    throw NormalError("invalid discrete function type for vector interpolation");
+  }
+
+  switch (m_mesh->dimension()) {
+  case 1: {
+    return this->_integrate<1>();
+  }
+  case 2: {
+    return this->_integrate<2>();
+  }
+  case 3: {
+    return this->_integrate<3>();
+  }
+    // LCOV_EXCL_START
+  default: {
+    throw UnexpectedError("invalid dimension");
+  }
+    // LCOV_EXCL_STOP
+  }
+}
diff --git a/src/scheme/DiscreteFunctionVectorIntegrator.hpp b/src/scheme/DiscreteFunctionVectorIntegrator.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..01e086e7efd9c2334d07dfdb3cf8dce1f33e49f1
--- /dev/null
+++ b/src/scheme/DiscreteFunctionVectorIntegrator.hpp
@@ -0,0 +1,47 @@
+#ifndef DISCRETE_FUNCTION_VECTOR_INTEGRATOR_HPP
+#define DISCRETE_FUNCTION_VECTOR_INTEGRATOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IMesh.hpp>
+#include <scheme/IDiscreteFunction.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+
+#include <memory>
+#include <vector>
+
+class DiscreteFunctionVectorIntegrator
+{
+ private:
+  std::shared_ptr<const IMesh> m_mesh;
+  std::shared_ptr<const IQuadratureDescriptor> m_quadrature_descriptor;
+  std::shared_ptr<const IDiscreteFunctionDescriptor> m_discrete_function_descriptor;
+  const std::vector<FunctionSymbolId> m_function_id_list;
+
+  template <size_t Dimension, typename DataType>
+  std::shared_ptr<IDiscreteFunction> _integrate() const;
+
+  template <size_t Dimension>
+  std::shared_ptr<IDiscreteFunction> _integrate() const;
+
+ public:
+  std::shared_ptr<IDiscreteFunction> integrate() const;
+
+  DiscreteFunctionVectorIntegrator(
+    const std::shared_ptr<const IMesh>& mesh,
+    const std::shared_ptr<const IQuadratureDescriptor>& quadrature_descriptor,
+    const std::shared_ptr<const IDiscreteFunctionDescriptor>& discrete_function_descriptor,
+    const std::vector<FunctionSymbolId>& function_id_list)
+    : m_mesh{mesh},
+      m_quadrature_descriptor(quadrature_descriptor),
+      m_discrete_function_descriptor{discrete_function_descriptor},
+      m_function_id_list{function_id_list}
+  {}
+
+  DiscreteFunctionVectorIntegrator(const DiscreteFunctionVectorIntegrator&) = delete;
+  DiscreteFunctionVectorIntegrator(DiscreteFunctionVectorIntegrator&&)      = delete;
+
+  ~DiscreteFunctionVectorIntegrator() = default;
+};
+
+#endif   // DISCRETE_FUNCTION_VECTOR_INTEGRATOR_HPP
diff --git a/src/scheme/EdgeIntegrator.hpp b/src/scheme/EdgeIntegrator.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2bf287c3d6f61cb83ef3619124334a2124898f8c
--- /dev/null
+++ b/src/scheme/EdgeIntegrator.hpp
@@ -0,0 +1,166 @@
+#ifndef EDGE_INTEGRATOR_HPP
+#define EDGE_INTEGRATOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/LineTransformation.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemId.hpp>
+#include <utils/Array.hpp>
+
+#include <functional>
+
+class EdgeIntegrator
+{
+ private:
+  template <size_t Dimension>
+  class AllEdges
+  {
+   private:
+    const Connectivity<Dimension>& m_connectivity;
+
+   public:
+    using index_type = EdgeId;
+
+    PUGS_INLINE
+    EdgeId
+    edgeId(const EdgeId edge_id) const
+    {
+      return edge_id;
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const
+    {
+      return m_connectivity.numberOfEdges();
+    }
+
+    PUGS_INLINE
+    AllEdges(const Connectivity<Dimension>& connectivity) : m_connectivity{connectivity} {}
+  };
+
+  template <template <typename T> typename ArrayT>
+  class EdgeList
+  {
+   private:
+    const ArrayT<EdgeId>& m_edge_list;
+
+   public:
+    using index_type = typename ArrayT<EdgeId>::index_type;
+
+    PUGS_INLINE
+    EdgeId
+    edgeId(const index_type index) const
+    {
+      return m_edge_list[index];
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const
+    {
+      return m_edge_list.size();
+    }
+
+    PUGS_INLINE
+    EdgeList(const ArrayT<EdgeId>& edge_list) : m_edge_list{edge_list} {}
+  };
+
+  template <typename MeshType, typename OutputArrayT, typename ListTypeT, typename OutputType, typename InputType>
+  static PUGS_INLINE void
+  _integrateTo(std::function<OutputType(InputType)> function,
+               const IQuadratureDescriptor& quadrature_descriptor,
+               const MeshType& mesh,
+               const ListTypeT& edge_list,
+               OutputArrayT& value)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+    static_assert(Dimension == 3);
+
+    static_assert(std::is_same_v<TinyVector<Dimension>, std::decay_t<InputType>>, "invalid input data type");
+    static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>,
+                  "invalid output data type");
+
+    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
+    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
+
+    if constexpr (std::is_arithmetic_v<OutputType>) {
+      value.fill(0);
+    } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) {
+      value.fill(zero);
+    } else {
+      static_assert(std::is_same_v<OutputType, double>, "unexpected output type");
+    }
+
+    const auto& connectivity = mesh.connectivity();
+
+    const auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+    const auto xr                  = mesh.xr();
+
+    const auto qf = QuadratureManager::instance().getLineFormula(quadrature_descriptor);
+
+    parallel_for(edge_list.size(), [=, &qf, &edge_list](typename ListTypeT::index_type index) {
+      const EdgeId edge_id = edge_list.edgeId(index);
+
+      const auto edge_to_node = edge_to_node_matrix[edge_id];
+
+      Assert(edge_to_node.size() == 2);
+      const LineTransformation<3> T(xr[edge_to_node[0]], xr[edge_to_node[1]]);
+
+      for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+        const auto xi = qf.point(i_point);
+        value[index] += qf.weight(i_point) * T.velocityNorm() * function(T(xi));
+      }
+    });
+  }
+
+ public:
+  template <typename MeshType, typename OutputArrayT, typename OutputType, typename InputType>
+  static PUGS_INLINE void
+  integrateTo(const std::function<OutputType(InputType)>& function,
+              const IQuadratureDescriptor& quadrature_descriptor,
+              const MeshType& mesh,
+              OutputArrayT& value)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    _integrateTo<MeshType, OutputArrayT>(function, quadrature_descriptor, mesh,
+                                         AllEdges<Dimension>{mesh.connectivity()}, value);
+  }
+
+  template <typename MeshType, typename OutputArrayT, typename FunctionType>
+  static PUGS_INLINE void
+  integrateTo(const FunctionType& function,
+              const IQuadratureDescriptor& quadrature_descriptor,
+              const MeshType& mesh,
+              OutputArrayT& value)
+  {
+    integrateTo(std::function(function), quadrature_descriptor, mesh, value);
+  }
+
+  template <typename MeshType, typename OutputType, typename InputType, template <typename DataType> typename ArrayT>
+  static PUGS_INLINE ArrayT<OutputType>
+  integrate(const std::function<OutputType(InputType)>& function,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const ArrayT<EdgeId>& edge_list)
+  {
+    ArrayT<OutputType> value(size(edge_list));
+    _integrateTo<MeshType, ArrayT<OutputType>>(function, quadrature_descriptor, mesh, EdgeList{edge_list}, value);
+
+    return value;
+  }
+
+  template <typename MeshType, typename FunctionType, template <typename DataType> typename ArrayT>
+  static PUGS_INLINE auto
+  integrate(const FunctionType& function,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const ArrayT<EdgeId>& edge_list)
+  {
+    return integrate(std::function(function), quadrature_descriptor, mesh, edge_list);
+  }
+};
+
+#endif   // EDGE_INTEGRATOR_HPP
diff --git a/src/scheme/FaceIntegrator.hpp b/src/scheme/FaceIntegrator.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..30ee39077ea12d898b4bdee00f12970be79e8753
--- /dev/null
+++ b/src/scheme/FaceIntegrator.hpp
@@ -0,0 +1,322 @@
+#ifndef FACE_INTEGRATOR_HPP
+#define FACE_INTEGRATOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/LineTransformation.hpp>
+#include <geometry/SquareTransformation.hpp>
+#include <geometry/TriangleTransformation.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemId.hpp>
+#include <utils/Array.hpp>
+
+#include <functional>
+
+class FaceIntegrator
+{
+ private:
+  template <size_t Dimension>
+  class AllFaces
+  {
+   private:
+    const Connectivity<Dimension>& m_connectivity;
+
+   public:
+    using index_type = FaceId;
+
+    PUGS_INLINE
+    FaceId
+    faceId(const FaceId face_id) const
+    {
+      return face_id;
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const
+    {
+      return m_connectivity.numberOfFaces();
+    }
+
+    PUGS_INLINE
+    AllFaces(const Connectivity<Dimension>& connectivity) : m_connectivity{connectivity} {}
+  };
+
+  template <template <typename T> typename ArrayT>
+  class FaceList
+  {
+   private:
+    const ArrayT<FaceId>& m_face_list;
+
+   public:
+    using index_type = typename ArrayT<FaceId>::index_type;
+
+    PUGS_INLINE
+    FaceId
+    faceId(const index_type index) const
+    {
+      return m_face_list[index];
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const
+    {
+      return m_face_list.size();
+    }
+
+    PUGS_INLINE
+    FaceList(const ArrayT<FaceId>& face_list) : m_face_list{face_list} {}
+  };
+
+  template <typename MeshType, typename OutputArrayT, typename ListTypeT, typename OutputType, typename InputType>
+  static PUGS_INLINE void
+  _tensorialIntegrateTo(std::function<OutputType(InputType)> function,
+                        const IQuadratureDescriptor& quadrature_descriptor,
+                        const MeshType& mesh,
+                        const ListTypeT& face_list,
+                        OutputArrayT& value)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+    static_assert(Dimension > 1);
+
+    static_assert(std::is_same_v<TinyVector<Dimension>, std::decay_t<InputType>>, "invalid input data type");
+    static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>,
+                  "invalid output data type");
+
+    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
+    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
+
+    if constexpr (std::is_arithmetic_v<OutputType>) {
+      value.fill(0);
+    } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) {
+      value.fill(zero);
+    } else {
+      static_assert(std::is_same_v<OutputType, double>, "unexpected output type");
+    }
+
+    const auto& connectivity = mesh.connectivity();
+
+    const auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+    const auto xr                  = mesh.xr();
+
+    if constexpr (Dimension == 2) {
+      const auto qf = QuadratureManager::instance().getLineFormula(quadrature_descriptor);
+
+      parallel_for(face_list.size(), [=, &qf, &face_list](typename ListTypeT::index_type index) {
+        const FaceId face_id = face_list.faceId(index);
+
+        const auto face_to_node = face_to_node_matrix[face_id];
+
+        Assert(face_to_node.size() == 2);
+        const LineTransformation<2> T(xr[face_to_node[0]], xr[face_to_node[1]]);
+
+        for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+          const auto xi = qf.point(i_point);
+          value[index] += qf.weight(i_point) * T.velocityNorm() * function(T(xi));
+        }
+      });
+
+    } else if constexpr (Dimension == 3) {
+      const auto qf = QuadratureManager::instance().getSquareFormula(quadrature_descriptor);
+
+      auto invalid_face = std::make_pair(false, FaceId{});
+
+      parallel_for(face_list.size(), [=, &invalid_face, &qf, &face_list](typename ListTypeT::index_type index) {
+        const FaceId face_id = face_list.faceId(index);
+
+        const auto face_to_node = face_to_node_matrix[face_id];
+
+        switch (face_to_node.size()) {
+        case 3: {
+          SquareTransformation<3> T(xr[face_to_node[0]], xr[face_to_node[1]], xr[face_to_node[2]], xr[face_to_node[2]]);
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.areaVariationNorm(xi) * function(T(xi));
+          }
+          break;
+        }
+        case 4: {
+          SquareTransformation<3> T(xr[face_to_node[0]], xr[face_to_node[1]], xr[face_to_node[2]], xr[face_to_node[3]]);
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            value[index] += qf.weight(i_point) * T.areaVariationNorm(xi) * function(T(xi));
+          }
+          break;
+        }
+        default: {
+          invalid_face = std::make_pair(true, face_id);
+        }
+        }
+      });
+
+      // LCOV_EXCL_START
+      if (invalid_face.first) {
+        std::ostringstream os;
+        os << "invalid face type for integration: " << face_to_node_matrix[invalid_face.second].size() << " points";
+        throw UnexpectedError(os.str());
+      }
+      // LCOV_EXCL_STOP
+
+    } else {
+      throw UnexpectedError("invalid dimension for face integration");
+    }
+  }
+
+  template <typename MeshType, typename OutputArrayT, typename ListTypeT, typename OutputType, typename InputType>
+  static PUGS_INLINE void
+  _directIntegrateTo(std::function<OutputType(InputType)> function,
+                     const IQuadratureDescriptor& quadrature_descriptor,
+                     const MeshType& mesh,
+                     const ListTypeT& face_list,
+                     OutputArrayT& value)
+  {
+    Assert(not quadrature_descriptor.isTensorial());
+
+    constexpr size_t Dimension = MeshType::Dimension;
+    static_assert(Dimension > 1);
+
+    static_assert(std::is_same_v<TinyVector<Dimension>, std::decay_t<InputType>>, "invalid input data type");
+    static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>,
+                  "invalid output data type");
+
+    if constexpr (std::is_arithmetic_v<OutputType>) {
+      value.fill(0);
+    } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) {
+      value.fill(zero);
+    } else {
+      static_assert(std::is_same_v<OutputType, double>, "unexpected output type");
+    }
+
+    const auto& connectivity = mesh.connectivity();
+
+    const auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+    const auto xr                  = mesh.xr();
+
+    if constexpr (Dimension == 2) {
+      parallel_for(face_list.size(),
+                   [=, &quadrature_descriptor, &face_list](const typename ListTypeT::index_type& index) {
+                     const FaceId face_id = face_list.faceId(index);
+
+                     const auto face_to_node = face_to_node_matrix[face_id];
+
+                     Assert(face_to_node.size() == 2);
+                     const LineTransformation<2> T(xr[face_to_node[0]], xr[face_to_node[1]]);
+                     const auto qf = QuadratureManager::instance().getLineFormula(quadrature_descriptor);
+
+                     for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                       const auto xi = qf.point(i_point);
+                       value[index] += qf.weight(i_point) * T.velocityNorm() * function(T(xi));
+                     }
+                   });
+
+    } else if constexpr (Dimension == 3) {
+      auto invalid_face = std::make_pair(false, FaceId{});
+
+      parallel_for(face_list.size(),
+                   [=, &invalid_face, &quadrature_descriptor, &face_list](const typename ListTypeT::index_type& index) {
+                     const FaceId face_id = face_list.faceId(index);
+
+                     const auto face_to_node = face_to_node_matrix[face_id];
+
+                     switch (face_to_node.size()) {
+                     case 3: {
+                       const TriangleTransformation<3> T(xr[face_to_node[0]], xr[face_to_node[1]], xr[face_to_node[2]]);
+                       const auto qf = QuadratureManager::instance().getTriangleFormula(quadrature_descriptor);
+
+                       for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                         const auto xi = qf.point(i_point);
+                         value[index] += qf.weight(i_point) * T.areaVariationNorm() * function(T(xi));
+                       }
+                       break;
+                     }
+                     case 4: {
+                       const SquareTransformation<3> T(xr[face_to_node[0]], xr[face_to_node[1]], xr[face_to_node[2]],
+                                                       xr[face_to_node[3]]);
+                       const auto qf = QuadratureManager::instance().getSquareFormula(quadrature_descriptor);
+
+                       for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                         const auto xi = qf.point(i_point);
+                         value[index] += qf.weight(i_point) * T.areaVariationNorm(xi) * function(T(xi));
+                       }
+                       break;
+                     }
+                       // LCOV_EXCL_START
+                     default: {
+                       invalid_face = std::make_pair(true, face_id);
+                       break;
+                     }
+                       // LCOV_EXCL_STOP
+                     }
+                   });
+
+      // LCOV_EXCL_START
+      if (invalid_face.first) {
+        std::ostringstream os;
+        os << "invalid face type for integration: " << face_to_node_matrix[invalid_face.second].size() << " points";
+        throw UnexpectedError(os.str());
+      }
+      // LCOV_EXCL_STOP
+    }
+  }
+
+ public:
+  template <typename MeshType, typename OutputArrayT, typename OutputType, typename InputType>
+  static PUGS_INLINE void
+  integrateTo(const std::function<OutputType(InputType)>& function,
+              const IQuadratureDescriptor& quadrature_descriptor,
+              const MeshType& mesh,
+              OutputArrayT& value)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    if (quadrature_descriptor.isTensorial()) {
+      _tensorialIntegrateTo<MeshType, OutputArrayT>(function, quadrature_descriptor, mesh,
+                                                    AllFaces<Dimension>{mesh.connectivity()}, value);
+    } else {
+      _directIntegrateTo<MeshType, OutputArrayT>(function, quadrature_descriptor, mesh,
+                                                 AllFaces<Dimension>{mesh.connectivity()}, value);
+    }
+  }
+
+  template <typename MeshType, typename OutputArrayT, typename FunctionType>
+  static PUGS_INLINE void
+  integrateTo(const FunctionType& function,
+              const IQuadratureDescriptor& quadrature_descriptor,
+              const MeshType& mesh,
+              OutputArrayT& value)
+  {
+    integrateTo(std::function(function), quadrature_descriptor, mesh, value);
+  }
+
+  template <typename MeshType, typename OutputType, typename InputType, template <typename DataType> typename ArrayT>
+  static PUGS_INLINE ArrayT<OutputType>
+  integrate(const std::function<OutputType(InputType)>& function,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const ArrayT<FaceId>& face_list)
+  {
+    ArrayT<OutputType> value(size(face_list));
+    if (quadrature_descriptor.isTensorial()) {
+      _tensorialIntegrateTo<MeshType, ArrayT<OutputType>>(function, quadrature_descriptor, mesh, FaceList{face_list},
+                                                          value);
+    } else {
+      _directIntegrateTo<MeshType, ArrayT<OutputType>>(function, quadrature_descriptor, mesh, FaceList{face_list},
+                                                       value);
+    }
+
+    return value;
+  }
+
+  template <typename MeshType, typename FunctionType, template <typename DataType> typename ArrayT>
+  static PUGS_INLINE auto
+  integrate(const FunctionType& function,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const ArrayT<FaceId>& face_list)
+  {
+    return integrate(std::function(function), quadrature_descriptor, mesh, face_list);
+  }
+};
+
+#endif   // FACE_INTEGRATOR_HPP
diff --git a/src/utils/Array.hpp b/src/utils/Array.hpp
index 54786db88592d7371e7b7fa5e9acd0aaec4fffe5..f78f543080edcfa614e6ad549a73349ffb66da74 100644
--- a/src/utils/Array.hpp
+++ b/src/utils/Array.hpp
@@ -6,6 +6,7 @@
 #include <utils/PugsAssert.hpp>
 #include <utils/PugsMacros.hpp>
 #include <utils/PugsUtils.hpp>
+#include <utils/Types.hpp>
 
 #include <Kokkos_CopyViews.hpp>
 #include <algorithm>
@@ -140,8 +141,15 @@ class [[nodiscard]] Array
   ~Array() = default;
 };
 
+template <typename DataType>
+[[nodiscard]] PUGS_INLINE size_t
+size(const Array<DataType>& array)
+{
+  return array.size();
+}
+
 template <typename DataType, typename... RT>
-PUGS_INLINE Array<DataType>
+[[nodiscard]] PUGS_INLINE Array<DataType>
 encapsulate(const Kokkos::View<DataType*, RT...>& values)
 {
   Array<DataType> array;
@@ -150,7 +158,7 @@ encapsulate(const Kokkos::View<DataType*, RT...>& values)
 }
 
 template <typename DataType>
-PUGS_INLINE Array<DataType>
+[[nodiscard]] PUGS_INLINE Array<DataType>
 subArray(const Array<DataType>& array,
          typename Array<DataType>::index_type begin,
          typename Array<DataType>::index_type size)
@@ -162,7 +170,7 @@ subArray(const Array<DataType>& array,
 
 // map, multimap, unordered_map and stack cannot be copied this way
 template <typename Container>
-PUGS_INLINE Array<std::remove_const_t<typename Container::value_type>>
+[[nodiscard]] PUGS_INLINE Array<std::remove_const_t<typename Container::value_type>>
 convert_to_array(const Container& given_container)
 {
   using DataType = typename Container::value_type;
@@ -173,4 +181,185 @@ convert_to_array(const Container& given_container)
   return array;
 }
 
+template <typename DataType>
+[[nodiscard]] std::remove_const_t<DataType>
+min(const Array<DataType>& array)
+{
+  using ArrayType  = Array<DataType>;
+  using data_type  = std::remove_const_t<typename ArrayType::data_type>;
+  using index_type = typename ArrayType::index_type;
+
+  class ArrayMin
+  {
+   private:
+    const ArrayType m_array;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_array.size(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& i, data_type& data) const
+    {
+      if (m_array[i] < data) {
+        data = m_array[i];
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      if (src < dst) {
+        dst = src;
+      }
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::max();
+    }
+
+    PUGS_INLINE
+    ArrayMin(const ArrayType& array) : m_array(array)
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~ArrayMin() = default;
+  };
+
+  return ArrayMin(array);
+}
+
+template <typename DataType>
+[[nodiscard]] std::remove_const_t<DataType>
+max(const Array<DataType>& array)
+{
+  using ArrayType  = Array<DataType>;
+  using data_type  = std::remove_const_t<typename ArrayType::data_type>;
+  using index_type = typename ArrayType::index_type;
+
+  class ArrayMax
+  {
+   private:
+    const ArrayType m_array;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_array.size(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& i, data_type& data) const
+    {
+      if (m_array[i] > data) {
+        data = m_array[i];
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      if (src > dst) {
+        dst = src;
+      }
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::min();
+    }
+
+    PUGS_INLINE
+    ArrayMax(const ArrayType& array) : m_array(array)
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~ArrayMax() = default;
+  };
+
+  return ArrayMax(array);
+}
+
+template <typename DataType>
+[[nodiscard]] std::remove_const_t<DataType>
+sum(const Array<DataType>& array)
+{
+  using ArrayType  = Array<DataType>;
+  using data_type  = std::remove_const_t<DataType>;
+  using index_type = typename ArrayType::index_type;
+
+  class ArraySum
+  {
+   private:
+    const ArrayType m_array;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_array.size(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& i, data_type& data) const
+    {
+      data += m_array[i];
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      dst += src;
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      if constexpr (std::is_arithmetic_v<data_type>) {
+        value = 0;
+      } else {
+        static_assert(is_tiny_vector_v<data_type> or is_tiny_matrix_v<data_type>, "invalid data type");
+        value = zero;
+      }
+    }
+
+    PUGS_INLINE
+    ArraySum(const ArrayType& array) : m_array(array)
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~ArraySum() = default;
+  };
+
+  return ArraySum(array);
+}
+
 #endif   // ARRAY_HPP
diff --git a/src/utils/ArrayUtils.hpp b/src/utils/ArrayUtils.hpp
deleted file mode 100644
index 3b3438294291af1deafe733374e7de9ddf4982b6..0000000000000000000000000000000000000000
--- a/src/utils/ArrayUtils.hpp
+++ /dev/null
@@ -1,211 +0,0 @@
-#ifndef ARRAY_UTILS_HPP
-#define ARRAY_UTILS_HPP
-
-#include <utils/PugsMacros.hpp>
-#include <utils/PugsTraits.hpp>
-#include <utils/PugsUtils.hpp>
-
-#include <utils/Types.hpp>
-
-template <typename DataType, template <typename> typename ArrayT>
-std::remove_const_t<DataType>
-min(const ArrayT<DataType>& array)
-{
-  using ArrayType  = ArrayT<DataType>;
-  using data_type  = std::remove_const_t<typename ArrayType::data_type>;
-  using index_type = typename ArrayType::index_type;
-
-  class ArrayMin
-  {
-   private:
-    const ArrayType m_array;
-
-   public:
-    PUGS_INLINE
-    operator data_type()
-    {
-      data_type reduced_value;
-      parallel_reduce(m_array.size(), *this, reduced_value);
-      return reduced_value;
-    }
-
-    PUGS_INLINE
-    void
-    operator()(const index_type& i, data_type& data) const
-    {
-      if (m_array[i] < data) {
-        data = m_array[i];
-      }
-    }
-
-    PUGS_INLINE
-    void
-    join(volatile data_type& dst, const volatile data_type& src) const
-    {
-      if (src < dst) {
-        dst = src;
-      }
-    }
-
-    PUGS_INLINE
-    void
-    init(data_type& value) const
-    {
-      value = std::numeric_limits<data_type>::max();
-    }
-
-    PUGS_INLINE
-    ArrayMin(const ArrayType& array) : m_array(array)
-    {
-      ;
-    }
-
-    PUGS_INLINE
-    ~ArrayMin() = default;
-  };
-
-  return ArrayMin(array);
-}
-
-template <typename DataType, template <typename> typename ArrayT>
-std::remove_const_t<DataType>
-max(const ArrayT<DataType>& array)
-{
-  using ArrayType  = ArrayT<DataType>;
-  using data_type  = std::remove_const_t<typename ArrayType::data_type>;
-  using index_type = typename ArrayType::index_type;
-
-  class ArrayMax
-  {
-   private:
-    const ArrayType m_array;
-
-   public:
-    PUGS_INLINE
-    operator data_type()
-    {
-      data_type reduced_value;
-      parallel_reduce(m_array.size(), *this, reduced_value);
-      return reduced_value;
-    }
-
-    PUGS_INLINE
-    void
-    operator()(const index_type& i, data_type& data) const
-    {
-      if (m_array[i] > data) {
-        data = m_array[i];
-      }
-    }
-
-    PUGS_INLINE
-    void
-    join(volatile data_type& dst, const volatile data_type& src) const
-    {
-      if (src > dst) {
-        dst = src;
-      }
-    }
-
-    PUGS_INLINE
-    void
-    init(data_type& value) const
-    {
-      value = std::numeric_limits<data_type>::min();
-    }
-
-    PUGS_INLINE
-    ArrayMax(const ArrayType& array) : m_array(array)
-    {
-      ;
-    }
-
-    PUGS_INLINE
-    ~ArrayMax() = default;
-  };
-
-  return ArrayMax(array);
-}
-
-template <typename DataType, template <typename> typename ArrayT>
-std::remove_const_t<DataType>
-sum(const ArrayT<DataType>& array)
-{
-  using ArrayType  = ArrayT<DataType>;
-  using data_type  = std::remove_const_t<DataType>;
-  using index_type = typename ArrayType::index_type;
-
-  class ArraySum
-  {
-   private:
-    const ArrayType m_array;
-
-   public:
-    PUGS_INLINE
-    operator data_type()
-    {
-      data_type reduced_value;
-      parallel_reduce(m_array.size(), *this, reduced_value);
-      return reduced_value;
-    }
-
-    PUGS_INLINE
-    void
-    operator()(const index_type& i, data_type& data) const
-    {
-      data += m_array[i];
-    }
-
-    PUGS_INLINE
-    void
-    join(volatile data_type& dst, const volatile data_type& src) const
-    {
-      dst += src;
-    }
-
-    PUGS_INLINE
-    void
-    init(data_type& value) const
-    {
-      if constexpr (std::is_arithmetic_v<data_type>) {
-        value = 0;
-      } else {
-        static_assert(is_tiny_vector_v<data_type> or is_tiny_matrix_v<data_type>, "invalid data type");
-        value = zero;
-      }
-    }
-
-    PUGS_INLINE
-    ArraySum(const ArrayType& array) : m_array(array)
-    {
-      ;
-    }
-
-    PUGS_INLINE
-    ~ArraySum() = default;
-  };
-
-  return ArraySum(array);
-}
-
-template <template <typename... SourceT> typename SourceArray,
-          template <typename... ImageT>
-          typename ImageArray,
-          typename... SourceT,
-          typename... ImageT>
-void
-value_copy(const SourceArray<SourceT...>& source_array, ImageArray<ImageT...>& image_array)
-{
-  using SourceDataType = typename SourceArray<SourceT...>::data_type;
-  using ImageDataType  = typename ImageArray<ImageT...>::data_type;
-
-  static_assert(std::is_same_v<std::remove_const_t<SourceDataType>, ImageDataType>);
-  static_assert(not std::is_const_v<ImageDataType>);
-
-  Assert(source_array.size() == image_array.size());
-
-  parallel_for(
-    source_array.size(), PUGS_LAMBDA(size_t i) { image_array[i] = source_array[i]; });
-}
-
-#endif   // ARRAY_UTILS_HPP
diff --git a/src/utils/CastArray.hpp b/src/utils/CastArray.hpp
index d329b4ef35f70f3a612ca430c45d0ec23bb7ed9d..07c696428573db04f05186464d0891851c686756 100644
--- a/src/utils/CastArray.hpp
+++ b/src/utils/CastArray.hpp
@@ -32,6 +32,19 @@ class [[nodiscard]] CastArray
     return m_values[i];
   }
 
+  template <typename ImageDataType, typename ImageCastDataType>
+  friend PUGS_INLINE void copy_to(const CastArray& source_array,
+                                  CastArray<ImageDataType, ImageCastDataType>& image_array)
+  {
+    static_assert(std::is_same_v<std::remove_const_t<CastDataType>, ImageCastDataType>);
+    static_assert(not std::is_const_v<ImageCastDataType>);
+
+    Assert(source_array.size() == image_array.size());
+    if (source_array.size() > 0) {
+      std::copy(source_array.m_values, source_array.m_values + source_array.size(), &image_array[0]);
+    }
+  }
+
   PUGS_INLINE
   CastArray& operator=(const CastArray&) = default;
 
diff --git a/src/utils/Messenger.hpp b/src/utils/Messenger.hpp
index 7e8b30c1a8e92c05fc30178e325110617c73c823..75ef1732a496e367917b55b86e4f2075570acd86 100644
--- a/src/utils/Messenger.hpp
+++ b/src/utils/Messenger.hpp
@@ -5,7 +5,6 @@
 #include <utils/PugsMacros.hpp>
 
 #include <utils/Array.hpp>
-#include <utils/ArrayUtils.hpp>
 #include <utils/CastArray.hpp>
 
 #include <type_traits>
@@ -136,7 +135,7 @@ class Messenger
     MPI_Gather(data_address, data_array.size(), mpi_datatype, gather_address, data_array.size(), mpi_datatype, rank,
                MPI_COMM_WORLD);
 #else    // PUGS_HAS_MPI
-    value_copy(data_array, gather_array);
+    copy_to(data_array, gather_array);
 #endif   // PUGS_HAS_MPI
   }
 
@@ -177,7 +176,7 @@ class Messenger
     MPI_Gatherv(data_address, data_array.size(), mpi_datatype, gather_address, sizes_address, positions_address,
                 mpi_datatype, rank, MPI_COMM_WORLD);
 #else    // PUGS_HAS_MPI
-    value_copy(data_array, gather_array);
+    copy_to(data_array, gather_array);
 #endif   // PUGS_HAS_MPI
   }
 
@@ -222,7 +221,7 @@ class Messenger
     MPI_Allgather(data_address, data_array.size(), mpi_datatype, gather_address, data_array.size(), mpi_datatype,
                   MPI_COMM_WORLD);
 #else    // PUGS_HAS_MPI
-    value_copy(data_array, gather_array);
+    copy_to(data_array, gather_array);
 #endif   // PUGS_HAS_MPI
   }
 
@@ -262,7 +261,7 @@ class Messenger
     MPI_Allgatherv(data_address, data_array.size(), mpi_datatype, gather_address, sizes_address, positions_address,
                    mpi_datatype, MPI_COMM_WORLD);
 #else    // PUGS_HAS_MPI
-    value_copy(data_array, gather_array);
+    copy_to(data_array, gather_array);
 #endif   // PUGS_HAS_MPI
   }
 
@@ -323,7 +322,7 @@ class Messenger
 
     MPI_Alltoall(sent_address, count, mpi_datatype, recv_address, count, mpi_datatype, MPI_COMM_WORLD);
 #else    // PUGS_HAS_MPI
-    value_copy(sent_array, recv_array);
+    copy_to(sent_array, recv_array);
 #endif   // PUGS_HAS_MPI
   }
 
@@ -378,7 +377,7 @@ class Messenger
     Assert(sent_array_list.size() == 1);
     Assert(recv_array_list.size() == 1);
 
-    value_copy(sent_array_list[0], recv_array_list[0]);
+    copy_to(sent_array_list[0], recv_array_list[0]);
 #endif   // PUGS_HAS_MPI
   }
 
diff --git a/src/utils/SmallArray.hpp b/src/utils/SmallArray.hpp
index b5186a7f6341606a92084fd7ee5521b5d252b4ee..9e0782f1c0adb0b9bfa4bcf25bcaa8977a3800d4 100644
--- a/src/utils/SmallArray.hpp
+++ b/src/utils/SmallArray.hpp
@@ -6,6 +6,7 @@
 #include <utils/PugsAssert.hpp>
 #include <utils/PugsMacros.hpp>
 #include <utils/PugsUtils.hpp>
+#include <utils/Types.hpp>
 
 #include <algorithm>
 
@@ -128,6 +129,13 @@ class [[nodiscard]] SmallArray
   ~SmallArray() = default;
 };
 
+template <typename DataType>
+PUGS_INLINE size_t
+size(const SmallArray<DataType>& array)
+{
+  return array.size();
+}
+
 // map, multimap, unordered_map and stack cannot be copied this way
 template <typename Container>
 PUGS_INLINE SmallArray<std::remove_const_t<typename Container::value_type>>
@@ -141,4 +149,60 @@ convert_to_small_array(const Container& given_container)
   return array;
 }
 
+template <typename DataType>
+[[nodiscard]] std::remove_const_t<DataType>
+min(const SmallArray<DataType>& array)
+{
+  using data_type  = std::remove_const_t<DataType>;
+  using index_type = typename SmallArray<DataType>::index_type;
+
+  data_type min_value = std::numeric_limits<data_type>::max();
+  for (index_type i = 0; i < array.size(); ++i) {
+    if (array[i] < min_value) {
+      min_value = array[i];
+    }
+  }
+  return min_value;
+}
+
+template <typename DataType>
+[[nodiscard]] std::remove_const_t<DataType>
+max(const SmallArray<DataType>& array)
+{
+  using data_type  = std::remove_const_t<DataType>;
+  using index_type = typename SmallArray<DataType>::index_type;
+
+  data_type max_value = -std::numeric_limits<data_type>::max();
+  for (index_type i = 0; i < array.size(); ++i) {
+    if (array[i] > max_value) {
+      max_value = array[i];
+    }
+  }
+  return max_value;
+}
+
+template <typename DataType>
+[[nodiscard]] std::remove_const_t<DataType>
+sum(const SmallArray<DataType>& array)
+{
+  using data_type  = std::remove_const_t<DataType>;
+  using index_type = typename SmallArray<DataType>::index_type;
+
+  data_type sum_value = [] {
+    if constexpr (std::is_arithmetic_v<DataType>) {
+      return 0;
+    } else if constexpr (is_tiny_vector_v<DataType> or is_tiny_matrix_v<DataType>) {
+      return zero;
+    } else {
+      static_assert(std::is_arithmetic_v<DataType>, "invalid data type");
+    }
+  }();
+
+  for (index_type i = 0; i < array.size(); ++i) {
+    sum_value += array[i];
+  }
+
+  return sum_value;
+}
+
 #endif   // SMALL_ARRAY_HPP
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 25bcaa008af00dd141bfc4a0ba1fc65bb11a2003..f825a2cfdfcd2f869037039d10eb041aced9980c 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -11,7 +11,6 @@ add_executable (unit_tests
   test_AffectationToTupleProcessor.cpp
   test_Array.cpp
   test_ArraySubscriptProcessor.cpp
-  test_ArrayUtils.cpp
   test_ASTBuilder.cpp
   test_ASTDotPrinter.cpp
   test_ASTModulesImporter.cpp
@@ -54,6 +53,7 @@ add_executable (unit_tests
   test_BuiltinFunctionEmbedderTable.cpp
   test_BuiltinFunctionProcessor.cpp
   test_CastArray.cpp
+  test_CellIntegrator.cpp
   test_ConsoleManager.cpp
   test_CG.cpp
   test_ContinueProcessor.cpp
@@ -61,6 +61,8 @@ add_executable (unit_tests
   test_CRSGraph.cpp
   test_CRSMatrix.cpp
   test_CRSMatrixDescriptor.cpp
+  test_CubeGaussQuadrature.cpp
+  test_CubeTransformation.cpp
   test_DataVariant.cpp
   test_Demangle.cpp
   test_DiscreteFunctionDescriptorP0.cpp
@@ -68,12 +70,14 @@ add_executable (unit_tests
   test_DiscreteFunctionType.cpp
   test_DiscreteFunctionUtils.cpp
   test_DoWhileProcessor.cpp
+  test_EdgeIntegrator.cpp
   test_EigenvalueSolver.cpp
   test_EmbeddedData.cpp
   test_EmbeddedIDiscreteFunctionUtils.cpp
   test_EscapedString.cpp
   test_Exceptions.cpp
   test_ExecutionPolicy.cpp
+  test_FaceIntegrator.cpp
   test_FakeProcessor.cpp
   test_ForProcessor.cpp
   test_FunctionArgumentConverter.cpp
@@ -86,6 +90,7 @@ add_executable (unit_tests
   test_ItemType.cpp
   test_LinearSolver.cpp
   test_LinearSolverOptions.cpp
+  test_LineTransformation.cpp
   test_ListAffectationProcessor.cpp
   test_MathModule.cpp
   test_NameProcessor.cpp
@@ -93,17 +98,29 @@ add_executable (unit_tests
   test_OStream.cpp
   test_ParseError.cpp
   test_PETScUtils.cpp
+  test_PrismGaussQuadrature.cpp
+  test_PrismTransformation.cpp
   test_PugsAssert.cpp
   test_PugsFunctionAdapter.cpp
   test_PugsUtils.cpp
+  test_PyramidGaussQuadrature.cpp
+  test_PyramidTransformation.cpp
   test_RevisionInfo.cpp
   test_SmallArray.cpp
   test_SmallVector.cpp
+  test_SquareGaussQuadrature.cpp
+  test_SquareTransformation.cpp
   test_SymbolTable.cpp
   test_Table.cpp
+  test_TetrahedronGaussQuadrature.cpp
+  test_TetrahedronTransformation.cpp
+  test_TensorialGaussLegendreQuadrature.cpp
+  test_TensorialGaussLobattoQuadrature.cpp
   test_Timer.cpp
   test_TinyMatrix.cpp
   test_TinyVector.cpp
+  test_TriangleGaussQuadrature.cpp
+  test_TriangleTransformation.cpp
   test_TupleToVectorProcessor.cpp
   test_UnaryExpressionProcessor.cpp
   test_UnaryOperatorMangler.cpp
@@ -145,6 +162,7 @@ target_link_libraries (unit_tests
   PugsLanguage
   PugsMesh
   PugsAlgebra
+  PugsAnalysis
   PugsScheme
   PugsOutput
   PugsUtils
@@ -160,6 +178,7 @@ target_link_libraries (unit_tests
 target_link_libraries (mpi_unit_tests
   test_Pugs_MeshDataBase
   PugsAlgebra
+  PugsAnalysis
   PugsUtils
   PugsLanguage
   PugsLanguageAST
diff --git a/tests/MeshDataBaseForTests.cpp b/tests/MeshDataBaseForTests.cpp
index 95399d4564f38313f443f144cf214298577f315a..b58cc22e8eef00cdbdc820d01efae0dce9147a44 100644
--- a/tests/MeshDataBaseForTests.cpp
+++ b/tests/MeshDataBaseForTests.cpp
@@ -1,8 +1,13 @@
 #include <MeshDataBaseForTests.hpp>
 #include <mesh/CartesianMeshBuilder.hpp>
 #include <mesh/Connectivity.hpp>
+#include <mesh/GmshReader.hpp>
+#include <utils/Messenger.hpp>
 #include <utils/PugsAssert.hpp>
 
+#include <filesystem>
+#include <fstream>
+
 const MeshDataBaseForTests* MeshDataBaseForTests::m_instance = nullptr;
 
 MeshDataBaseForTests::MeshDataBaseForTests()
@@ -15,6 +20,10 @@ MeshDataBaseForTests::MeshDataBaseForTests()
 
   m_cartesian_3d_mesh = std::dynamic_pointer_cast<const Mesh<Connectivity<3>>>(
     CartesianMeshBuilder{TinyVector<3>{0, 1, 0}, TinyVector<3>{2, -1, 3}, TinyVector<3, size_t>{6, 7, 4}}.mesh());
+
+  m_unordered_1d_mesh = _buildUnordered1dMesh();
+  m_hybrid_2d_mesh    = _buildHybrid2dMesh();
+  m_hybrid_3d_mesh    = _buildHybrid3dMesh();
 }
 
 const MeshDataBaseForTests&
@@ -39,19 +48,913 @@ MeshDataBaseForTests::destroy()
 }
 
 std::shared_ptr<const Mesh<Connectivity<1>>>
-MeshDataBaseForTests::cartesianMesh1D() const
+MeshDataBaseForTests::cartesian1DMesh() const
 {
   return m_cartesian_1d_mesh;
 }
 
 std::shared_ptr<const Mesh<Connectivity<2>>>
-MeshDataBaseForTests::cartesianMesh2D() const
+MeshDataBaseForTests::cartesian2DMesh() const
 {
   return m_cartesian_2d_mesh;
 }
 
 std::shared_ptr<const Mesh<Connectivity<3>>>
-MeshDataBaseForTests::cartesianMesh3D() const
+MeshDataBaseForTests::cartesian3DMesh() const
 {
   return m_cartesian_3d_mesh;
 }
+
+std::shared_ptr<const Mesh<Connectivity<1>>>
+MeshDataBaseForTests::unordered1DMesh() const
+{
+  return m_unordered_1d_mesh;
+}
+
+std::shared_ptr<const Mesh<Connectivity<2>>>
+MeshDataBaseForTests::hybrid2DMesh() const
+{
+  return m_hybrid_2d_mesh;
+}
+
+std::shared_ptr<const Mesh<Connectivity<3>>>
+MeshDataBaseForTests::hybrid3DMesh() const
+{
+  return m_hybrid_3d_mesh;
+}
+
+std::shared_ptr<const Mesh<Connectivity<1>>>
+MeshDataBaseForTests::_buildUnordered1dMesh()
+{
+  const std::string filename = std::filesystem::path{PUGS_BINARY_DIR}.append("tests").append("unordered-1d.msh");
+  if (parallel::rank() == 0) {
+    std::ofstream fout(filename);
+    fout << R"($MeshFormat
+2.2 0 8
+$EndMeshFormat
+$PhysicalNames
+4
+0 1 "XMIN"
+0 2 "XMAX"
+1 3 "LEFT"
+1 4 "RIGHT"
+$EndPhysicalNames
+$Nodes
+35
+1 0 0 0
+2 -1 0 0
+3 1 0 0
+4 0.03246387076421061 0 0
+5 0.07692961499792127 0 0
+6 0.1378343163027127 0 0
+7 0.2212554508344625 0 0
+8 0.3355173320316702 0 0
+9 0.4920217492706127 0 0
+10 0.7063857794730655 0 0
+11 -0.9513615311521172 0 0
+12 -0.9036693300347614 0 0
+13 -0.8569049934960202 0 0
+14 -0.8110504814343311 0 0
+15 -0.7660880891035384 0 0
+16 -0.7220004662902741 0 0
+17 -0.6787705699229041 0 0
+18 -0.6363817403249178 0 0
+19 -0.5948175927947426 0 0
+20 -0.5540620983944274 0 0
+21 -0.5140995098187926 0 0
+22 -0.4749144219806904 0 0
+23 -0.4364916774353312 0 0
+24 -0.3988164798351664 0 0
+25 -0.3618742567441507 0 0
+26 -0.3256507606537927 0 0
+27 -0.2901320091863738 0 0
+28 -0.2553042883413312 0 0
+29 -0.2211541538737678 0 0
+30 -0.1876684263237806 0 0
+31 -0.1548341743437643 0 0
+32 -0.1226387276497952 0 0
+33 -0.09106965120674937 0 0
+34 -0.0601147701140996 0 0
+35 -0.02976212372405818 0 0
+$EndNodes
+$Elements
+36
+1 15 2 1 2 2
+2 15 2 2 3 3
+3 1 2 4 1 1 4
+4 1 2 4 1 4 5
+5 1 2 4 1 5 6
+6 1 2 4 1 6 7
+7 1 2 4 1 7 8
+8 1 2 4 1 8 9
+9 1 2 4 1 9 10
+10 1 2 4 1 10 3
+11 1 2 3 2 2 11
+12 1 2 3 2 11 12
+13 1 2 3 2 12 13
+14 1 2 3 2 13 14
+15 1 2 3 2 14 15
+16 1 2 3 2 15 16
+17 1 2 3 2 16 17
+18 1 2 3 2 17 18
+19 1 2 3 2 18 19
+20 1 2 3 2 19 20
+21 1 2 3 2 20 21
+22 1 2 3 2 21 22
+23 1 2 3 2 22 23
+24 1 2 3 2 23 24
+25 1 2 3 2 24 25
+26 1 2 3 2 25 26
+27 1 2 3 2 26 27
+28 1 2 3 2 27 28
+29 1 2 3 2 28 29
+30 1 2 3 2 29 30
+31 1 2 3 2 30 31
+32 1 2 3 2 31 32
+33 1 2 3 2 32 33
+34 1 2 3 2 33 34
+35 1 2 3 2 34 35
+36 1 2 3 2 35 1
+$EndElements
+)";
+  }
+
+  return std::dynamic_pointer_cast<const Mesh<Connectivity<1>>>(GmshReader{filename}.mesh());
+}
+
+std::shared_ptr<const Mesh<Connectivity<2>>>
+MeshDataBaseForTests::_buildHybrid2dMesh()
+{
+  const std::string filename = std::filesystem::path{PUGS_BINARY_DIR}.append("tests").append("hybrid-2d.msh");
+  if (parallel::rank() == 0) {
+    std::ofstream fout(filename);
+    fout << R"($MeshFormat
+2.2 0 8
+$EndMeshFormat
+$PhysicalNames
+10
+0 7 "XMINYMIN"
+0 8 "XMINYMAX"
+0 9 "XMAXYMIN"
+0 10 "XMAXYMAX"
+1 1 "XMIN"
+1 2 "XMAX"
+1 3 "YMAX"
+1 4 "YMIN"
+2 5 "LEFT"
+2 6 "RIGHT"
+$EndPhysicalNames
+$Nodes
+53
+1 0 0 0
+2 1 0 0
+3 1 1 0
+4 0 1 0
+5 2 0 0
+6 2 1 0
+7 0 0.7500000000003477 0
+8 0 0.5000000000020616 0
+9 0 0.2500000000010419 0
+10 1 0.2499999999994109 0
+11 1 0.4999999999986918 0
+12 1 0.7499999999993401 0
+13 2 0.2499999999994109 0
+14 2 0.4999999999986918 0
+15 2 0.7499999999993401 0
+16 0.7500000000003477 1 0
+17 0.5000000000020616 1 0
+18 0.2500000000010419 1 0
+19 1.749999999999999 1 0
+20 1.499999999999998 1 0
+21 1.249999999999999 1 0
+22 0.2499999999994109 0 0
+23 0.4999999999986918 0 0
+24 0.7499999999993401 0 0
+25 1.250000000000001 0 0
+26 1.500000000000002 0 0
+27 1.750000000000001 0 0
+28 0.5645539210988633 0.7822119881665017 0
+29 0.8034002623653069 0.616177001724073 0
+30 0.6429040982169911 0.5281266166868597 0
+31 0.8070137458488089 0.4050273873671746 0
+32 0.193468206450602 0.7967828567280011 0
+33 0.192644796000149 0.6419508693748935 0
+34 0.3924123452244094 0.5739639975328588 0
+35 0.3555609898784376 0.7915857937795373 0
+36 0.1932938945983771 0.4291816994842411 0
+37 0.3837729988924632 0.3582605556259788 0
+38 0.5797710532625071 0.2819566416178859 0
+39 0.7822395465040892 0.7780567773069014 0
+40 0.355038784333282 0.2031847484281184 0
+41 0.2059222030287144 0.2167595959294507 0
+42 0.7859869002303115 0.2399319972346836 0
+43 1.212848849190124 0.2732369527435796 0
+44 1.307250451008527 0.5758879308566747 0
+45 1.493594137086004 0.410605759034483 0
+46 1.439097356236337 0.1761649793157971 0
+47 1.574782862067863 0.7190230111887566 0
+48 1.740502300977129 0.4934083027495074 0
+49 1.853320217111459 0.2440179674328906 0
+50 1.629419001691624 0.2206995018497341 0
+51 1.844214193840832 0.743738552322716 0
+52 1.151907331010239 0.7762099486172758 0
+53 1.354193535052909 0.8313717608489495 0
+$EndNodes
+$Elements
+86
+1 15 2 7 1 1
+2 15 2 8 4 4
+3 15 2 9 5 5
+4 15 2 10 6 6
+5 1 2 1 1 4 7
+6 1 2 1 1 7 8
+7 1 2 1 1 8 9
+8 1 2 1 1 9 1
+9 1 2 2 3 5 13
+10 1 2 2 3 13 14
+11 1 2 2 3 14 15
+12 1 2 2 3 15 6
+13 1 2 3 4 3 16
+14 1 2 3 4 16 17
+15 1 2 3 4 17 18
+16 1 2 3 4 18 4
+17 1 2 3 5 6 19
+18 1 2 3 5 19 20
+19 1 2 3 5 20 21
+20 1 2 3 5 21 3
+21 1 2 4 6 1 22
+22 1 2 4 6 22 23
+23 1 2 4 6 23 24
+24 1 2 4 6 24 2
+25 1 2 4 7 2 25
+26 1 2 4 7 25 26
+27 1 2 4 7 26 27
+28 1 2 4 7 27 5
+29 2 2 6 2 43 45 44
+30 2 2 6 2 46 45 43
+31 2 2 6 2 11 43 44
+32 2 2 6 2 45 48 47
+33 2 2 6 2 46 43 25
+34 2 2 6 2 44 45 47
+35 2 2 6 2 43 11 10
+36 2 2 6 2 48 50 49
+37 2 2 6 2 14 51 48
+38 2 2 6 2 45 50 48
+39 2 2 6 2 27 49 50
+40 2 2 6 2 52 11 44
+41 2 2 6 2 49 27 5
+42 2 2 6 2 47 19 20
+43 2 2 6 2 12 52 3
+44 2 2 6 2 52 21 3
+45 2 2 6 2 47 53 44
+46 2 2 6 2 27 50 26
+47 2 2 6 2 43 10 2
+48 2 2 6 2 25 43 2
+49 2 2 6 2 20 53 47
+50 2 2 6 2 21 53 20
+51 2 2 6 2 46 50 45
+52 2 2 6 2 26 50 46
+53 2 2 6 2 44 53 52
+54 2 2 6 2 46 25 26
+55 2 2 6 2 11 52 12
+56 2 2 6 2 47 48 51
+57 2 2 6 2 51 14 15
+58 2 2 6 2 13 49 5
+59 2 2 6 2 51 19 47
+60 2 2 6 2 21 52 53
+61 2 2 6 2 19 51 6
+62 2 2 6 2 51 15 6
+63 2 2 6 2 48 49 14
+64 2 2 6 2 49 13 14
+65 3 2 5 1 37 34 33 36
+66 3 2 5 1 30 31 11 29
+67 3 2 5 1 35 34 30 28
+68 3 2 5 1 30 29 39 28
+69 3 2 5 1 34 37 38 30
+70 3 2 5 1 37 40 23 38
+71 3 2 5 1 41 40 37 36
+72 3 2 5 1 28 17 18 35
+73 3 2 5 1 30 38 42 31
+74 3 2 5 1 11 31 42 10
+75 3 2 5 1 39 16 17 28
+76 3 2 5 1 33 32 4 7
+77 3 2 5 1 11 12 39 29
+78 3 2 5 1 35 18 4 32
+79 3 2 5 1 23 40 41 22
+80 3 2 5 1 16 39 12 3
+81 3 2 5 1 41 36 8 9
+82 3 2 5 1 9 1 22 41
+83 3 2 5 1 10 42 24 2
+84 3 2 5 1 23 24 42 38
+85 3 2 5 1 7 8 36 33
+86 3 2 5 1 35 32 33 34
+$EndElements
+)";
+  }
+  return std::dynamic_pointer_cast<const Mesh<Connectivity<2>>>(GmshReader{filename}.mesh());
+}
+
+std::shared_ptr<const Mesh<Connectivity<3>>>
+MeshDataBaseForTests::_buildHybrid3dMesh()
+{
+  const std::string filename = std::filesystem::path{PUGS_BINARY_DIR}.append("tests").append("hybrid-3d.msh");
+  if (parallel::rank() == 0) {
+    std::ofstream fout(filename);
+    fout << R"($MeshFormat
+2.2 0 8
+$EndMeshFormat
+$PhysicalNames
+29
+0 40 "XMINYMINZMIN"
+0 41 "XMAXYMINZMIN"
+0 42 "XMINYMAXZMIN"
+0 43 "XMINYMAXZMAX"
+0 44 "XMINYMINZMAX"
+0 45 "XMAXYMINZMAX"
+0 47 "XMAXYMAXZMAX"
+0 51 "XMAXYMAXZMIN"
+1 28 "XMINZMIN"
+1 29 "XMINZMAX"
+1 30 "XMINYMAX"
+1 31 "XMINYMIN"
+1 32 "XMAXZMIN"
+1 33 "XMAXZMAX"
+1 34 "XMAXYMAX"
+1 35 "XMAXYMIN"
+1 36 "YMINZMIN"
+1 37 "YMINZMAX"
+1 38 "YMAXZMIN"
+1 39 "YMAXZMAX"
+2 22 "XMIN"
+2 23 "XMAX"
+2 24 "ZMAX"
+2 25 "ZMIN"
+2 26 "YMAX"
+2 27 "YMIN"
+3 52 "HEXAHEDRA"
+3 53 "PYRAMIDS"
+3 54 "TETRAHEDRA"
+$EndPhysicalNames
+$Nodes
+132
+1 0 0 0
+2 0.7 0 0
+3 0.8 1 0
+4 0 1 0
+5 1.3 0 0
+6 1.2 1 0
+7 0 1 1
+8 0 0 1
+9 0.7 0 1
+10 0.8 1 1
+11 1.3 0 1
+12 1.2 1 1
+13 2 0 0
+14 2 1 0
+15 2 0 1
+16 2 1 1
+17 0 0.7500000000003466 0
+18 0 0.5000000000020587 0
+19 0 0.2500000000010403 0
+20 0.7249999999999414 0.2499999999994139 0
+21 0.7499999999998691 0.4999999999986909 0
+22 0.7749999999999342 0.7499999999993421 0
+23 1.266666666666749 0.3333333333325114 0
+24 1.23333333333342 0.6666666666657952 0
+25 0.4000000000010956 1 0
+26 0.3499999999991014 0 0
+27 0.9999999999987851 0 0
+28 0 0.7500000000003466 1
+29 0 0.5000000000020587 1
+30 0 0.2500000000010403 1
+31 0.3499999999991014 0 1
+32 0.7249999999999414 0.2499999999994139 1
+33 0.7499999999998691 0.4999999999986909 1
+34 0.7749999999999342 0.7499999999993421 1
+35 0.4000000000010956 1 1
+36 0 1 0.3333333333333333
+37 0 1 0.6666666666666666
+38 0 0 0.3333333333333333
+39 0 0 0.6666666666666666
+40 0.7 0 0.3333333333333333
+41 0.7 0 0.6666666666666666
+42 0.8 1 0.3333333333333333
+43 0.8 1 0.6666666666666666
+44 0.9999999999987851 0 1
+45 1.266666666666749 0.3333333333325114 1
+46 1.23333333333342 0.6666666666657952 1
+47 1.3 0 0.3333333333333333
+48 1.3 0 0.6666666666666666
+49 1.2 1 0.3333333333333333
+50 1.2 1 0.6666666666666666
+51 1.630495225600928 0 0
+52 1.630495225600928 0 1
+53 2 0 0.499999999998694
+54 2 0.499999999998694 1
+55 2 0.499999999998694 0
+56 1.577708829260476 1 0
+57 1.577708829258421 1 1
+58 2 1 0.499999999998694
+59 0.3962888297748171 0.7916234456658734 0
+60 0.5588133104363274 0.5235006176096062 0
+61 0.48540966431768 0.3326974099157888 0
+62 0.2545886146233393 0.4410037988896774 0
+63 0.1888904744336107 0.2469120833355397 0
+64 0.9952483415423343 0.5591370310039079 0
+65 1.009047204638942 0.8056163713004886 0
+66 0.9496163434716166 0.3148023652713143 0
+67 0 0.7500000000003466 0.3333333333333333
+68 0 0.7500000000003466 0.6666666666666666
+69 0 0.5000000000020587 0.3333333333333333
+70 0 0.5000000000020587 0.6666666666666666
+71 0 0.2500000000010403 0.3333333333333333
+72 0 0.2500000000010403 0.6666666666666666
+73 0.3499999999991014 0 0.3333333333333333
+74 0.3499999999991014 0 0.6666666666666666
+75 0.7249999999999414 0.2499999999994139 0.3333333333333333
+76 0.7249999999999414 0.2499999999994139 0.6666666666666666
+77 0.7499999999998691 0.4999999999986909 0.3333333333333333
+78 0.7499999999998691 0.4999999999986909 0.6666666666666666
+79 0.7749999999999342 0.7499999999993421 0.3333333333333333
+80 0.7749999999999342 0.7499999999993421 0.6666666666666666
+81 0.4000000000010956 1 0.3333333333333333
+82 0.4000000000010956 1 0.6666666666666666
+83 0.3962888297748171 0.7916234456658734 1
+84 0.5588133104363274 0.5235006176096062 1
+85 0.48540966431768 0.3326974099157888 1
+86 0.2545886146233393 0.4410037988896774 1
+87 0.1888904744336107 0.2469120833355397 1
+88 0.9999999999987851 0 0.3333333333333333
+89 0.9999999999987851 0 0.6666666666666666
+90 1.266666666666749 0.3333333333325114 0.3333333333333333
+91 1.266666666666749 0.3333333333325114 0.6666666666666666
+92 1.23333333333342 0.6666666666657952 0.3333333333333333
+93 1.23333333333342 0.6666666666657952 0.6666666666666666
+94 0.9952483415423343 0.5591370310039079 1
+95 1.009047204638942 0.8056163713004886 1
+96 0.9496163434716166 0.3148023652713143 1
+97 1.694794622046484 0 0.4999999999999999
+98 2 0.667671095007814 0.3323289049917093
+99 2 0.3400507564846686 0.5028063863717376
+100 2 0.7126842003874787 0.6873157996117608
+101 1.638498486684473 0.2509397074193687 1
+102 1.619828855633658 0.6299359336032246 1
+103 1.638498486684556 0.2509397074193682 0
+104 1.619828855634071 0.629935933603235 0
+105 1.657210007479593 1 0.5000000082593427
+106 0.3962888297748171 0.7916234456658734 0.3333333333333333
+107 0.3962888297748171 0.7916234456658734 0.6666666666666666
+108 0.5588133104363274 0.5235006176096062 0.3333333333333333
+109 0.5588133104363274 0.5235006176096062 0.6666666666666666
+110 0.48540966431768 0.3326974099157888 0.3333333333333333
+111 0.48540966431768 0.3326974099157888 0.6666666666666666
+112 0.2545886146233393 0.4410037988896774 0.3333333333333333
+113 0.2545886146233393 0.4410037988896774 0.6666666666666666
+114 0.1888904744336107 0.2469120833355397 0.3333333333333333
+115 0.1888904744336107 0.2469120833355397 0.6666666666666666
+116 0.9952483415423343 0.5591370310039079 0.3333333333333333
+117 0.9952483415423343 0.5591370310039079 0.6666666666666666
+118 1.009047204638942 0.8056163713004886 0.3333333333333333
+119 1.009047204638942 0.8056163713004886 0.6666666666666666
+120 0.9496163434716166 0.3148023652713143 0.3333333333333333
+121 0.9496163434716166 0.3148023652713143 0.6666666666666666
+122 1.605048220195734 0.3680048220187183 0.5
+123 1.521560099439846 0.6946560099431293 0.4999999999999999
+124 1.449585918125941 0.1832919251455124 0.1666666666666667
+125 1.449585918125941 0.1832919251455124 0.4999999999999999
+126 1.449585918125941 0.1832919251455124 0.8333333333333333
+127 1.416252584792843 0.5166252584784292 0.1666666666666667
+128 1.416252584792843 0.5166252584784292 0.4999999999999999
+129 1.416252584792843 0.5166252584784292 0.8333333333333333
+130 1.382919251459699 0.8499585918121965 0.1666666666666667
+131 1.382919251459699 0.8499585918121965 0.4999999999999999
+132 1.382919251459699 0.8499585918121965 0.8333333333333333
+$EndNodes
+$Elements
+384
+1 15 2 40 1 1
+2 15 2 42 4 4
+3 15 2 43 7 7
+4 15 2 44 8 8
+5 15 2 41 27 13
+6 15 2 51 28 14
+7 15 2 45 29 15
+8 15 2 47 30 16
+9 1 2 28 1 4 17
+10 1 2 28 1 17 18
+11 1 2 28 1 18 19
+12 1 2 28 1 19 1
+13 1 2 38 4 3 25
+14 1 2 38 4 25 4
+15 1 2 38 5 6 3
+16 1 2 36 6 1 26
+17 1 2 36 6 26 2
+18 1 2 36 7 2 27
+19 1 2 36 7 27 5
+20 1 2 29 12 7 28
+21 1 2 29 12 28 29
+22 1 2 29 12 29 30
+23 1 2 29 12 30 8
+24 1 2 37 13 8 31
+25 1 2 37 13 31 9
+26 1 2 39 15 10 35
+27 1 2 39 15 35 7
+28 1 2 30 17 4 36
+29 1 2 30 17 36 37
+30 1 2 30 17 37 7
+31 1 2 31 18 1 38
+32 1 2 31 18 38 39
+33 1 2 31 18 39 8
+34 1 2 37 35 9 44
+35 1 2 37 35 44 11
+36 1 2 39 37 12 10
+37 1 2 36 49 5 51
+38 1 2 36 49 51 13
+39 1 2 37 50 11 52
+40 1 2 37 50 52 15
+41 1 2 35 51 13 53
+42 1 2 35 51 53 15
+43 1 2 33 52 15 54
+44 1 2 33 52 54 16
+45 1 2 32 53 13 55
+46 1 2 32 53 55 14
+47 1 2 38 54 14 56
+48 1 2 38 54 56 6
+49 1 2 39 55 12 57
+50 1 2 39 55 57 16
+51 1 2 34 56 14 58
+52 1 2 34 56 58 16
+53 2 2 25 2 20 2 27
+54 2 2 25 2 23 27 5
+55 2 2 25 2 64 23 24
+56 2 2 25 2 22 65 3
+57 2 2 25 2 23 64 66
+58 2 2 25 2 65 6 3
+59 2 2 25 2 64 21 66
+60 2 2 25 2 65 24 6
+61 2 2 25 2 24 65 64
+62 2 2 25 2 65 22 64
+63 2 2 25 2 66 20 27
+64 2 2 25 2 21 64 22
+65 2 2 25 2 20 66 21
+66 2 2 25 2 23 66 27
+67 2 2 24 54 32 9 44
+68 2 2 24 54 45 44 11
+69 2 2 24 54 94 45 46
+70 2 2 24 54 34 95 10
+71 2 2 24 54 45 94 96
+72 2 2 24 54 95 12 10
+73 2 2 24 54 94 33 96
+74 2 2 24 54 95 46 12
+75 2 2 24 54 46 95 94
+76 2 2 24 54 95 34 94
+77 2 2 24 54 96 32 44
+78 2 2 24 54 33 94 34
+79 2 2 24 54 32 96 33
+80 2 2 24 54 45 96 44
+81 2 2 27 55 51 5 47
+82 2 2 27 55 48 11 52
+83 2 2 27 55 51 97 13
+84 2 2 27 55 97 53 13
+85 2 2 27 55 97 52 15
+86 2 2 27 55 15 53 97
+87 2 2 27 55 97 47 48
+88 2 2 27 55 47 97 51
+89 2 2 27 55 48 52 97
+90 2 2 23 56 53 99 13
+91 2 2 23 56 99 55 13
+92 2 2 23 56 55 98 14
+93 2 2 23 56 98 58 14
+94 2 2 23 56 99 53 15
+95 2 2 23 56 54 99 15
+96 2 2 23 56 100 54 16
+97 2 2 23 56 58 100 16
+98 2 2 23 56 100 99 54
+99 2 2 23 56 55 99 98
+100 2 2 23 56 100 58 98
+101 2 2 23 56 98 99 100
+102 2 2 24 57 45 101 11
+103 2 2 24 57 101 52 11
+104 2 2 24 57 46 12 57
+105 2 2 24 57 52 101 15
+106 2 2 24 57 101 54 15
+107 2 2 24 57 54 102 16
+108 2 2 24 57 102 57 16
+109 2 2 24 57 46 102 45
+110 2 2 24 57 101 45 102
+111 2 2 24 57 102 46 57
+112 2 2 24 57 102 54 101
+113 2 2 25 58 23 103 5
+114 2 2 25 58 103 51 5
+115 2 2 25 58 24 6 56
+116 2 2 25 58 51 103 13
+117 2 2 25 58 103 55 13
+118 2 2 25 58 55 104 14
+119 2 2 25 58 104 56 14
+120 2 2 25 58 24 104 23
+121 2 2 25 58 103 23 104
+122 2 2 25 58 104 24 56
+123 2 2 25 58 104 55 103
+124 2 2 26 59 6 49 56
+125 2 2 26 59 50 12 57
+126 2 2 26 59 56 105 14
+127 2 2 26 59 105 58 14
+128 2 2 26 59 105 57 16
+129 2 2 26 59 16 58 105
+130 2 2 26 59 105 49 50
+131 2 2 26 59 49 105 56
+132 2 2 26 59 50 57 105
+133 3 2 25 1 59 60 21 22
+134 3 2 25 1 22 3 25 59
+135 3 2 25 1 18 62 59 17
+136 3 2 25 1 25 4 17 59
+137 3 2 25 1 62 63 26 61
+138 3 2 25 1 60 61 20 21
+139 3 2 25 1 62 18 19 63
+140 3 2 25 1 26 2 20 61
+141 3 2 25 1 61 60 59 62
+142 3 2 25 1 19 1 26 63
+143 3 2 22 19 4 17 67 36
+144 3 2 22 19 36 67 68 37
+145 3 2 22 19 37 68 28 7
+146 3 2 22 19 17 18 69 67
+147 3 2 22 19 67 69 70 68
+148 3 2 22 19 68 70 29 28
+149 3 2 22 19 18 19 71 69
+150 3 2 22 19 69 71 72 70
+151 3 2 22 19 70 72 30 29
+152 3 2 22 19 19 1 38 71
+153 3 2 22 19 71 38 39 72
+154 3 2 22 19 72 39 8 30
+155 3 2 27 23 1 26 73 38
+156 3 2 27 23 38 73 74 39
+157 3 2 27 23 39 74 31 8
+158 3 2 27 23 26 2 40 73
+159 3 2 27 23 73 40 41 74
+160 3 2 27 23 74 41 9 31
+161 3 2 26 31 3 25 81 42
+162 3 2 26 31 42 81 82 43
+163 3 2 26 31 43 82 35 10
+164 3 2 26 31 25 4 36 81
+165 3 2 26 31 81 36 37 82
+166 3 2 26 31 82 37 7 35
+167 3 2 24 32 83 84 33 34
+168 3 2 24 32 34 10 35 83
+169 3 2 24 32 29 86 83 28
+170 3 2 24 32 35 7 28 83
+171 3 2 24 32 86 87 31 85
+172 3 2 24 32 84 85 32 33
+173 3 2 24 32 86 29 30 87
+174 3 2 24 32 31 9 32 85
+175 3 2 24 32 85 84 83 86
+176 3 2 24 32 30 8 31 87
+177 3 2 27 45 2 27 88 40
+178 3 2 27 45 40 88 89 41
+179 3 2 27 45 41 89 44 9
+180 3 2 27 45 27 5 47 88
+181 3 2 27 45 88 47 48 89
+182 3 2 27 45 89 48 11 44
+183 3 2 26 53 6 3 42 49
+184 3 2 26 53 49 42 43 50
+185 3 2 26 53 50 43 10 12
+186 4 2 54 3 90 103 127 122
+187 4 2 54 3 101 91 129 122
+188 4 2 54 3 103 90 124 122
+189 4 2 54 3 126 91 101 122
+190 4 2 54 3 92 104 130 123
+191 4 2 54 3 102 93 132 123
+192 4 2 54 3 129 45 91 101
+193 4 2 54 3 90 23 127 103
+194 4 2 54 3 45 126 91 101
+195 4 2 54 3 23 90 124 103
+196 4 2 54 3 92 127 104 123
+197 4 2 54 3 93 102 129 123
+198 4 2 54 3 125 90 91 122
+199 4 2 54 3 91 90 128 122
+200 4 2 54 3 104 127 103 122
+201 4 2 54 3 101 129 102 122
+202 4 2 54 3 127 122 104 123
+203 4 2 54 3 102 122 129 123
+204 4 2 54 3 103 124 97 122
+205 4 2 54 3 126 101 97 122
+206 4 2 54 3 132 57 102 123
+207 4 2 54 3 130 104 56 123
+208 4 2 54 3 46 93 132 102
+209 4 2 54 3 92 24 130 104
+210 4 2 54 3 129 93 46 102
+211 4 2 54 3 92 127 24 104
+212 4 2 54 3 102 100 54 122
+213 4 2 54 3 100 122 102 123
+214 4 2 54 3 54 101 102 122
+215 4 2 54 3 104 103 55 122
+216 4 2 54 3 51 103 124 97
+217 4 2 54 3 126 101 52 97
+218 4 2 54 3 105 130 56 123
+219 4 2 54 3 132 105 57 123
+220 4 2 54 3 98 104 55 122
+221 4 2 54 3 93 92 131 123
+222 4 2 54 3 93 128 92 123
+223 4 2 54 3 51 124 47 97
+224 4 2 54 3 52 48 126 97
+225 4 2 54 3 54 100 99 122
+226 4 2 54 3 104 122 98 123
+227 4 2 54 3 98 122 100 123
+228 4 2 54 3 54 99 101 122
+229 4 2 54 3 55 103 99 122
+230 4 2 54 3 49 130 56 105
+231 4 2 54 3 57 132 50 105
+232 4 2 54 3 55 99 98 122
+233 4 2 54 3 103 99 97 53
+234 4 2 54 3 101 15 97 53
+235 4 2 54 3 97 99 101 53
+236 4 2 54 3 105 100 102 123
+237 4 2 54 3 53 97 103 13
+238 4 2 54 3 103 97 99 122
+239 4 2 54 3 53 103 99 13
+240 4 2 54 3 15 101 99 53
+241 4 2 54 3 99 97 101 122
+242 4 2 54 3 105 104 98 123
+243 4 2 54 3 105 57 102 100
+244 4 2 54 3 98 105 56 104
+245 4 2 54 3 12 132 50 57
+246 4 2 54 3 56 6 130 49
+247 4 2 54 3 104 127 23 103
+248 4 2 54 3 129 45 101 102
+249 4 2 54 3 103 55 99 13
+250 4 2 54 3 15 99 101 54
+251 4 2 54 3 14 98 56 104
+252 4 2 54 3 52 101 15 97
+253 4 2 54 3 97 51 103 13
+254 4 2 54 3 105 56 104 123
+255 4 2 54 3 57 105 102 123
+256 4 2 54 3 56 105 98 14
+257 4 2 54 3 100 98 99 122
+258 4 2 54 3 57 12 132 46
+259 4 2 54 3 24 56 6 130
+260 4 2 54 3 105 16 57 100
+261 4 2 54 3 16 102 57 100
+262 4 2 54 3 126 11 48 52
+263 4 2 54 3 5 124 47 51
+264 4 2 54 3 58 98 105 14
+265 4 2 54 3 132 57 46 102
+266 4 2 54 3 24 56 130 104
+267 4 2 54 3 105 98 100 123
+268 4 2 54 3 100 105 16 58
+269 4 2 54 3 11 126 45 101
+270 4 2 54 3 23 124 5 103
+271 4 2 54 3 50 131 49 105
+272 4 2 54 3 45 129 46 102
+273 4 2 54 3 24 127 23 104
+274 4 2 54 3 54 102 16 100
+275 4 2 54 3 104 14 98 55
+276 4 2 54 3 124 5 103 51
+277 4 2 54 3 11 126 101 52
+278 4 2 54 3 105 100 98 58
+279 4 2 54 3 47 125 48 97
+280 4 2 54 3 93 128 129 91
+281 4 2 54 3 93 129 128 123
+282 4 2 54 3 122 129 128 91
+283 4 2 54 3 122 128 129 123
+284 4 2 54 3 122 128 127 90
+285 4 2 54 3 122 127 128 123
+286 4 2 54 3 92 127 128 90
+287 4 2 54 3 92 128 127 123
+288 4 2 54 3 91 125 126 48
+289 4 2 54 3 91 126 125 122
+290 4 2 54 3 97 126 125 48
+291 4 2 54 3 97 125 126 122
+292 4 2 54 3 97 125 124 47
+293 4 2 54 3 97 124 125 122
+294 4 2 54 3 90 124 125 47
+295 4 2 54 3 90 125 124 122
+296 4 2 54 3 105 131 132 50
+297 4 2 54 3 105 132 131 123
+298 4 2 54 3 93 132 131 50
+299 4 2 54 3 93 131 132 123
+300 4 2 54 3 92 131 130 49
+301 4 2 54 3 92 130 131 123
+302 4 2 54 3 105 130 131 49
+303 4 2 54 3 105 131 130 123
+304 5 2 52 1 21 22 59 60 77 79 106 108
+305 5 2 52 1 77 79 106 108 78 80 107 109
+306 5 2 52 1 78 80 107 109 33 34 83 84
+307 5 2 52 1 25 59 22 3 81 106 79 42
+308 5 2 52 1 81 106 79 42 82 107 80 43
+309 5 2 52 1 82 107 80 43 35 83 34 10
+310 5 2 52 1 59 17 18 62 106 67 69 112
+311 5 2 52 1 106 67 69 112 107 68 70 113
+312 5 2 52 1 107 68 70 113 83 28 29 86
+313 5 2 52 1 17 59 25 4 67 106 81 36
+314 5 2 52 1 67 106 81 36 68 107 82 37
+315 5 2 52 1 68 107 82 37 28 83 35 7
+316 5 2 52 1 26 61 62 63 73 110 112 114
+317 5 2 52 1 73 110 112 114 74 111 113 115
+318 5 2 52 1 74 111 113 115 31 85 86 87
+319 5 2 52 1 20 21 60 61 75 77 108 110
+320 5 2 52 1 75 77 108 110 76 78 109 111
+321 5 2 52 1 76 78 109 111 32 33 84 85
+322 5 2 52 1 19 63 62 18 71 114 112 69
+323 5 2 52 1 71 114 112 69 72 115 113 70
+324 5 2 52 1 72 115 113 70 30 87 86 29
+325 5 2 52 1 20 61 26 2 75 110 73 40
+326 5 2 52 1 75 110 73 40 76 111 74 41
+327 5 2 52 1 76 111 74 41 32 85 31 9
+328 5 2 52 1 59 62 61 60 106 112 110 108
+329 5 2 52 1 106 112 110 108 107 113 111 109
+330 5 2 52 1 107 113 111 109 83 86 85 84
+331 5 2 52 1 26 63 19 1 73 114 71 38
+332 5 2 52 1 73 114 71 38 74 115 72 39
+333 5 2 52 1 74 115 72 39 31 87 30 8
+334 6 2 53 2 27 20 2 88 75 40
+335 6 2 53 2 88 75 40 89 76 41
+336 6 2 53 2 89 76 41 44 32 9
+337 6 2 53 2 5 23 27 47 90 88
+338 6 2 53 2 47 90 88 48 91 89
+339 6 2 53 2 48 91 89 11 45 44
+340 6 2 53 2 24 64 23 92 116 90
+341 6 2 53 2 92 116 90 93 117 91
+342 6 2 53 2 93 117 91 46 94 45
+343 6 2 53 2 3 22 65 42 79 118
+344 6 2 53 2 42 79 118 43 80 119
+345 6 2 53 2 43 80 119 10 34 95
+346 6 2 53 2 66 23 64 120 90 116
+347 6 2 53 2 120 90 116 121 91 117
+348 6 2 53 2 121 91 117 96 45 94
+349 6 2 53 2 3 65 6 42 118 49
+350 6 2 53 2 42 118 49 43 119 50
+351 6 2 53 2 43 119 50 10 95 12
+352 6 2 53 2 66 64 21 120 116 77
+353 6 2 53 2 120 116 77 121 117 78
+354 6 2 53 2 121 117 78 96 94 33
+355 6 2 53 2 6 65 24 49 118 92
+356 6 2 53 2 49 118 92 50 119 93
+357 6 2 53 2 50 119 93 12 95 46
+358 6 2 53 2 64 24 65 116 92 118
+359 6 2 53 2 116 92 118 117 93 119
+360 6 2 53 2 117 93 119 94 46 95
+361 6 2 53 2 64 65 22 116 118 79
+362 6 2 53 2 116 118 79 117 119 80
+363 6 2 53 2 117 119 80 94 95 34
+364 6 2 53 2 27 66 20 88 120 75
+365 6 2 53 2 88 120 75 89 121 76
+366 6 2 53 2 89 121 76 44 96 32
+367 6 2 53 2 22 21 64 79 77 116
+368 6 2 53 2 79 77 116 80 78 117
+369 6 2 53 2 80 78 117 34 33 94
+370 6 2 53 2 21 20 66 77 75 120
+371 6 2 53 2 77 75 120 78 76 121
+372 6 2 53 2 78 76 121 33 32 96
+373 6 2 53 2 27 23 66 88 90 120
+374 6 2 53 2 88 90 120 89 91 121
+375 6 2 53 2 89 91 121 44 45 96
+376 7 2 54 3 5 23 90 47 124
+377 7 2 54 3 47 90 91 48 125
+378 7 2 54 3 48 91 45 11 126
+379 7 2 54 3 23 24 92 90 127
+380 7 2 54 3 90 92 93 91 128
+381 7 2 54 3 91 93 46 45 129
+382 7 2 54 3 24 6 49 92 130
+383 7 2 54 3 92 49 50 93 131
+384 7 2 54 3 93 50 12 46 132
+$EndElements
+$Periodic
+9
+0 7 4
+1
+7 4
+0 8 1
+1
+8 1
+1 12 1
+3
+28 17
+29 18
+30 19
+1 13 6
+1
+31 26
+1 15 4
+1
+35 25
+1 35 7
+1
+44 27
+1 37 5
+0
+2 32 1
+5
+86 62
+87 63
+83 59
+84 60
+85 61
+2 54 2
+3
+94 64
+95 65
+96 66
+$EndPeriodic
+)";
+  }
+  return std::dynamic_pointer_cast<const Mesh<Connectivity<3>>>(GmshReader{filename}.mesh());
+}
diff --git a/tests/MeshDataBaseForTests.hpp b/tests/MeshDataBaseForTests.hpp
index 589ec9b82f9be6451afd496aab3399c13bb3d718..79f3379f0cbfd51416594aa6015d66aa0b9f861c 100644
--- a/tests/MeshDataBaseForTests.hpp
+++ b/tests/MeshDataBaseForTests.hpp
@@ -9,10 +9,38 @@ class Connectivity;
 template <typename ConnectivityT>
 class Mesh;
 
+#include <array>
 #include <memory>
+#include <string>
 
 class MeshDataBaseForTests
 {
+ public:
+  template <size_t Dimension>
+  class NamedMesh
+  {
+   private:
+    const std::string m_name;
+    const std::shared_ptr<const Mesh<Connectivity<Dimension>>> m_mesh;
+
+   public:
+    NamedMesh(const std::string& name, const std::shared_ptr<const Mesh<Connectivity<Dimension>>>& mesh)
+      : m_name(name), m_mesh(mesh)
+    {}
+
+    const std::string&
+    name() const
+    {
+      return m_name;
+    }
+
+    auto
+    mesh() const
+    {
+      return m_mesh;
+    }
+  };
+
  private:
   explicit MeshDataBaseForTests();
 
@@ -22,12 +50,47 @@ class MeshDataBaseForTests
   std::shared_ptr<const Mesh<Connectivity<2>>> m_cartesian_2d_mesh;
   std::shared_ptr<const Mesh<Connectivity<3>>> m_cartesian_3d_mesh;
 
+  std::shared_ptr<const Mesh<Connectivity<1>>> m_unordered_1d_mesh;
+  std::shared_ptr<const Mesh<Connectivity<2>>> m_hybrid_2d_mesh;
+  std::shared_ptr<const Mesh<Connectivity<3>>> m_hybrid_3d_mesh;
+
+  std::shared_ptr<const Mesh<Connectivity<1>>> _buildUnordered1dMesh();
+  std::shared_ptr<const Mesh<Connectivity<2>>> _buildHybrid2dMesh();
+  std::shared_ptr<const Mesh<Connectivity<3>>> _buildHybrid3dMesh();
+
  public:
-  std::shared_ptr<const Mesh<Connectivity<1>>> cartesianMesh1D() const;
-  std::shared_ptr<const Mesh<Connectivity<2>>> cartesianMesh2D() const;
-  std::shared_ptr<const Mesh<Connectivity<3>>> cartesianMesh3D() const;
+  std::shared_ptr<const Mesh<Connectivity<1>>> cartesian1DMesh() const;
+  std::shared_ptr<const Mesh<Connectivity<1>>> unordered1DMesh() const;
+
+  std::shared_ptr<const Mesh<Connectivity<2>>> cartesian2DMesh() const;
+  std::shared_ptr<const Mesh<Connectivity<2>>> hybrid2DMesh() const;
+
+  std::shared_ptr<const Mesh<Connectivity<3>>> cartesian3DMesh() const;
+  std::shared_ptr<const Mesh<Connectivity<3>>> hybrid3DMesh() const;
 
   static const MeshDataBaseForTests& get();
+
+  auto
+  all1DMeshes() const
+  {
+    return std::array{NamedMesh{"cartesian 1d mesh", cartesian1DMesh()},   //
+                      NamedMesh{"unordered 1d mesh", unordered1DMesh()}};
+  }
+
+  auto
+  all2DMeshes() const
+  {
+    return std::array{NamedMesh{"cartesian 2d mesh", cartesian2DMesh()},   //
+                      NamedMesh{"hybrid 2d mesh", hybrid2DMesh()}};
+  }
+
+  auto
+  all3DMeshes() const
+  {
+    return std::array{NamedMesh{"cartesian 3d mesh", cartesian3DMesh()},   //
+                      NamedMesh{std::string("hybrid 3d mesh"), hybrid3DMesh()}};
+  }
+
   static void create();
   static void destroy();
 
diff --git a/tests/mpi_test_main.cpp b/tests/mpi_test_main.cpp
index 4c94634173799a17519b5d417bfc914a99e2e740..95b26e0a4964472335139a7afc0cdbf8205ff61f 100644
--- a/tests/mpi_test_main.cpp
+++ b/tests/mpi_test_main.cpp
@@ -2,6 +2,7 @@
 
 #include <Kokkos_Core.hpp>
 
+#include <analysis/QuadratureManager.hpp>
 #include <language/utils/OperatorRepository.hpp>
 #include <mesh/DiamondDualConnectivityManager.hpp>
 #include <mesh/DiamondDualMeshManager.hpp>
@@ -27,7 +28,7 @@ main(int argc, char* argv[])
 
   const std::string output_base_name{"mpi_test_rank_"};
 
-  std::filesystem::path parallel_output(std::string{PUGS_BINARY_DIR});
+  std::filesystem::path parallel_output = std::filesystem::path{PUGS_BINARY_DIR}.append("tests");
 
   std::filesystem::path gcov_prefix = [&]() -> std::filesystem::path {
     std::string template_temp_dir = std::filesystem::temp_directory_path() / "pugs_gcov_XXXXXX";
@@ -59,6 +60,7 @@ main(int argc, char* argv[])
 
       SynchronizerManager::create();
       RandomEngine::create();
+      QuadratureManager::create();
       MeshDataManager::create();
       DiamondDualConnectivityManager::create();
       DiamondDualMeshManager::create();
@@ -71,7 +73,7 @@ main(int argc, char* argv[])
                                     << rang::style::reset << '\n';
 
           for (size_t i_rank = 1; i_rank < parallel::size(); ++i_rank) {
-            std::filesystem::path parallel_output(std::string{PUGS_BINARY_DIR});
+            std::filesystem::path parallel_output = std::filesystem::path{PUGS_BINARY_DIR}.append("tests");
             parallel_output /= output_base_name + std::to_string(i_rank);
             session.config().stream() << " - " << rang::fg::green << parallel_output.parent_path().string()
                                       << parallel_output.preferred_separator << rang::style::reset << rang::fgB::green
@@ -91,6 +93,7 @@ main(int argc, char* argv[])
       DiamondDualMeshManager::destroy();
       DiamondDualConnectivityManager::destroy();
       MeshDataManager::destroy();
+      QuadratureManager::destroy();
       RandomEngine::destroy();
       SynchronizerManager::destroy();
     }
diff --git a/tests/test_Array.cpp b/tests/test_Array.cpp
index 5b9889f396f6413b4c704e48ffa1497bf6edb8fe..d6382d60699c670033a4ae64cd5970f6dcb86c21 100644
--- a/tests/test_Array.cpp
+++ b/tests/test_Array.cpp
@@ -1,6 +1,8 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_all.hpp>
 
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
 #include <utils/Array.hpp>
 #include <utils/PugsAssert.hpp>
 #include <utils/Types.hpp>
@@ -251,6 +253,72 @@ TEST_CASE("Array", "[utils]")
     REQUIRE(array_ost.str() == ref_ost.str());
   }
 
+  SECTION("checking for Array reductions")
+  {
+    Array<int> a(10);
+    a[0] = 13;
+    a[1] = 1;
+    a[2] = 8;
+    a[3] = -3;
+    a[4] = 23;
+    a[5] = -1;
+    a[6] = 13;
+    a[7] = 0;
+    a[8] = 12;
+    a[9] = 9;
+
+    SECTION("Min")
+    {
+      REQUIRE(min(a) == -3);
+    }
+
+    SECTION("Max")
+    {
+      REQUIRE(max(a) == 23);
+    }
+
+    SECTION("Sum")
+    {
+      REQUIRE((sum(a) == 75));
+    }
+
+    SECTION("TinyVector Sum")
+    {
+      using N2 = TinyVector<2, int>;
+      Array<N2> b(10);
+      b[0] = {13, 2};
+      b[1] = {1, 3};
+      b[2] = {8, -2};
+      b[3] = {-3, 2};
+      b[4] = {23, 4};
+      b[5] = {-1, -3};
+      b[6] = {13, 17};
+      b[7] = {0, 9};
+      b[8] = {12, 13};
+      b[9] = {9, -17};
+
+      REQUIRE((sum(b) == N2{75, 28}));
+    }
+
+    SECTION("TinyMatrix Sum")
+    {
+      using N22 = TinyMatrix<2, 2, int>;
+      Array<N22> b(10);
+      b[0] = {13, 2, 0, 1};
+      b[1] = {1, 3, 6, 3};
+      b[2] = {8, -2, -1, 21};
+      b[3] = {-3, 2, 5, 12};
+      b[4] = {23, 4, 7, 1};
+      b[5] = {-1, -3, 33, 11};
+      b[6] = {13, 17, 12, 13};
+      b[7] = {0, 9, 1, 14};
+      b[8] = {12, 13, -3, -71};
+      b[9] = {9, -17, 0, 16};
+
+      REQUIRE((sum(b) == N22{75, 28, 60, 21}));
+    }
+  }
+
 #ifndef NDEBUG
 
   SECTION("output with signaling NaN")
diff --git a/tests/test_ArrayUtils.cpp b/tests/test_ArrayUtils.cpp
deleted file mode 100644
index eb976b40df67c640bbe1a376671706835ca4c2e0..0000000000000000000000000000000000000000
--- a/tests/test_ArrayUtils.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-#include <catch2/catch_test_macros.hpp>
-#include <catch2/matchers/catch_matchers_all.hpp>
-
-#include <utils/Array.hpp>
-#include <utils/ArrayUtils.hpp>
-#include <utils/PugsAssert.hpp>
-
-#include <algebra/TinyMatrix.hpp>
-#include <algebra/TinyVector.hpp>
-
-// clazy:excludeall=non-pod-global-static
-
-// Instantiate to ensure full coverage is performed
-template class Array<int>;
-
-TEST_CASE("ArrayUtils", "[utils]")
-{
-  SECTION("checking for Array reductions")
-  {
-    Array<int> a(10);
-    a[0] = 13;
-    a[1] = 1;
-    a[2] = 8;
-    a[3] = -3;
-    a[4] = 23;
-    a[5] = -1;
-    a[6] = 13;
-    a[7] = 0;
-    a[8] = 12;
-    a[9] = 9;
-
-    SECTION("Min")
-    {
-      REQUIRE((min(a) == -3));
-    }
-
-    SECTION("Max")
-    {
-      REQUIRE((max(a) == 23));
-    }
-
-    SECTION("Sum")
-    {
-      REQUIRE((sum(a) == 75));
-    }
-
-    SECTION("TinyVector Sum")
-    {
-      using N2 = TinyVector<2, int>;
-      Array<N2> b(10);
-      b[0] = {13, 2};
-      b[1] = {1, 3};
-      b[2] = {8, -2};
-      b[3] = {-3, 2};
-      b[4] = {23, 4};
-      b[5] = {-1, -3};
-      b[6] = {13, 17};
-      b[7] = {0, 9};
-      b[8] = {12, 13};
-      b[9] = {9, -17};
-
-      REQUIRE((sum(b) == N2{75, 28}));
-    }
-
-    SECTION("TinyMatrix Sum")
-    {
-      using N22 = TinyMatrix<2, 2, int>;
-      Array<N22> b(10);
-      b[0] = {13, 2, 0, 1};
-      b[1] = {1, 3, 6, 3};
-      b[2] = {8, -2, -1, 21};
-      b[3] = {-3, 2, 5, 12};
-      b[4] = {23, 4, 7, 1};
-      b[5] = {-1, -3, 33, 11};
-      b[6] = {13, 17, 12, 13};
-      b[7] = {0, 9, 1, 14};
-      b[8] = {12, 13, -3, -71};
-      b[9] = {9, -17, 0, 16};
-
-      REQUIRE((sum(b) == N22{75, 28, 60, 21}));
-    }
-  }
-}
diff --git a/tests/test_BinaryExpressionProcessor_raw.cpp b/tests/test_BinaryExpressionProcessor_raw.cpp
index c115c67ca070c0610e5225c022fc527e4cff8a1a..22e32de89c715a5fa8e24a38007e0ab792e9dd19 100644
--- a/tests/test_BinaryExpressionProcessor_raw.cpp
+++ b/tests/test_BinaryExpressionProcessor_raw.cpp
@@ -3,6 +3,7 @@
 
 #include <language/node_processor/BinaryExpressionProcessor.hpp>
 #include <language/utils/OFStream.hpp>
+#include <utils/pugs_config.hpp>
 
 // clazy:excludeall=non-pod-global-static
 
@@ -51,8 +52,8 @@ TEST_CASE("BinaryExpressionProcessor raw operators", "[language]")
   REQUIRE(BinOp<language::divide_op>{}.eval(2.9, 3) == (2.9 / 3));
 
   {
-    std::filesystem::path path = std::filesystem::temp_directory_path();
-    path.append(std::string{"binary_expression_processor_shift_left_"} + std::to_string(getpid()));
+    std::filesystem::path path{PUGS_BINARY_DIR};
+    path.append("tests").append(std::string{"binary_expression_processor_shift_left_"} + std::to_string(getpid()));
 
     std::string filename = path.string();
 
diff --git a/tests/test_BinaryExpressionProcessor_shift.cpp b/tests/test_BinaryExpressionProcessor_shift.cpp
index 082d5fc5e829e11ac14e700e226f07278fca13c8..2f89f1f61c45ddcc137434d28f3196201c1ce866 100644
--- a/tests/test_BinaryExpressionProcessor_shift.cpp
+++ b/tests/test_BinaryExpressionProcessor_shift.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <test_BinaryExpressionProcessor_utils.hpp>
+#include <utils/pugs_config.hpp>
 
 #include <fstream>
 #include <unistd.h>
@@ -12,9 +13,8 @@ TEST_CASE("BinaryExpressionProcessor shift", "[language]")
 {
   SECTION("<<")
   {
-    std::filesystem::path path = std::filesystem::temp_directory_path();
-
-    path.append(std::string{"binary_expression_processor_"} + std::to_string(getpid()));
+    std::filesystem::path path{PUGS_BINARY_DIR};
+    path.append("tests").append(std::string{"binary_expression_processor_"} + std::to_string(getpid()));
 
     std::string filename = path.string();
 
diff --git a/tests/test_CastArray.cpp b/tests/test_CastArray.cpp
index d1c9cad2c1f7d2d39dad4e8fba8764d9b0bd6c2c..0f4a841f1e6bfc270fe789947efeafdccff40ff7 100644
--- a/tests/test_CastArray.cpp
+++ b/tests/test_CastArray.cpp
@@ -1,7 +1,6 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_all.hpp>
 
-#include <utils/ArrayUtils.hpp>
 #include <utils/CastArray.hpp>
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_CellIntegrator.cpp b/tests/test_CellIntegrator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..97bf0b98fa9beaace5bea6bf427b69dbe75548fc
--- /dev/null
+++ b/tests/test_CellIntegrator.cpp
@@ -0,0 +1,934 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussLobattoQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <mesh/DiamondDualMeshManager.hpp>
+#include <mesh/ItemValue.hpp>
+#include <mesh/Mesh.hpp>
+#include <scheme/CellIntegrator.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("CellIntegrator", "[scheme]")
+{
+  SECTION("1D")
+  {
+    using R1 = TinyVector<1>;
+
+    const auto mesh = MeshDataBaseForTests::get().unordered1DMesh();
+    auto f          = [](const R1& x) -> double { return x[0] * x[0] + 1; };
+
+    Array<const double> int_f_per_cell = [=] {
+      Array<double> int_f(mesh->numberOfCells());
+      auto cell_to_node_matrix = mesh->connectivity().cellToNodeMatrix();
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+          auto cell_node_list  = cell_to_node_matrix[cell_id];
+          auto xr              = mesh->xr();
+          const double x_left  = xr[cell_node_list[0]][0];
+          const double x_right = xr[cell_node_list[1]][0];
+          int_f[cell_id]       = 1. / 3 * (x_right * x_right * x_right - x_left * x_left * x_left) + x_right - x_left;
+        });
+
+      return int_f;
+    }();
+
+    SECTION("direct formula")
+    {
+      SECTION("all cells")
+      {
+        SECTION("CellValue")
+        {
+          CellValue<double> values(mesh->connectivity());
+          CellIntegrator::integrateTo([=](const R1 x) { return f(x); }, GaussQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("Array")
+        {
+          Array<double> values(mesh->numberOfCells());
+
+          CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<double> values(mesh->numberOfCells());
+          CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+
+      SECTION("cell list")
+      {
+        SECTION("Array")
+        {
+          Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+          {
+            size_t k = 0;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+              cell_list[k] = cell_id;
+            }
+
+            REQUIRE(k == cell_list.size());
+          }
+
+          Array<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, cell_list);
+
+          double error = 0;
+          for (size_t i = 0; i < cell_list.size(); ++i) {
+            error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+          {
+            size_t k = 0;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+              cell_list[k] = cell_id;
+            }
+
+            REQUIRE(k == cell_list.size());
+          }
+
+          SmallArray<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, cell_list);
+
+          double error = 0;
+          for (size_t i = 0; i < cell_list.size(); ++i) {
+            error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+    }
+
+    SECTION("tensorial formula")
+    {
+      SECTION("all cells")
+      {
+        SECTION("CellValue")
+        {
+          CellValue<double> values(mesh->connectivity());
+          CellIntegrator::integrateTo([=](const R1 x) { return f(x); }, GaussLobattoQuadratureDescriptor(2), *mesh,
+                                      values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("Array")
+        {
+          Array<double> values(mesh->numberOfCells());
+
+          CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<double> values(mesh->numberOfCells());
+          CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+
+      SECTION("cell list")
+      {
+        SECTION("Array")
+        {
+          Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+          {
+            size_t k = 0;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+              cell_list[k] = cell_id;
+            }
+
+            REQUIRE(k == cell_list.size());
+          }
+
+          Array<double> values = CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, cell_list);
+
+          double error = 0;
+          for (size_t i = 0; i < cell_list.size(); ++i) {
+            error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+          {
+            size_t k = 0;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+              cell_list[k] = cell_id;
+            }
+
+            REQUIRE(k == cell_list.size());
+          }
+
+          SmallArray<double> values =
+            CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, cell_list);
+
+          double error = 0;
+          for (size_t i = 0; i < cell_list.size(); ++i) {
+            error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+    }
+  }
+
+  SECTION("2D")
+  {
+    using R2 = TinyVector<2>;
+
+    const auto mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+
+    auto f = [](const R2& X) -> double {
+      const double x = X[0];
+      const double y = X[1];
+      return x * x + 2 * x * y + 3 * y * y + 2;
+    };
+
+    Array<const double> int_f_per_cell = [=] {
+      Array<double> int_f(mesh->numberOfCells());
+      auto cell_to_node_matrix = mesh->connectivity().cellToNodeMatrix();
+      auto cell_type           = mesh->connectivity().cellType();
+
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+          auto cell_node_list = cell_to_node_matrix[cell_id];
+          auto xr             = mesh->xr();
+          double integral     = 0;
+
+          switch (cell_type[cell_id]) {
+          case CellType::Triangle: {
+            TriangleTransformation<2> T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]]);
+            auto qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(4));
+
+            for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+              const auto& xi = qf.point(i);
+              integral += qf.weight(i) * T.jacobianDeterminant() * f(T(xi));
+            }
+            break;
+          }
+          case CellType::Quadrangle: {
+            SquareTransformation<2> T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]],
+                                      xr[cell_node_list[3]]);
+            auto qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(4));
+
+            for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+              const auto& xi = qf.point(i);
+              integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi));
+            }
+            break;
+          }
+          default: {
+            throw UnexpectedError("invalid cell type in 2d");
+          }
+          }
+          int_f[cell_id] = integral;
+        });
+
+      return int_f;
+    }();
+
+    SECTION("direct formula")
+    {
+      SECTION("all cells")
+      {
+        SECTION("CellValue")
+        {
+          CellValue<double> values(mesh->connectivity());
+          CellIntegrator::integrateTo([=](const R2 x) { return f(x); }, GaussQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("Array")
+        {
+          Array<double> values(mesh->numberOfCells());
+
+          CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<double> values(mesh->numberOfCells());
+          CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+
+      SECTION("cell list")
+      {
+        SECTION("Array")
+        {
+          Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+          {
+            size_t k = 0;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+              cell_list[k] = cell_id;
+            }
+
+            REQUIRE(k == cell_list.size());
+          }
+
+          Array<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, cell_list);
+
+          double error = 0;
+          for (size_t i = 0; i < cell_list.size(); ++i) {
+            error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+          {
+            size_t k = 0;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+              cell_list[k] = cell_id;
+            }
+
+            REQUIRE(k == cell_list.size());
+          }
+
+          SmallArray<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, cell_list);
+
+          double error = 0;
+          for (size_t i = 0; i < cell_list.size(); ++i) {
+            error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+    }
+
+    SECTION("tensorial formula")
+    {
+      SECTION("all cells")
+      {
+        SECTION("CellValue")
+        {
+          CellValue<double> values(mesh->connectivity());
+          CellIntegrator::integrateTo([=](const R2 x) { return f(x); }, GaussLobattoQuadratureDescriptor(2), *mesh,
+                                      values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("Array")
+        {
+          Array<double> values(mesh->numberOfCells());
+
+          CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<double> values(mesh->numberOfCells());
+          CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+
+      SECTION("cell list")
+      {
+        SECTION("Array")
+        {
+          Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+          {
+            size_t k = 0;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+              cell_list[k] = cell_id;
+            }
+
+            REQUIRE(k == cell_list.size());
+          }
+
+          Array<double> values = CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, cell_list);
+
+          double error = 0;
+          for (size_t i = 0; i < cell_list.size(); ++i) {
+            error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+          {
+            size_t k = 0;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+              cell_list[k] = cell_id;
+            }
+
+            REQUIRE(k == cell_list.size());
+          }
+
+          SmallArray<double> values =
+            CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, cell_list);
+
+          double error = 0;
+          for (size_t i = 0; i < cell_list.size(); ++i) {
+            error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+    }
+  }
+
+  SECTION("3D")
+  {
+    using R3 = TinyVector<3>;
+
+    auto hybrid_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+
+    auto f = [](const R3& X) -> double {
+      const double x = X[0];
+      const double y = X[1];
+      const double z = X[2];
+      return x * x + 2 * x * y + 3 * y * y + 2 * z * z - z + 1;
+    };
+
+    std::vector<std::pair<std::string, decltype(hybrid_mesh)>> mesh_list;
+    mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
+    mesh_list.push_back(
+      std::make_pair("diamond mesh", DiamondDualMeshManager::instance().getDiamondDualMesh(hybrid_mesh)));
+
+    for (auto mesh_info : mesh_list) {
+      auto mesh_name = mesh_info.first;
+      auto mesh      = mesh_info.second;
+
+      SECTION(mesh_name)
+      {
+        SECTION("direct formula")
+        {
+          Array<const double> int_f_per_cell = [=] {
+            Array<double> int_f(mesh->numberOfCells());
+            auto cell_to_node_matrix = mesh->connectivity().cellToNodeMatrix();
+            auto cell_type           = mesh->connectivity().cellType();
+
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                auto cell_node_list = cell_to_node_matrix[cell_id];
+                auto xr             = mesh->xr();
+                double integral     = 0;
+
+                switch (cell_type[cell_id]) {
+                case CellType::Tetrahedron: {
+                  TetrahedronTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]],
+                                              xr[cell_node_list[3]]);
+                  auto qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(4));
+
+                  for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                    const auto& xi = qf.point(i);
+                    integral += qf.weight(i) * T.jacobianDeterminant() * f(T(xi));
+                  }
+                  break;
+                }
+                case CellType::Pyramid: {
+                  PyramidTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]],
+                                          xr[cell_node_list[3]], xr[cell_node_list[4]]);
+                  auto qf = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(4));
+
+                  for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                    const auto& xi = qf.point(i);
+                    integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi));
+                  }
+                  break;
+                }
+                case CellType::Prism: {
+                  PrismTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]],
+                                        xr[cell_node_list[3]], xr[cell_node_list[4]], xr[cell_node_list[5]]);
+                  auto qf = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(4));
+
+                  for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                    const auto& xi = qf.point(i);
+                    integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi));
+                  }
+                  break;
+                }
+                case CellType::Hexahedron: {
+                  CubeTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]],
+                                       xr[cell_node_list[3]], xr[cell_node_list[4]], xr[cell_node_list[5]],
+                                       xr[cell_node_list[6]], xr[cell_node_list[7]]);
+                  auto qf = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(4));
+
+                  for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                    const auto& xi = qf.point(i);
+                    integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi));
+                  }
+                  break;
+                }
+                case CellType::Diamond: {
+                  if (cell_node_list.size() == 5) {
+                    auto qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(4));
+                    {   // top tetrahedron
+                      TetrahedronTransformation T0(xr[cell_node_list[1]], xr[cell_node_list[2]], xr[cell_node_list[3]],
+                                                   xr[cell_node_list[4]]);
+
+                      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                        const auto& xi = qf.point(i);
+                        integral += qf.weight(i) * T0.jacobianDeterminant() * f(T0(xi));
+                      }
+                    }
+                    {   // bottom tetrahedron
+                      TetrahedronTransformation T1(xr[cell_node_list[3]], xr[cell_node_list[2]], xr[cell_node_list[1]],
+                                                   xr[cell_node_list[0]]);
+
+                      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                        const auto& xi = qf.point(i);
+                        integral += qf.weight(i) * T1.jacobianDeterminant() * f(T1(xi));
+                      }
+                    }
+                  } else if (cell_node_list.size() == 6) {
+                    auto qf = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(4));
+                    {   // top pyramid
+                      PyramidTransformation T0(xr[cell_node_list[1]], xr[cell_node_list[2]], xr[cell_node_list[3]],
+                                               xr[cell_node_list[4]], xr[cell_node_list[5]]);
+
+                      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                        const auto& xi = qf.point(i);
+                        integral += qf.weight(i) * T0.jacobianDeterminant(xi) * f(T0(xi));
+                      }
+                    }
+                    {   // bottom pyramid
+                      PyramidTransformation T1(xr[cell_node_list[4]], xr[cell_node_list[3]], xr[cell_node_list[2]],
+                                               xr[cell_node_list[1]], xr[cell_node_list[0]]);
+
+                      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                        const auto& xi = qf.point(i);
+                        integral += qf.weight(i) * T1.jacobianDeterminant(xi) * f(T1(xi));
+                      }
+                    }
+                  } else {
+                    INFO("Diamond cells with more than 6 vertices are not tested");
+                    REQUIRE(false);
+                  }
+                  break;
+                }
+                default: {
+                  INFO("Diamond cells not tested yet");
+                  REQUIRE(cell_type[cell_id] != CellType::Diamond);
+                }
+                }
+                int_f[cell_id] = integral;
+              });
+
+            return int_f;
+          }();
+
+          SECTION("all cells")
+          {
+            SECTION("CellValue")
+            {
+              CellValue<double> values(mesh->connectivity());
+              CellIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussQuadratureDescriptor(4), *mesh,
+                                          values);
+
+              double error = 0;
+              for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+                error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+              }
+
+              REQUIRE(error == 0);
+            }
+
+            SECTION("Array")
+            {
+              Array<double> values(mesh->numberOfCells());
+
+              CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+                error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+              }
+
+              REQUIRE(error == 0);
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<double> values(mesh->numberOfCells());
+              CellIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+                error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+              }
+
+              REQUIRE(error == 0);
+            }
+          }
+
+          SECTION("cell list")
+          {
+            SECTION("Array")
+            {
+              Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+              {
+                size_t k = 0;
+                for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+                  cell_list[k] = cell_id;
+                }
+
+                REQUIRE(k == cell_list.size());
+              }
+
+              Array<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, cell_list);
+
+              double error = 0;
+              for (size_t i = 0; i < cell_list.size(); ++i) {
+                error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == 0);
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+              {
+                size_t k = 0;
+                for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+                  cell_list[k] = cell_id;
+                }
+
+                REQUIRE(k == cell_list.size());
+              }
+
+              SmallArray<double> values = CellIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, cell_list);
+
+              double error = 0;
+              for (size_t i = 0; i < cell_list.size(); ++i) {
+                error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == 0);
+            }
+          }
+        }
+
+        SECTION("tensorial formula")
+        {
+          Array<const double> int_f_per_cell = [=] {
+            Array<double> int_f(mesh->numberOfCells());
+            auto cell_to_node_matrix = mesh->connectivity().cellToNodeMatrix();
+            auto cell_type           = mesh->connectivity().cellType();
+
+            auto qf = QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(4));
+
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                auto cell_node_list = cell_to_node_matrix[cell_id];
+                auto xr             = mesh->xr();
+                double integral     = 0;
+
+                switch (cell_type[cell_id]) {
+                case CellType::Tetrahedron: {
+                  CubeTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]],
+                                       xr[cell_node_list[2]], xr[cell_node_list[3]], xr[cell_node_list[3]],
+                                       xr[cell_node_list[3]], xr[cell_node_list[3]]);
+
+                  for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                    const auto& xi = qf.point(i);
+                    integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi));
+                  }
+                  break;
+                }
+                case CellType::Pyramid: {
+                  CubeTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]],
+                                       xr[cell_node_list[3]], xr[cell_node_list[4]], xr[cell_node_list[4]],
+                                       xr[cell_node_list[4]], xr[cell_node_list[4]]);
+
+                  for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                    const auto& xi = qf.point(i);
+                    integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi));
+                  }
+                  break;
+                }
+                case CellType::Prism: {
+                  CubeTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]],
+                                       xr[cell_node_list[2]], xr[cell_node_list[3]], xr[cell_node_list[4]],
+                                       xr[cell_node_list[5]], xr[cell_node_list[5]]);
+                  for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                    const auto& xi = qf.point(i);
+                    integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi));
+                  }
+                  break;
+                }
+                case CellType::Hexahedron: {
+                  CubeTransformation T(xr[cell_node_list[0]], xr[cell_node_list[1]], xr[cell_node_list[2]],
+                                       xr[cell_node_list[3]], xr[cell_node_list[4]], xr[cell_node_list[5]],
+                                       xr[cell_node_list[6]], xr[cell_node_list[7]]);
+
+                  for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                    const auto& xi = qf.point(i);
+                    integral += qf.weight(i) * T.jacobianDeterminant(xi) * f(T(xi));
+                  }
+                  break;
+                }
+                case CellType::Diamond: {
+                  if (cell_node_list.size() == 5) {
+                    {   // top tetrahedron
+                      CubeTransformation T0(xr[cell_node_list[1]], xr[cell_node_list[2]], xr[cell_node_list[3]],
+                                            xr[cell_node_list[3]], xr[cell_node_list[4]], xr[cell_node_list[4]],
+                                            xr[cell_node_list[4]], xr[cell_node_list[4]]);
+
+                      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                        const auto& xi = qf.point(i);
+                        integral += qf.weight(i) * T0.jacobianDeterminant(xi) * f(T0(xi));
+                      }
+                    }
+                    {   // bottom tetrahedron
+                      CubeTransformation T1(xr[cell_node_list[3]], xr[cell_node_list[2]], xr[cell_node_list[1]],
+                                            xr[cell_node_list[1]], xr[cell_node_list[0]], xr[cell_node_list[0]],
+                                            xr[cell_node_list[0]], xr[cell_node_list[0]]);
+
+                      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                        const auto& xi = qf.point(i);
+                        integral += qf.weight(i) * T1.jacobianDeterminant(xi) * f(T1(xi));
+                      }
+                    }
+                  } else if (cell_node_list.size() == 6) {
+                    {   // top pyramid
+                      CubeTransformation T0(xr[cell_node_list[1]], xr[cell_node_list[2]], xr[cell_node_list[3]],
+                                            xr[cell_node_list[4]], xr[cell_node_list[5]], xr[cell_node_list[5]],
+                                            xr[cell_node_list[5]], xr[cell_node_list[5]]);
+
+                      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                        const auto& xi = qf.point(i);
+                        integral += qf.weight(i) * T0.jacobianDeterminant(xi) * f(T0(xi));
+                      }
+                    }
+                    {   // bottom pyramid
+                      CubeTransformation T1(xr[cell_node_list[4]], xr[cell_node_list[3]], xr[cell_node_list[2]],
+                                            xr[cell_node_list[1]], xr[cell_node_list[0]], xr[cell_node_list[0]],
+                                            xr[cell_node_list[0]], xr[cell_node_list[0]]);
+
+                      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                        const auto& xi = qf.point(i);
+                        integral += qf.weight(i) * T1.jacobianDeterminant(xi) * f(T1(xi));
+                      }
+                    }
+                  } else {
+                    INFO("Diamond cells with more than 6 vertices are not tested");
+                    REQUIRE(false);
+                  }
+                  break;
+                }
+                default: {
+                  INFO("Diamond cells not tested yet");
+                  REQUIRE(cell_type[cell_id] != CellType::Diamond);
+                }
+                }
+                int_f[cell_id] = integral;
+              });
+
+            return int_f;
+          }();
+
+          SECTION("all cells")
+          {
+            SECTION("CellValue")
+            {
+              CellValue<double> values(mesh->connectivity());
+              CellIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussLegendreQuadratureDescriptor(10),
+                                          *mesh, values);
+
+              auto cell_type = mesh->connectivity().cellType();
+              double error   = 0;
+              for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+                error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("Array")
+            {
+              Array<double> values(mesh->numberOfCells());
+
+              CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+                error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<double> values(mesh->numberOfCells());
+              CellIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+                error += std::abs(int_f_per_cell[cell_id] - values[cell_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+          }
+
+          SECTION("cell list")
+          {
+            SECTION("Array")
+            {
+              Array<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+              {
+                size_t k = 0;
+                for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+                  cell_list[k] = cell_id;
+                }
+
+                REQUIRE(k == cell_list.size());
+              }
+
+              Array<double> values =
+                CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, cell_list);
+
+              double error = 0;
+              for (size_t i = 0; i < cell_list.size(); ++i) {
+                error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<CellId> cell_list{mesh->numberOfCells() / 2 + mesh->numberOfCells() % 2};
+
+              {
+                size_t k = 0;
+                for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++(++cell_id), ++k) {
+                  cell_list[k] = cell_id;
+                }
+
+                REQUIRE(k == cell_list.size());
+              }
+
+              SmallArray<double> values =
+                CellIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, cell_list);
+
+              double error = 0;
+              for (size_t i = 0; i < cell_list.size(); ++i) {
+                error += std::abs(int_f_per_cell[cell_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_CubeGaussQuadrature.cpp b/tests/test_CubeGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1db30eb178be24be223f63b5f6a68aa441261909
--- /dev/null
+++ b/tests/test_CubeGaussQuadrature.cpp
@@ -0,0 +1,509 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <algebra/TinyMatrix.hpp>
+
+#include <analysis/CubeGaussQuadrature.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <utils/Exceptions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("CubeGaussQuadrature", "[analysis]")
+{
+  auto integrate = [](auto f, auto quadrature_formula) {
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    auto value = weight_list[0] * f(point_list[0]);
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(point_list[i]);
+    }
+
+    return value;
+  };
+
+  auto integrate_on_brick = [](auto f, auto quadrature_formula, const std::array<TinyVector<3>, 4>& tetrahedron) {
+    const auto& A = tetrahedron[0];
+    const auto& B = tetrahedron[1];
+    const auto& C = tetrahedron[2];
+    const auto& D = tetrahedron[3];
+
+    TinyMatrix<3> J;
+    for (size_t i = 0; i < 3; ++i) {
+      J(i, 0) = 0.5 * (B[i] - A[i]);
+      J(i, 1) = 0.5 * (C[i] - A[i]);
+      J(i, 2) = 0.5 * (D[i] - A[i]);
+    }
+    TinyVector s = 0.5 * (B + C + D - A);
+
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    auto value = weight_list[0] * f(J * (point_list[0]) + s);
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(J * (point_list[i]) + s);
+    }
+
+    return det(J) * value;
+  };
+
+  auto get_order = [&integrate, &integrate_on_brick](auto f, auto quadrature_formula, const double exact_value) {
+    using R3 = TinyVector<3>;
+
+    const double int_K_hat = integrate(f, quadrature_formula);
+    const double int_refined   //
+      = integrate_on_brick(f, quadrature_formula, {R3{-1, -1, -1}, R3{+0, -1, -1}, R3{-1, +0, -1}, R3{-1, -1, +0}}) +
+        integrate_on_brick(f, quadrature_formula, {R3{+0, -1, -1}, R3{+1, -1, -1}, R3{+0, +0, -1}, R3{+0, -1, +0}}) +
+        integrate_on_brick(f, quadrature_formula, {R3{-1, +0, -1}, R3{+0, +0, -1}, R3{-1, +1, -1}, R3{-1, +0, +0}}) +
+        integrate_on_brick(f, quadrature_formula, {R3{+0, +0, -1}, R3{+1, +0, -1}, R3{+0, +1, -1}, R3{+0, +0, +0}}) +
+        integrate_on_brick(f, quadrature_formula, {R3{-1, -1, +0}, R3{+0, -1, +0}, R3{-1, +0, +0}, R3{-1, -1, +1}}) +
+        integrate_on_brick(f, quadrature_formula, {R3{+0, -1, +0}, R3{+1, -1, +0}, R3{+0, +0, +0}, R3{+0, -1, +1}}) +
+        integrate_on_brick(f, quadrature_formula, {R3{-1, +0, +0}, R3{+0, +0, +0}, R3{-1, +1, +0}, R3{-1, +0, +1}}) +
+        integrate_on_brick(f, quadrature_formula, {R3{+0, +0, +0}, R3{+1, +0, +0}, R3{+0, +1, +0}, R3{+0, +0, +1}});
+
+    return -std::log((int_refined - exact_value) / (int_K_hat - exact_value)) / std::log(2);
+  };
+
+  auto p0 = [](const TinyVector<3>&) { return 4; };
+  auto p1 = [](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return 2 * x + 3 * y + z - 1;
+  };
+  auto p2 = [&p1](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p1(X) * (2.5 * x - 3 * y + z + 3);
+  };
+  auto p3 = [&p2](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p2(X) * (3 * x + 2 * y - 3 * z - 1);
+  };
+  auto p4 = [&p3](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p3(X) * (2 * x - 0.5 * y - 1.3 * z + 1);
+  };
+  auto p5 = [&p4](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p4(X) * (-0.1 * x + 1.3 * y - 3 * z + 1);
+  };
+  auto p6 = [&p5](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p5(X) * 7875. / 143443 * (2 * x - y + 4 * z + 1);
+  };
+  auto p7 = [&p6](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p6(X) * (0.7 * x - 2.7 * y + 1.3 * z - 2);
+  };
+  auto p8 = [&p7](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p7(X) * (0.3 * x + 1.2 * y - 0.7 * z + 0.2);
+  };
+  auto p9 = [&p8](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p8(X) * (-0.2 * x - 1.7 * y + 0.4 * z - 0.4);
+  };
+  auto p10 = [&p9](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p9(X) * (0.8 * x + 0.1 * y - 0.7 * z + 0.2);
+  };
+  auto p11 = [&p10](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p10(X) * (-0.6 * x - 0.5 * y + 0.3 * z - 0.1);
+  };
+  auto p12 = [&p11](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p11(X) * (0.4 * x - 0.7 * y - 0.6 * z + 0.7);
+  };
+  auto p13 = [&p12](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p12(X) * (-0.9 * x + 0.3 * y + 0.3 * z - 0.3);
+  };
+  auto p14 = [&p13](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p13(X) * (0.2 * x - 0.7 * y + 0.6 * z + 0.1);
+  };
+  auto p15 = [&p14](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p14(X) * (-0.5 * x - 0.3 * y + 0.7 * z - 0.4);
+  };
+  auto p16 = [&p15](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p15(X) * (0.7 * x + 0.6 * y - 0.1 * z + 0.6);
+  };
+  auto p17 = [&p16](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p16(X) * (-0.6 * x + 0.3 * y + 0.7 * z + 0.8);
+  };
+  auto p18 = [&p17](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p17(X) * (0.1 * x + 0.9 * y - 0.4 * z - 0.3);
+  };
+  auto p19 = [&p18](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p18(X) * (-0.8 * x - 0.3 * y + 0.9 * z + 0.8);
+  };
+  auto p20 = [&p19](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p19(X) * (0.3 * x - 0.7 * y - 0.8 * z + 0.7);
+  };
+  auto p21 = [&p20](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p20(X) * (-0.9 * x + 0.2 * y + 0.5 * z - 0.6);
+  };
+  auto p22 = [&p21](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p21(X) * (0.3 * x - 0.6 * y - 0.7 * z + 0.2);
+  };
+
+  SECTION("degree 0 and 1")
+  {
+    const QuadratureFormula<3>& l1 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(1));
+
+    REQUIRE(l1.numberOfPoints() == 1);
+
+    REQUIRE(integrate(p0, l1) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l1) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l1) != Catch::Approx(-32));
+
+    REQUIRE(get_order(p2, l1, -32) == Catch::Approx(2));
+  }
+
+  SECTION("degree 2 and 3")
+  {
+    const QuadratureFormula<3>& l2 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(2));
+    const QuadratureFormula<3>& l3 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(3));
+
+    REQUIRE(&l2 == &l3);
+
+    REQUIRE(l3.numberOfPoints() == 6);
+
+    REQUIRE(integrate(p0, l3) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l3) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l3) == Catch::Approx(-32));
+    REQUIRE(integrate(p3, l3) == Catch::Approx(108));
+    REQUIRE(integrate(p4, l3) != Catch::Approx(868. / 75));
+
+    REQUIRE(get_order(p4, l3, 868. / 75) == Catch::Approx(4));
+  }
+
+  SECTION("degree 4 and 5")
+  {
+    const QuadratureFormula<3>& l4 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(4));
+    const QuadratureFormula<3>& l5 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(5));
+
+    REQUIRE(&l4 == &l5);
+
+    REQUIRE(l5.numberOfPoints() == 14);
+
+    REQUIRE(integrate(p0, l5) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l5) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l5) == Catch::Approx(-32));
+    REQUIRE(integrate(p3, l5) == Catch::Approx(108));
+    REQUIRE(integrate(p4, l5) == Catch::Approx(868. / 75));
+    REQUIRE(integrate(p5, l5) == Catch::Approx(11176. / 225));
+    REQUIRE(integrate(p6, l5) != Catch::Approx(-34781. / 430329));
+
+    REQUIRE(get_order(p6, l5, -34781. / 430329) == Catch::Approx(6));
+  }
+
+  SECTION("degree 6 and 7")
+  {
+    const QuadratureFormula<3>& l6 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(6));
+    const QuadratureFormula<3>& l7 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(7));
+
+    REQUIRE(&l6 == &l7);
+
+    REQUIRE(l7.numberOfPoints() == 34);
+
+    REQUIRE(integrate(p0, l7) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l7) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l7) == Catch::Approx(-32));
+    REQUIRE(integrate(p3, l7) == Catch::Approx(108));
+    REQUIRE(integrate(p4, l7) == Catch::Approx(868. / 75));
+    REQUIRE(integrate(p5, l7) == Catch::Approx(11176. / 225));
+    REQUIRE(integrate(p6, l7) == Catch::Approx(-34781. / 430329));
+    REQUIRE(integrate(p7, l7) == Catch::Approx(-37338109. / 4303290));
+    REQUIRE(integrate(p8, l7) != Catch::Approx(422437099. / 21516450));
+
+    REQUIRE(get_order(p8, l7, 422437099. / 21516450) == Catch::Approx(8));
+  }
+
+  SECTION("degree 8 and 9")
+  {
+    const QuadratureFormula<3>& l8 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(8));
+    const QuadratureFormula<3>& l9 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(9));
+
+    REQUIRE(&l8 == &l9);
+
+    REQUIRE(l9.numberOfPoints() == 58);
+
+    REQUIRE(integrate(p0, l9) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l9) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l9) == Catch::Approx(-32));
+    REQUIRE(integrate(p3, l9) == Catch::Approx(108));
+    REQUIRE(integrate(p4, l9) == Catch::Approx(868. / 75));
+    REQUIRE(integrate(p5, l9) == Catch::Approx(11176. / 225));
+    REQUIRE(integrate(p6, l9) == Catch::Approx(-34781. / 430329));
+    REQUIRE(integrate(p7, l9) == Catch::Approx(-37338109. / 4303290));
+    REQUIRE(integrate(p8, l9) == Catch::Approx(422437099. / 21516450));
+    REQUIRE(integrate(p9, l9) == Catch::Approx(-7745999747. / 358607500));
+    REQUIRE(integrate(p10, l9) != Catch::Approx(-564286973089. / 14792559375));
+
+    REQUIRE(get_order(p10, l9, -564286973089. / 14792559375) == Catch::Approx(10));
+  }
+
+  SECTION("degree 10 and 11")
+  {
+    const QuadratureFormula<3>& l10 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(10));
+    const QuadratureFormula<3>& l11 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(11));
+
+    REQUIRE(&l10 == &l11);
+
+    REQUIRE(l11.numberOfPoints() == 90);
+
+    REQUIRE(integrate(p0, l11) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l11) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l11) == Catch::Approx(-32));
+    REQUIRE(integrate(p3, l11) == Catch::Approx(108));
+    REQUIRE(integrate(p4, l11) == Catch::Approx(868. / 75));
+    REQUIRE(integrate(p5, l11) == Catch::Approx(11176. / 225));
+    REQUIRE(integrate(p6, l11) == Catch::Approx(-34781. / 430329));
+    REQUIRE(integrate(p7, l11) == Catch::Approx(-37338109. / 4303290));
+    REQUIRE(integrate(p8, l11) == Catch::Approx(422437099. / 21516450));
+    REQUIRE(integrate(p9, l11) == Catch::Approx(-7745999747. / 358607500));
+    REQUIRE(integrate(p10, l11) == Catch::Approx(-564286973089. / 14792559375));
+    REQUIRE(integrate(p11, l11) == Catch::Approx(5047102242313. / 59170237500));
+    REQUIRE(integrate(p12, l11) != Catch::Approx(41226980237154884. / 504796088671875));
+
+    REQUIRE(get_order(p12, l11, 41226980237154884. / 504796088671875) == Catch::Approx(12));
+  }
+
+  SECTION("degree 12 and 13")
+  {
+    const QuadratureFormula<3>& l12 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(12));
+    const QuadratureFormula<3>& l13 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(13));
+
+    REQUIRE(&l12 == &l13);
+
+    REQUIRE(l13.numberOfPoints() == 154);
+
+    REQUIRE(integrate(p0, l13) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l13) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l13) == Catch::Approx(-32));
+    REQUIRE(integrate(p3, l13) == Catch::Approx(108));
+    REQUIRE(integrate(p4, l13) == Catch::Approx(868. / 75));
+    REQUIRE(integrate(p5, l13) == Catch::Approx(11176. / 225));
+    REQUIRE(integrate(p6, l13) == Catch::Approx(-34781. / 430329));
+    REQUIRE(integrate(p7, l13) == Catch::Approx(-37338109. / 4303290));
+    REQUIRE(integrate(p8, l13) == Catch::Approx(422437099. / 21516450));
+    REQUIRE(integrate(p9, l13) == Catch::Approx(-7745999747. / 358607500));
+    REQUIRE(integrate(p10, l13) == Catch::Approx(-564286973089. / 14792559375));
+    REQUIRE(integrate(p11, l13) == Catch::Approx(5047102242313. / 59170237500));
+    REQUIRE(integrate(p12, l13) == Catch::Approx(41226980237154884. / 504796088671875));
+    REQUIRE(integrate(p13, l13) == Catch::Approx(-2061220959094693133. / 26922458062500000));
+    REQUIRE(integrate(p14, l13) != Catch::Approx(9658346476244058223. / 134612290312500000));
+
+    REQUIRE(get_order(p14, l13, 9658346476244058223. / 134612290312500000) == Catch::Approx(14));
+  }
+
+  SECTION("degree 14 and 15")
+  {
+    const QuadratureFormula<3>& l14 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(14));
+    const QuadratureFormula<3>& l15 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(15));
+
+    REQUIRE(&l14 == &l15);
+
+    REQUIRE(l15.numberOfPoints() == 256);
+
+    REQUIRE(integrate(p0, l15) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l15) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l15) == Catch::Approx(-32));
+    REQUIRE(integrate(p3, l15) == Catch::Approx(108));
+    REQUIRE(integrate(p4, l15) == Catch::Approx(868. / 75));
+    REQUIRE(integrate(p5, l15) == Catch::Approx(11176. / 225));
+    REQUIRE(integrate(p6, l15) == Catch::Approx(-34781. / 430329));
+    REQUIRE(integrate(p7, l15) == Catch::Approx(-37338109. / 4303290));
+    REQUIRE(integrate(p8, l15) == Catch::Approx(422437099. / 21516450));
+    REQUIRE(integrate(p9, l15) == Catch::Approx(-7745999747. / 358607500));
+    REQUIRE(integrate(p10, l15) == Catch::Approx(-564286973089. / 14792559375));
+    REQUIRE(integrate(p11, l15) == Catch::Approx(5047102242313. / 59170237500));
+    REQUIRE(integrate(p12, l15) == Catch::Approx(41226980237154884. / 504796088671875));
+    REQUIRE(integrate(p13, l15) == Catch::Approx(-2061220959094693133. / 26922458062500000));
+    REQUIRE(integrate(p14, l15) == Catch::Approx(9658346476244058223. / 134612290312500000));
+    REQUIRE(integrate(p15, l15) == Catch::Approx(-74949066496612419191. / 673061451562500000.));
+    REQUIRE(integrate(p16, l15) != Catch::Approx(-46230170725718969828017. / 228840893531250000000.));
+
+    REQUIRE(get_order(p16, l15, -46230170725718969828017. / 228840893531250000000.) == Catch::Approx(16));
+  }
+
+  SECTION("degree 16 and 17")
+  {
+    const QuadratureFormula<3>& l16 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(16));
+    const QuadratureFormula<3>& l17 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(17));
+
+    REQUIRE(&l16 == &l17);
+
+    REQUIRE(l17.numberOfPoints() == 346);
+
+    REQUIRE(integrate(p0, l17) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l17) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l17) == Catch::Approx(-32));
+    REQUIRE(integrate(p3, l17) == Catch::Approx(108));
+    REQUIRE(integrate(p4, l17) == Catch::Approx(868. / 75));
+    REQUIRE(integrate(p5, l17) == Catch::Approx(11176. / 225));
+    REQUIRE(integrate(p6, l17) == Catch::Approx(-34781. / 430329));
+    REQUIRE(integrate(p7, l17) == Catch::Approx(-37338109. / 4303290));
+    REQUIRE(integrate(p8, l17) == Catch::Approx(422437099. / 21516450));
+    REQUIRE(integrate(p9, l17) == Catch::Approx(-7745999747. / 358607500));
+    REQUIRE(integrate(p10, l17) == Catch::Approx(-564286973089. / 14792559375));
+    REQUIRE(integrate(p11, l17) == Catch::Approx(5047102242313. / 59170237500));
+    REQUIRE(integrate(p12, l17) == Catch::Approx(41226980237154884. / 504796088671875));
+    REQUIRE(integrate(p13, l17) == Catch::Approx(-2061220959094693133. / 26922458062500000));
+    REQUIRE(integrate(p14, l17) == Catch::Approx(9658346476244058223. / 134612290312500000));
+    REQUIRE(integrate(p15, l17) == Catch::Approx(-74949066496612419191. / 673061451562500000.));
+    REQUIRE(integrate(p16, l17) == Catch::Approx(-46230170725718969828017. / 228840893531250000000.));
+    REQUIRE(integrate(p17, l17) == Catch::Approx(2946902633453348474221. / 190700744609375000000.));
+    REQUIRE(integrate(p18, l17) != Catch::Approx(904723313909284441962799. / 47555998186962890625000.));
+
+    REQUIRE(get_order(p18, l17, 904723313909284441962799. / 47555998186962890625000.) == Catch::Approx(18));
+  }
+
+  SECTION("degree 18 and 19")
+  {
+    const QuadratureFormula<3>& l18 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(18));
+    const QuadratureFormula<3>& l19 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(19));
+
+    REQUIRE(&l18 == &l19);
+
+    REQUIRE(l19.numberOfPoints() == 454);
+
+    REQUIRE(integrate(p0, l19) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l19) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l19) == Catch::Approx(-32));
+    REQUIRE(integrate(p3, l19) == Catch::Approx(108));
+    REQUIRE(integrate(p4, l19) == Catch::Approx(868. / 75));
+    REQUIRE(integrate(p5, l19) == Catch::Approx(11176. / 225));
+    REQUIRE(integrate(p6, l19) == Catch::Approx(-34781. / 430329));
+    REQUIRE(integrate(p7, l19) == Catch::Approx(-37338109. / 4303290));
+    REQUIRE(integrate(p8, l19) == Catch::Approx(422437099. / 21516450));
+    REQUIRE(integrate(p9, l19) == Catch::Approx(-7745999747. / 358607500));
+    REQUIRE(integrate(p10, l19) == Catch::Approx(-564286973089. / 14792559375));
+    REQUIRE(integrate(p11, l19) == Catch::Approx(5047102242313. / 59170237500));
+    REQUIRE(integrate(p12, l19) == Catch::Approx(41226980237154884. / 504796088671875));
+    REQUIRE(integrate(p13, l19) == Catch::Approx(-2061220959094693133. / 26922458062500000));
+    REQUIRE(integrate(p14, l19) == Catch::Approx(9658346476244058223. / 134612290312500000));
+    REQUIRE(integrate(p15, l19) == Catch::Approx(-74949066496612419191. / 673061451562500000.));
+    REQUIRE(integrate(p16, l19) == Catch::Approx(-46230170725718969828017. / 228840893531250000000.));
+    REQUIRE(integrate(p17, l19) == Catch::Approx(2946902633453348474221. / 190700744609375000000.));
+    REQUIRE(integrate(p18, l19) == Catch::Approx(904723313909284441962799. / 47555998186962890625000.));
+    REQUIRE(integrate(p19, l19) == Catch::Approx(-91477977618647751170958517. / 22826879129742187500000000.));
+    REQUIRE(integrate(p20, l19) != Catch::Approx(-11898262429946164522483495921. / 836985568090546875000000000.));
+
+    REQUIRE(get_order(p20, l19, -11898262429946164522483495921. / 836985568090546875000000000.) == Catch::Approx(20));
+  }
+
+  SECTION("degree 20 and 21")
+  {
+    const QuadratureFormula<3>& l20 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(20));
+    const QuadratureFormula<3>& l21 = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(21));
+
+    REQUIRE(&l20 == &l21);
+
+    REQUIRE(l21.numberOfPoints() == 580);
+
+    REQUIRE(integrate(p0, l21) == Catch::Approx(32));
+    REQUIRE(integrate(p1, l21) == Catch::Approx(-8));
+    REQUIRE(integrate(p2, l21) == Catch::Approx(-32));
+    REQUIRE(integrate(p3, l21) == Catch::Approx(108));
+    REQUIRE(integrate(p4, l21) == Catch::Approx(868. / 75));
+    REQUIRE(integrate(p5, l21) == Catch::Approx(11176. / 225));
+    REQUIRE(integrate(p6, l21) == Catch::Approx(-34781. / 430329));
+    REQUIRE(integrate(p7, l21) == Catch::Approx(-37338109. / 4303290));
+    REQUIRE(integrate(p8, l21) == Catch::Approx(422437099. / 21516450));
+    REQUIRE(integrate(p9, l21) == Catch::Approx(-7745999747. / 358607500));
+    REQUIRE(integrate(p10, l21) == Catch::Approx(-564286973089. / 14792559375));
+    REQUIRE(integrate(p11, l21) == Catch::Approx(5047102242313. / 59170237500));
+    REQUIRE(integrate(p12, l21) == Catch::Approx(41226980237154884. / 504796088671875));
+    REQUIRE(integrate(p13, l21) == Catch::Approx(-2061220959094693133. / 26922458062500000));
+    REQUIRE(integrate(p14, l21) == Catch::Approx(9658346476244058223. / 134612290312500000));
+    REQUIRE(integrate(p15, l21) == Catch::Approx(-74949066496612419191. / 673061451562500000.));
+    REQUIRE(integrate(p16, l21) == Catch::Approx(-46230170725718969828017. / 228840893531250000000.));
+    REQUIRE(integrate(p17, l21) == Catch::Approx(2946902633453348474221. / 190700744609375000000.));
+    REQUIRE(integrate(p18, l21) == Catch::Approx(904723313909284441962799. / 47555998186962890625000.));
+    REQUIRE(integrate(p19, l21) == Catch::Approx(-91477977618647751170958517. / 22826879129742187500000000.));
+    REQUIRE(integrate(p20, l21) == Catch::Approx(-11898262429946164522483495921. / 836985568090546875000000000.));
+    REQUIRE(integrate(p21, l21) == Catch::Approx(7694483338683700814691225463. / 224192562881396484375000000.));
+    REQUIRE(integrate(p22, l21) != Catch::Approx(10761048146311587678467825954981. / 481266701652064453125000000000.));
+
+    REQUIRE(get_order(p22, l21, 10761048146311587678467825954981. / 481266701652064453125000000000.) ==
+            Catch::Approx(22));
+  }
+
+  SECTION("max implemented degree")
+  {
+    REQUIRE(QuadratureManager::instance().maxCubeDegree(QuadratureType::Gauss) == CubeGaussQuadrature::max_degree);
+  }
+
+  SECTION("Access functions")
+  {
+    const QuadratureFormula<3>& quadrature_formula =
+      QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(7));
+
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    REQUIRE(point_list.size() == quadrature_formula.numberOfPoints());
+    REQUIRE(weight_list.size() == quadrature_formula.numberOfPoints());
+
+    for (size_t i = 0; i < quadrature_formula.numberOfPoints(); ++i) {
+      REQUIRE(&point_list[i] == &quadrature_formula.point(i));
+      REQUIRE(&weight_list[i] == &quadrature_formula.weight(i));
+    }
+  }
+}
diff --git a/tests/test_CubeTransformation.cpp b/tests/test_CubeTransformation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7d060ccd6faf409fb08813607209da1d9da616b1
--- /dev/null
+++ b/tests/test_CubeTransformation.cpp
@@ -0,0 +1,298 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussLobattoQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/CubeTransformation.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("CubeTransformation", "[geometry]")
+{
+  using R3 = TinyVector<3>;
+
+  SECTION("invertible transformation")
+  {
+    const R3 a_hat = {-1, -1, -1};
+    const R3 b_hat = {+1, -1, -1};
+    const R3 c_hat = {+1, +1, -1};
+    const R3 d_hat = {-1, +1, -1};
+    const R3 e_hat = {-1, -1, +1};
+    const R3 f_hat = {+1, -1, +1};
+    const R3 g_hat = {+1, +1, +1};
+    const R3 h_hat = {-1, +1, +1};
+
+    const R3 m_hat = zero;
+
+    const R3 a = {0, 0, 0};
+    const R3 b = {3, 1, 3};
+    const R3 c = {2, 5, 2};
+    const R3 d = {0, 3, 1};
+    const R3 e = {1, 2, 5};
+    const R3 f = {3, 1, 7};
+    const R3 g = {2, 5, 5};
+    const R3 h = {0, 3, 6};
+
+    const R3 m = 0.125 * (a + b + c + d + e + f + g + h);
+
+    const CubeTransformation t(a, b, c, d, e, f, g, h);
+
+    SECTION("values")
+    {
+      REQUIRE(l2Norm(t(a_hat) - a) == Catch::Approx(0));
+      REQUIRE(l2Norm(t(b_hat) - b) == Catch::Approx(0));
+      REQUIRE(l2Norm(t(c_hat) - c) == Catch::Approx(0));
+      REQUIRE(l2Norm(t(d_hat) - d) == Catch::Approx(0));
+      REQUIRE(l2Norm(t(e_hat) - e) == Catch::Approx(0));
+      REQUIRE(l2Norm(t(f_hat) - f) == Catch::Approx(0));
+      REQUIRE(l2Norm(t(g_hat) - g) == Catch::Approx(0));
+      REQUIRE(l2Norm(t(h_hat) - h) == Catch::Approx(0));
+
+      REQUIRE(l2Norm(t(m_hat) - m) == Catch::Approx(0));
+    }
+
+    SECTION("Jacobian determinant")
+    {
+      SECTION("at points")
+      {
+        auto detJ = [](const R3& X) {
+          const double x = X[0];
+          const double y = X[1];
+          const double z = X[2];
+
+          return -(3 * x * y * y * z + 9 * y * y * z - 6 * x * x * y * z + 6 * x * y * z - 96 * y * z + 14 * x * x * z -
+                   49 * x * z + 119 * z - 21 * x * y * y + 21 * y * y + 90 * x * y - 10 * y + 40 * x * x - 109 * x -
+                   491) /
+                 128;
+        };
+
+        REQUIRE(t.jacobianDeterminant(a_hat) == Catch::Approx(detJ(a_hat)));
+        REQUIRE(t.jacobianDeterminant(b_hat) == Catch::Approx(detJ(b_hat)));
+        REQUIRE(t.jacobianDeterminant(c_hat) == Catch::Approx(detJ(c_hat)));
+        REQUIRE(t.jacobianDeterminant(d_hat) == Catch::Approx(detJ(d_hat)));
+        REQUIRE(t.jacobianDeterminant(e_hat) == Catch::Approx(detJ(e_hat)));
+        REQUIRE(t.jacobianDeterminant(f_hat) == Catch::Approx(detJ(f_hat)));
+        REQUIRE(t.jacobianDeterminant(g_hat) == Catch::Approx(detJ(g_hat)));
+        REQUIRE(t.jacobianDeterminant(h_hat) == Catch::Approx(detJ(h_hat)));
+
+        REQUIRE(t.jacobianDeterminant(m_hat) == Catch::Approx(detJ(m_hat)));
+      }
+
+      SECTION("Gauss order 3")
+      {
+        // The jacobian determinant is of maximal degree 2 in each variable
+        const QuadratureFormula<3>& gauss =
+          QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(2));
+
+        double volume = 0;
+        for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+          volume += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i));
+        }
+
+        // 353. / 12 is actually the volume of the hexahedron
+        REQUIRE(volume == Catch::Approx(353. / 12));
+      }
+
+      auto p = [](const R3& X) {
+        const double& x = X[0];
+        const double& y = X[1];
+        const double& z = X[2];
+
+        return 2 * x * x + 3 * y * z + z * z + 2 * x - 3 * y + 0.5 * z + 3;
+      };
+
+      SECTION("Gauss order 6")
+      {
+        // Jacbian determinant is a degree 2 polynomial, so the
+        // following formula is required to reach exactness
+        const QuadratureFormula<3>& gauss = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(6));
+
+        double integral = 0;
+        for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+          integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i)));
+        }
+
+        REQUIRE(integral == Catch::Approx(488879. / 360));
+      }
+
+      SECTION("Gauss-Legendre order 4")
+      {
+        // Jacbian determinant is a degree 2 polynomial, so the
+        // following formula is required to reach exactness
+        const QuadratureFormula<3>& gauss =
+          QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(4));
+
+        double integral = 0;
+        for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+          integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i)));
+        }
+
+        REQUIRE(integral == Catch::Approx(488879. / 360));
+      }
+
+      SECTION("Gauss-Lobatto order 4")
+      {
+        // Jacbian determinant is a degree 2 polynomial, so the
+        // following formula is required to reach exactness
+        const QuadratureFormula<3>& gauss =
+          QuadratureManager::instance().getCubeFormula(GaussLobattoQuadratureDescriptor(4));
+
+        double integral = 0;
+        for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+          integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i)));
+        }
+
+        REQUIRE(integral == Catch::Approx(488879. / 360));
+      }
+    }
+  }
+
+  SECTION("degenerate to tetrahedron")
+  {
+    using R3 = TinyVector<3>;
+
+    const R3 a = {1, 2, 1};
+    const R3 b = {3, 1, 3};
+    const R3 c = {2, 5, 2};
+    const R3 d = {2, 3, 4};
+
+    const CubeTransformation t(a, b, c, c, d, d, d, d);
+
+    auto p = [](const R3& X) {
+      const double x = X[0];
+      const double y = X[1];
+      const double z = X[2];
+      return 2 * x * x + 3 * x * y + y * y + 3 * y - z * z + 2 * x * z + 2 * z;
+    };
+
+    SECTION("Polynomial Gauss integral")
+    {
+      QuadratureFormula<3> qf = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(6));
+
+      double sum = 0;
+      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+        const R3 xi = qf.point(i);
+        sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi));
+      }
+
+      REQUIRE(sum == Catch::Approx(231. / 2));
+    }
+
+    SECTION("Polynomial Gauss-Lobatto integral")
+    {
+      QuadratureFormula<3> qf = QuadratureManager::instance().getCubeFormula(GaussLobattoQuadratureDescriptor(4));
+
+      double sum = 0;
+      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+        const R3 xi = qf.point(i);
+        sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi));
+      }
+
+      REQUIRE(sum == Catch::Approx(231. / 2));
+    }
+
+    SECTION("Polynomial Gauss-Legendre integral")
+    {
+      QuadratureFormula<3> qf = QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(4));
+
+      double sum = 0;
+      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+        const R3 xi = qf.point(i);
+        sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi));
+      }
+
+      REQUIRE(sum == Catch::Approx(231. / 2));
+    }
+  }
+
+  SECTION("degenerate to prism")
+  {
+    const R3 a = {1, 2, 0};
+    const R3 b = {3, 1, 3};
+    const R3 c = {2, 5, 2};
+    const R3 d = {0, 3, 1};
+    const R3 e = {1, 2, 5};
+    const R3 f = {3, 1, 7};
+
+    const CubeTransformation t(a, b, c, c, d, e, f, f);
+
+    auto p = [](const R3& X) {
+      const double x = X[0];
+      const double y = X[1];
+      const double z = X[2];
+
+      return 3 * x * x + 2 * y * y + 3 * z * z + 4 * x + 3 * y + 2 * z + 1;
+    };
+
+    SECTION("Polynomial Gauss integral")
+    {
+      // 8 is the minimum quadrature rule to integrate the polynomial
+      // on the prism due to the jacobian determinant expression
+      const QuadratureFormula<3>& gauss = QuadratureManager::instance().getCubeFormula(GaussQuadratureDescriptor(8));
+
+      double integral = 0;
+      for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+        integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i)));
+      }
+
+      REQUIRE(integral == Catch::Approx(30377. / 90));
+    }
+
+    SECTION("Polynomial Gauss-Legendre integral")
+    {
+      const QuadratureFormula<3>& gauss =
+        QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(4));
+
+      double integral = 0;
+      for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+        integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i)));
+      }
+
+      REQUIRE(integral == Catch::Approx(30377. / 90));
+    }
+
+    SECTION("Polynomial Gauss-Lobatto integral")
+    {
+      const QuadratureFormula<3>& gauss =
+        QuadratureManager::instance().getCubeFormula(GaussLobattoQuadratureDescriptor(4));
+
+      double integral = 0;
+      for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+        integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i)));
+      }
+
+      REQUIRE(integral == Catch::Approx(30377. / 90));
+    }
+  }
+
+  SECTION("degenerate to pyramid")
+  {
+    const R3 a = {1, 2, 0};
+    const R3 b = {3, 1, 3};
+    const R3 c = {2, 5, 2};
+    const R3 d = {0, 3, 1};
+    const R3 e = {1, 2, 5};
+
+    const CubeTransformation t(a, b, c, d, e, e, e, e);
+
+    auto p = [](const R3& X) {
+      const double x = X[0];
+      const double y = X[1];
+      const double z = X[2];
+
+      return 3 * x * x + 2 * y * y + 3 * z * z + 4 * x + 3 * y + 2 * z + 1;
+    };
+
+    // 4 is the minimum quadrature rule to integrate the polynomial on the pyramid
+    const QuadratureFormula<3>& gauss =
+      QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(20));
+    double integral = 0;
+    for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+      integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i)));
+    }
+
+    REQUIRE(integral == Catch::Approx(7238. / 15));
+  }
+}
diff --git a/tests/test_DiscreteFunctionInterpoler.cpp b/tests/test_DiscreteFunctionInterpoler.cpp
index d24bae5f065ba88afd225268c425b92f9211bcf5..809ca559ca254cbbb5d90c63f8dcd943a5df23c8 100644
--- a/tests/test_DiscreteFunctionInterpoler.cpp
+++ b/tests/test_DiscreteFunctionInterpoler.cpp
@@ -43,10 +43,16 @@ TEST_CASE("DiscreteFunctionInterpoler", "[scheme]")
   {
     constexpr size_t Dimension = 1;
 
-    const auto& mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
-    auto xj             = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+    std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    std::string_view data = R"(
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_1d = named_mesh.mesh();
+
+        auto xj = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+
+        std::string_view data = R"(
 import math;
 let B_scalar_non_linear_1d: R^1 -> B, x -> (exp(2 * x[0]) + 3 > 4);
 let N_scalar_non_linear_1d: R^1 -> N, x -> floor(3 * x[0] * x[0] + 2);
@@ -59,274 +65,276 @@ let R1x1_non_linear_1d: R^1 -> R^1x1, x -> (2 * exp(x[0]) * sin(x[0]) + 3);
 let R2x2_non_linear_1d: R^1 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]);
 let R3x3_non_linear_1d: R^1 -> R^3x3, x -> (2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0], -4*x[0], 2*x[0]+1, 3, -6*x[0], exp(x[0]));
 )";
-    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-    auto ast = ASTBuilder::build(input);
-
-    ASTModulesImporter{*ast};
-    ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-    ASTSymbolTableBuilder{*ast};
-    ASTNodeDataTypeBuilder{*ast};
-
-    ASTNodeTypeCleaner<language::var_declaration>{*ast};
-    ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-    ASTNodeExpressionBuilder{*ast};
-
-    std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
-
-    TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-    position.byte = data.size();   // ensure that variables are declared at this point
-
-    SECTION("B_scalar_non_linear_1d")
-    {
-      auto [i_symbol, found] = symbol_table->find("B_scalar_non_linear_1d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::exp(2 * x[0]) + 3 > 4;
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("N_scalar_non_linear_1d")
-    {
-      auto [i_symbol, found] = symbol_table->find("N_scalar_non_linear_1d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(3 * x[0] * x[0] + 2);
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("Z_scalar_non_linear_1d")
-    {
-      auto [i_symbol, found] = symbol_table->find("Z_scalar_non_linear_1d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(std::exp(2 * x[0]) - 1);
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("R_scalar_non_linear_1d")
-    {
-      auto [i_symbol, found] = symbol_table->find("R_scalar_non_linear_1d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = 2 * std::exp(x[0]) + 3;
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-    SECTION("R1_non_linear_1d")
-    {
-      using DataType = TinyVector<1>;
-
-      auto [i_symbol, found] = symbol_table->find("R1_non_linear_1d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0])};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
+        ASTSymbolTableBuilder{*ast};
+        ASTNodeDataTypeBuilder{*ast};
+
+        ASTNodeTypeCleaner<language::var_declaration>{*ast};
+        ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+        ASTNodeExpressionBuilder{*ast};
+
+        std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+        TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+        position.byte = data.size();   // ensure that variables are declared at this point
 
-    SECTION("R2_non_linear_1d")
-    {
-      using DataType = TinyVector<2>;
+        SECTION("B_scalar_non_linear_1d")
+        {
+          auto [i_symbol, found] = symbol_table->find("B_scalar_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::exp(2 * x[0]) + 3 > 4;
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("N_scalar_non_linear_1d")
+        {
+          auto [i_symbol, found] = symbol_table->find("N_scalar_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(3 * x[0] * x[0] + 2);
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("Z_scalar_non_linear_1d")
+        {
+          auto [i_symbol, found] = symbol_table->find("Z_scalar_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-      auto [i_symbol, found] = symbol_table->find("R2_non_linear_1d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0]), -3 * x[0]};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R3_non_linear_1d")
-    {
-      using DataType = TinyVector<3>;
-
-      auto [i_symbol, found] = symbol_table->find("R3_non_linear_1d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0]) + 3, x[0] - 2, 3};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R1x1_non_linear_1d")
-    {
-      using DataType = TinyMatrix<1>;
-
-      auto [i_symbol, found] = symbol_table->find("R1x1_non_linear_1d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0]) * std::sin(x[0]) + 3};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R2x2_non_linear_1d")
-    {
-      using DataType = TinyMatrix<2>;
-
-      auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_1d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id] =
-            DataType{2 * std::exp(x[0]) * std::sin(x[0]) + 3, std::sin(x[0] - 2 * x[0]), 3, x[0] * x[0]};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R3x3_non_linear_1d")
-    {
-      using DataType = TinyMatrix<3>;
-
-      auto [i_symbol, found] = symbol_table->find("R3x3_non_linear_1d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * exp(x[0]) * std::sin(x[0]) + 3,
-                                         std::sin(x[0] - 2 * x[0]),
-                                         3,
-                                         x[0] * x[0],
-                                         -4 * x[0],
-                                         2 * x[0] + 1,
-                                         3,
-                                         -6 * x[0],
-                                         std::exp(x[0])};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(std::exp(2 * x[0]) - 1);
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("R_scalar_non_linear_1d")
+        {
+          auto [i_symbol, found] = symbol_table->find("R_scalar_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = 2 * std::exp(x[0]) + 3;
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("R1_non_linear_1d")
+        {
+          using DataType = TinyVector<1>;
+
+          auto [i_symbol, found] = symbol_table->find("R1_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0])};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R2_non_linear_1d")
+        {
+          using DataType = TinyVector<2>;
+
+          auto [i_symbol, found] = symbol_table->find("R2_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]), -3 * x[0]};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R3_non_linear_1d")
+        {
+          using DataType = TinyVector<3>;
+
+          auto [i_symbol, found] = symbol_table->find("R3_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]) + 3, x[0] - 2, 3};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R1x1_non_linear_1d")
+        {
+          using DataType = TinyMatrix<1>;
+
+          auto [i_symbol, found] = symbol_table->find("R1x1_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]) * std::sin(x[0]) + 3};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R2x2_non_linear_1d")
+        {
+          using DataType = TinyMatrix<2>;
+
+          auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id] =
+                DataType{2 * std::exp(x[0]) * std::sin(x[0]) + 3, std::sin(x[0] - 2 * x[0]), 3, x[0] * x[0]};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R3x3_non_linear_1d")
+        {
+          using DataType = TinyMatrix<3>;
+
+          auto [i_symbol, found] = symbol_table->find("R3x3_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * exp(x[0]) * std::sin(x[0]) + 3,
+                                             std::sin(x[0] - 2 * x[0]),
+                                             3,
+                                             x[0] * x[0],
+                                             -4 * x[0],
+                                             2 * x[0] + 1,
+                                             3,
+                                             -6 * x[0],
+                                             std::exp(x[0])};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+      }
     }
   }
 
@@ -334,10 +342,16 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> (2 * exp(x[0]) * sin(x[0]) + 3, sin(x
   {
     constexpr size_t Dimension = 2;
 
-    const auto& mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
-    auto xj             = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    std::string_view data = R"(
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
+
+        auto xj = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+
+        std::string_view data = R"(
 import math;
 let B_scalar_non_linear_2d: R^2 -> B, x -> (exp(2 * x[0])< 2*x[1]);
 let N_scalar_non_linear_2d: R^2 -> N, x -> floor(3 * (x[0] + x[1]) * (x[0] + x[1]) + 2);
@@ -350,275 +364,277 @@ let R1x1_non_linear_2d: R^2 -> R^1x1, x -> (2 * exp(x[0]) * sin(x[1]) + 3);
 let R2x2_non_linear_2d: R^2 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[1]) + 3, sin(x[1] - 2 * x[0]), 3, x[1] * x[0]);
 let R3x3_non_linear_2d: R^2 -> R^3x3, x -> (2 * exp(x[0]) * sin(x[1]) + 3, sin(x[1] - 2 * x[0]), 3, x[1] * x[0], -4*x[1], 2*x[0]+1, 3, -6*x[0], exp(x[1]));
 )";
-    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-    auto ast = ASTBuilder::build(input);
-
-    ASTModulesImporter{*ast};
-    ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-    ASTSymbolTableBuilder{*ast};
-    ASTNodeDataTypeBuilder{*ast};
-
-    ASTNodeTypeCleaner<language::var_declaration>{*ast};
-    ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-    ASTNodeExpressionBuilder{*ast};
-
-    std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
-
-    TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-    position.byte = data.size();   // ensure that variables are declared at this point
-
-    SECTION("B_scalar_non_linear_2d")
-    {
-      auto [i_symbol, found] = symbol_table->find("B_scalar_non_linear_2d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::exp(2 * x[0]) < 2 * x[1];
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("N_scalar_non_linear_2d")
-    {
-      auto [i_symbol, found] = symbol_table->find("N_scalar_non_linear_2d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(3 * (x[0] + x[1]) * (x[0] + x[1]) + 2);
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("Z_scalar_non_linear_2d")
-    {
-      auto [i_symbol, found] = symbol_table->find("Z_scalar_non_linear_2d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(std::exp(2 * x[0]) - 3 * x[1]);
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("R_scalar_non_linear_2d")
-    {
-      auto [i_symbol, found] = symbol_table->find("R_scalar_non_linear_2d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = 2 * std::exp(x[0]) + 3 * x[1];
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("R1_non_linear_2d")
-    {
-      using DataType = TinyVector<1>;
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-      auto [i_symbol, found] = symbol_table->find("R1_non_linear_2d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+        ASTSymbolTableBuilder{*ast};
+        ASTNodeDataTypeBuilder{*ast};
+
+        ASTNodeTypeCleaner<language::var_declaration>{*ast};
+        ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+        ASTNodeExpressionBuilder{*ast};
+
+        std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+        TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+        position.byte = data.size();   // ensure that variables are declared at this point
 
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+        SECTION("B_scalar_non_linear_2d")
+        {
+          auto [i_symbol, found] = symbol_table->find("B_scalar_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::exp(2 * x[0]) < 2 * x[1];
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("N_scalar_non_linear_2d")
+        {
+          auto [i_symbol, found] = symbol_table->find("N_scalar_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(3 * (x[0] + x[1]) * (x[0] + x[1]) + 2);
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("Z_scalar_non_linear_2d")
+        {
+          auto [i_symbol, found] = symbol_table->find("Z_scalar_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-      CellValue<DataType> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0])};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R2_non_linear_2d")
-    {
-      using DataType = TinyVector<2>;
-
-      auto [i_symbol, found] = symbol_table->find("R2_non_linear_2d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0]), -3 * x[1]};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R3_non_linear_2d")
-    {
-      using DataType = TinyVector<3>;
-
-      auto [i_symbol, found] = symbol_table->find("R3_non_linear_2d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0]) + 3, x[1] - 2, 3};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R1x1_non_linear_2d")
-    {
-      using DataType = TinyMatrix<1>;
-
-      auto [i_symbol, found] = symbol_table->find("R1x1_non_linear_2d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R2x2_non_linear_2d")
-    {
-      using DataType = TinyMatrix<2>;
-
-      auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_2d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id] =
-            DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3, std::sin(x[1] - 2 * x[0]), 3, x[1] * x[0]};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R3x3_non_linear_2d")
-    {
-      using DataType = TinyMatrix<3>;
-
-      auto [i_symbol, found] = symbol_table->find("R3x3_non_linear_2d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-
-          cell_value[cell_id] = DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3,
-                                         std::sin(x[1] - 2 * x[0]),
-                                         3,
-                                         x[1] * x[0],
-                                         -4 * x[1],
-                                         2 * x[0] + 1,
-                                         3,
-                                         -6 * x[0],
-                                         std::exp(x[1])};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(std::exp(2 * x[0]) - 3 * x[1]);
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("R_scalar_non_linear_2d")
+        {
+          auto [i_symbol, found] = symbol_table->find("R_scalar_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = 2 * std::exp(x[0]) + 3 * x[1];
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("R1_non_linear_2d")
+        {
+          using DataType = TinyVector<1>;
+
+          auto [i_symbol, found] = symbol_table->find("R1_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0])};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R2_non_linear_2d")
+        {
+          using DataType = TinyVector<2>;
+
+          auto [i_symbol, found] = symbol_table->find("R2_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]), -3 * x[1]};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R3_non_linear_2d")
+        {
+          using DataType = TinyVector<3>;
+
+          auto [i_symbol, found] = symbol_table->find("R3_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]) + 3, x[1] - 2, 3};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R1x1_non_linear_2d")
+        {
+          using DataType = TinyMatrix<1>;
+
+          auto [i_symbol, found] = symbol_table->find("R1x1_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R2x2_non_linear_2d")
+        {
+          using DataType = TinyMatrix<2>;
+
+          auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id] =
+                DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3, std::sin(x[1] - 2 * x[0]), 3, x[1] * x[0]};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R3x3_non_linear_2d")
+        {
+          using DataType = TinyMatrix<3>;
+
+          auto [i_symbol, found] = symbol_table->find("R3x3_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+
+              cell_value[cell_id] = DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3,
+                                             std::sin(x[1] - 2 * x[0]),
+                                             3,
+                                             x[1] * x[0],
+                                             -4 * x[1],
+                                             2 * x[0] + 1,
+                                             3,
+                                             -6 * x[0],
+                                             std::exp(x[1])};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+      }
     }
   }
 
@@ -626,10 +642,16 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> (2 * exp(x[0]) * sin(x[1]) + 3, sin(x
   {
     constexpr size_t Dimension = 3;
 
-    const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
-    auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
+
+        auto xj = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
 
-    std::string_view data = R"(
+        std::string_view data = R"(
 import math;
 let B_scalar_non_linear_3d: R^3 -> B, x -> (exp(2 * x[0])< 2*x[1]+x[2]);
 let N_scalar_non_linear_3d: R^3 -> N, x -> floor(3 * (x[0] + x[1]) * (x[0] + x[1]) + x[2] * x[2]);
@@ -642,275 +664,277 @@ let R1x1_non_linear_3d: R^3 -> R^1x1, x -> (2 * exp(x[0]) * sin(x[1]) + 3 * x[2]
 let R2x2_non_linear_3d: R^3 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[1]) + 3, sin(x[2] - 2 * x[0]), 3, x[1] * x[0] - x[2]);
 let R3x3_non_linear_3d: R^3 -> R^3x3, x -> (2 * exp(x[0]) * sin(x[1]) + 3, sin(x[1] - 2 * x[2]), 3, x[1] * x[2], -4*x[1], 2*x[2]+1, 3, -6*x[2], exp(x[1] + x[2]));
 )";
-    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-    auto ast = ASTBuilder::build(input);
-
-    ASTModulesImporter{*ast};
-    ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-    ASTSymbolTableBuilder{*ast};
-    ASTNodeDataTypeBuilder{*ast};
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-    ASTNodeTypeCleaner<language::var_declaration>{*ast};
-    ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-    ASTNodeExpressionBuilder{*ast};
+        ASTSymbolTableBuilder{*ast};
+        ASTNodeDataTypeBuilder{*ast};
+
+        ASTNodeTypeCleaner<language::var_declaration>{*ast};
+        ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+        ASTNodeExpressionBuilder{*ast};
+
+        std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+        TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+        position.byte = data.size();   // ensure that variables are declared at this point
 
-    std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+        SECTION("B_scalar_non_linear_3d")
+        {
+          auto [i_symbol, found] = symbol_table->find("B_scalar_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::exp(2 * x[0]) < 2 * x[1] + x[2];
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("N_scalar_non_linear_3d")
+        {
+          auto [i_symbol, found] = symbol_table->find("N_scalar_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(3 * (x[0] + x[1]) * (x[0] + x[1]) + x[2] * x[2]);
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("Z_scalar_non_linear_3d")
+        {
+          auto [i_symbol, found] = symbol_table->find("Z_scalar_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-    TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-    position.byte = data.size();   // ensure that variables are declared at this point
-
-    SECTION("B_scalar_non_linear_3d")
-    {
-      auto [i_symbol, found] = symbol_table->find("B_scalar_non_linear_3d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::exp(2 * x[0]) < 2 * x[1] + x[2];
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("N_scalar_non_linear_3d")
-    {
-      auto [i_symbol, found] = symbol_table->find("N_scalar_non_linear_3d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(3 * (x[0] + x[1]) * (x[0] + x[1]) + x[2] * x[2]);
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("Z_scalar_non_linear_3d")
-    {
-      auto [i_symbol, found] = symbol_table->find("Z_scalar_non_linear_3d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(std::exp(2 * x[0]) - 3 * x[1] + x[2]);
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("R_scalar_non_linear_3d")
-    {
-      auto [i_symbol, found] = symbol_table->find("R_scalar_non_linear_3d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<double> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = 2 * std::exp(x[0] + x[2]) + 3 * x[1];
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
-    }
-
-    SECTION("R1_non_linear_3d")
-    {
-      using DataType = TinyVector<1>;
-
-      auto [i_symbol, found] = symbol_table->find("R1_non_linear_3d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0]) + std::sin(x[1] + x[2])};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R2_non_linear_3d")
-    {
-      using DataType = TinyVector<2>;
-
-      auto [i_symbol, found] = symbol_table->find("R2_non_linear_3d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0]), -3 * x[1] * x[2]};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R3_non_linear_3d")
-    {
-      using DataType = TinyVector<3>;
-
-      auto [i_symbol, found] = symbol_table->find("R3_non_linear_3d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0]) + 3, x[1] - 2, 3 * x[2]};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R1x1_non_linear_3d")
-    {
-      using DataType = TinyMatrix<1>;
-
-      auto [i_symbol, found] = symbol_table->find("R1x1_non_linear_3d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3 * x[2]};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R2x2_non_linear_3d")
-    {
-      using DataType = TinyMatrix<2>;
-
-      auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_3d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id] =
-            DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3, std::sin(x[2] - 2 * x[0]), 3, x[1] * x[0] - x[2]};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
-    }
-
-    SECTION("R3x3_non_linear_3d")
-    {
-      using DataType = TinyMatrix<3>;
-
-      auto [i_symbol, found] = symbol_table->find("R3x3_non_linear_3d", position);
-      REQUIRE(found);
-      REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-      FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-      CellValue<DataType> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-
-          cell_value[cell_id] = DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3,
-                                         std::sin(x[1] - 2 * x[2]),
-                                         3,
-                                         x[1] * x[2],
-                                         -4 * x[1],
-                                         2 * x[2] + 1,
-                                         3,
-                                         -6 * x[2],
-                                         std::exp(x[1] + x[2])};
-        });
-
-      DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
-                                            function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
-
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(std::exp(2 * x[0]) - 3 * x[1] + x[2]);
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("R_scalar_non_linear_3d")
+        {
+          auto [i_symbol, found] = symbol_table->find("R_scalar_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = 2 * std::exp(x[0] + x[2]) + 3 * x[1];
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+        }
+
+        SECTION("R1_non_linear_3d")
+        {
+          using DataType = TinyVector<1>;
+
+          auto [i_symbol, found] = symbol_table->find("R1_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]) + std::sin(x[1] + x[2])};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R2_non_linear_3d")
+        {
+          using DataType = TinyVector<2>;
+
+          auto [i_symbol, found] = symbol_table->find("R2_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]), -3 * x[1] * x[2]};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R3_non_linear_3d")
+        {
+          using DataType = TinyVector<3>;
+
+          auto [i_symbol, found] = symbol_table->find("R3_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]) + 3, x[1] - 2, 3 * x[2]};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R1x1_non_linear_3d")
+        {
+          using DataType = TinyMatrix<1>;
+
+          auto [i_symbol, found] = symbol_table->find("R1x1_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3 * x[2]};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R2x2_non_linear_3d")
+        {
+          using DataType = TinyMatrix<2>;
+
+          auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id] =
+                DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3, std::sin(x[2] - 2 * x[0]), 3, x[1] * x[0] - x[2]};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+
+        SECTION("R3x3_non_linear_3d")
+        {
+          using DataType = TinyMatrix<3>;
+
+          auto [i_symbol, found] = symbol_table->find("R3x3_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+
+              cell_value[cell_id] = DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3,
+                                             std::sin(x[1] - 2 * x[2]),
+                                             3,
+                                             x[1] * x[2],
+                                             -4 * x[1],
+                                             2 * x[2] + 1,
+                                             3,
+                                             -6 * x[2],
+                                             std::exp(x[1] + x[2])};
+            });
+
+          DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
+                                                function_symbol_id);
+          std::shared_ptr discrete_function = interpoler.interpolate();
+
+          REQUIRE(same_cell_value(cell_value,
+                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+        }
+      }
     }
   }
 }
diff --git a/tests/test_DiscreteFunctionP0.cpp b/tests/test_DiscreteFunctionP0.cpp
index f8d7bb6ef78d027287cf7d721299f051ba7c7a98..7073def1e438a3a19df04723ba88349d3bb7d7b1 100644
--- a/tests/test_DiscreteFunctionP0.cpp
+++ b/tests/test_DiscreteFunctionP0.cpp
@@ -22,128 +22,147 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
   {
     SECTION("1D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
       constexpr size_t Dimension = 1;
 
-      DiscreteFunctionP0<Dimension, double> f{mesh};
-      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0);
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0);
 
-      REQUIRE(f.mesh().get() == mesh.get());
+          REQUIRE(f.mesh().get() == mesh.get());
 
-      DiscreteFunctionP0 g{f};
-      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0);
+          DiscreteFunctionP0 g{f};
+          REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0);
 
-      CellValue<TinyVector<Dimension>> h_values{mesh->connectivity()};
-      h_values.fill(ZeroType{});
+          CellValue<TinyVector<Dimension>> h_values{mesh->connectivity()};
+          h_values.fill(ZeroType{});
 
-      DiscreteFunctionP0 zero{mesh, [&] {
-                                CellValue<TinyVector<Dimension>> cell_value{mesh->connectivity()};
-                                cell_value.fill(ZeroType{});
-                                return cell_value;
-                              }()};
+          DiscreteFunctionP0 zero{mesh, [&] {
+                                    CellValue<TinyVector<Dimension>> cell_value{mesh->connectivity()};
+                                    cell_value.fill(ZeroType{});
+                                    return cell_value;
+                                  }()};
 
-      DiscreteFunctionP0 h{mesh, h_values};
-      REQUIRE(same_values(h, zero));
-      REQUIRE(same_values(h, h_values));
+          DiscreteFunctionP0 h{mesh, h_values};
+          REQUIRE(same_values(h, zero));
+          REQUIRE(same_values(h, h_values));
 
-      DiscreteFunctionP0<Dimension, TinyVector<Dimension>> shallow_h{mesh};
-      shallow_h = h;
+          DiscreteFunctionP0<Dimension, TinyVector<Dimension>> shallow_h{mesh};
+          shallow_h = h;
 
-      copy_to(MeshDataManager::instance().getMeshData(*mesh).xj(), h_values);
+          copy_to(MeshDataManager::instance().getMeshData(*mesh).xj(), h_values);
 
-      REQUIRE(same_values(shallow_h, h_values));
-      REQUIRE(same_values(h, h_values));
-      REQUIRE(not same_values(h, zero));
+          REQUIRE(same_values(shallow_h, h_values));
+          REQUIRE(same_values(h, h_values));
+          REQUIRE(not same_values(h, zero));
 
-      DiscreteFunctionP0 moved_h{std::move(h)};
-      REQUIRE(same_values(moved_h, h_values));
+          DiscreteFunctionP0 moved_h{std::move(h)};
+          REQUIRE(same_values(moved_h, h_values));
+        }
+      }
     }
 
     SECTION("2D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
-
       constexpr size_t Dimension = 2;
 
-      DiscreteFunctionP0<Dimension, double> f{mesh};
-      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0);
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0);
 
-      REQUIRE(f.mesh().get() == mesh.get());
+          REQUIRE(f.mesh().get() == mesh.get());
 
-      DiscreteFunctionP0 g{f};
-      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0);
+          DiscreteFunctionP0 g{f};
+          REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0);
 
-      CellValue<TinyVector<Dimension>> h_values{mesh->connectivity()};
-      h_values.fill(ZeroType{});
+          CellValue<TinyVector<Dimension>> h_values{mesh->connectivity()};
+          h_values.fill(ZeroType{});
 
-      DiscreteFunctionP0 zero{mesh, [&] {
-                                CellValue<TinyVector<Dimension>> cell_value{mesh->connectivity()};
-                                cell_value.fill(ZeroType{});
-                                return cell_value;
-                              }()};
+          DiscreteFunctionP0 zero{mesh, [&] {
+                                    CellValue<TinyVector<Dimension>> cell_value{mesh->connectivity()};
+                                    cell_value.fill(ZeroType{});
+                                    return cell_value;
+                                  }()};
 
-      DiscreteFunctionP0 h{mesh, h_values};
-      REQUIRE(same_values(h, zero));
-      REQUIRE(same_values(h, h_values));
+          DiscreteFunctionP0 h{mesh, h_values};
+          REQUIRE(same_values(h, zero));
+          REQUIRE(same_values(h, h_values));
 
-      DiscreteFunctionP0<Dimension, TinyVector<Dimension>> shallow_h{mesh};
-      shallow_h = h;
+          DiscreteFunctionP0<Dimension, TinyVector<Dimension>> shallow_h{mesh};
+          shallow_h = h;
 
-      copy_to(MeshDataManager::instance().getMeshData(*mesh).xj(), h_values);
+          copy_to(MeshDataManager::instance().getMeshData(*mesh).xj(), h_values);
 
-      REQUIRE(same_values(shallow_h, h_values));
-      REQUIRE(same_values(h, h_values));
-      REQUIRE(not same_values(h, zero));
+          REQUIRE(same_values(shallow_h, h_values));
+          REQUIRE(same_values(h, h_values));
+          REQUIRE(not same_values(h, zero));
 
-      DiscreteFunctionP0 moved_h{std::move(h)};
-      REQUIRE(same_values(moved_h, h_values));
+          DiscreteFunctionP0 moved_h{std::move(h)};
+          REQUIRE(same_values(moved_h, h_values));
+        }
+      }
     }
 
     SECTION("3D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
-
       constexpr size_t Dimension = 3;
 
-      DiscreteFunctionP0<Dimension, double> f{mesh};
-      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0);
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0);
 
-      REQUIRE(f.mesh().get() == mesh.get());
+          REQUIRE(f.mesh().get() == mesh.get());
 
-      DiscreteFunctionP0 g{f};
-      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0);
+          DiscreteFunctionP0 g{f};
+          REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0);
 
-      CellValue<TinyVector<Dimension>> h_values{mesh->connectivity()};
-      h_values.fill(ZeroType{});
+          CellValue<TinyVector<Dimension>> h_values{mesh->connectivity()};
+          h_values.fill(ZeroType{});
 
-      DiscreteFunctionP0 zero{mesh, [&] {
-                                CellValue<TinyVector<Dimension>> cell_value{mesh->connectivity()};
-                                cell_value.fill(ZeroType{});
-                                return cell_value;
-                              }()};
+          DiscreteFunctionP0 zero{mesh, [&] {
+                                    CellValue<TinyVector<Dimension>> cell_value{mesh->connectivity()};
+                                    cell_value.fill(ZeroType{});
+                                    return cell_value;
+                                  }()};
 
-      DiscreteFunctionP0 h{mesh, h_values};
-      REQUIRE(same_values(h, zero));
-      REQUIRE(same_values(h, h_values));
+          DiscreteFunctionP0 h{mesh, h_values};
+          REQUIRE(same_values(h, zero));
+          REQUIRE(same_values(h, h_values));
 
-      DiscreteFunctionP0<Dimension, TinyVector<Dimension>> shallow_h{mesh};
-      shallow_h = h;
+          DiscreteFunctionP0<Dimension, TinyVector<Dimension>> shallow_h{mesh};
+          shallow_h = h;
 
-      copy_to(MeshDataManager::instance().getMeshData(*mesh).xj(), h_values);
+          copy_to(MeshDataManager::instance().getMeshData(*mesh).xj(), h_values);
 
-      REQUIRE(same_values(shallow_h, h_values));
-      REQUIRE(same_values(h, h_values));
-      REQUIRE(not same_values(h, zero));
+          REQUIRE(same_values(shallow_h, h_values));
+          REQUIRE(same_values(h, h_values));
+          REQUIRE(not same_values(h, zero));
 
-      DiscreteFunctionP0 moved_h{std::move(h)};
-      REQUIRE(same_values(moved_h, h_values));
+          DiscreteFunctionP0 moved_h{std::move(h)};
+          REQUIRE(same_values(moved_h, h_values));
+        }
+      }
     }
   }
 
@@ -161,65 +180,89 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
     SECTION("1D")
     {
-      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh1D();
       constexpr size_t Dimension = 1;
 
-      DiscreteFunctionP0<Dimension, double> f{mesh};
-      f.fill(3);
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          f.fill(3);
 
-      REQUIRE(all_values_equal(f, 3));
+          REQUIRE(all_values_equal(f, 3));
 
-      DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
-      v.fill(TinyVector<3>{1, 2, 3});
+          DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+          v.fill(TinyVector<3>{1, 2, 3});
 
-      REQUIRE(all_values_equal(v, TinyVector<3>{1, 2, 3}));
+          REQUIRE(all_values_equal(v, TinyVector<3>{1, 2, 3}));
 
-      DiscreteFunctionP0<Dimension, TinyMatrix<3>> A{mesh};
-      A.fill(TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9});
+          DiscreteFunctionP0<Dimension, TinyMatrix<3>> A{mesh};
+          A.fill(TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9});
 
-      REQUIRE(all_values_equal(A, TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9}));
+          REQUIRE(all_values_equal(A, TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9}));
+        }
+      }
     }
 
     SECTION("2D")
     {
-      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh2D();
       constexpr size_t Dimension = 2;
 
-      DiscreteFunctionP0<Dimension, double> f{mesh};
-      f.fill(3);
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          f.fill(3);
 
-      REQUIRE(all_values_equal(f, 3));
+          REQUIRE(all_values_equal(f, 3));
 
-      DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
-      v.fill(TinyVector<3>{1, 2, 3});
+          DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+          v.fill(TinyVector<3>{1, 2, 3});
 
-      REQUIRE(all_values_equal(v, TinyVector<3>{1, 2, 3}));
+          REQUIRE(all_values_equal(v, TinyVector<3>{1, 2, 3}));
 
-      DiscreteFunctionP0<Dimension, TinyMatrix<3>> A{mesh};
-      A.fill(TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9});
+          DiscreteFunctionP0<Dimension, TinyMatrix<3>> A{mesh};
+          A.fill(TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9});
 
-      REQUIRE(all_values_equal(A, TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9}));
+          REQUIRE(all_values_equal(A, TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9}));
+        }
+      }
     }
 
     SECTION("3D")
     {
-      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh3D();
       constexpr size_t Dimension = 3;
 
-      DiscreteFunctionP0<Dimension, double> f{mesh};
-      f.fill(3);
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          f.fill(3);
 
-      REQUIRE(all_values_equal(f, 3));
+          REQUIRE(all_values_equal(f, 3));
 
-      DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
-      v.fill(TinyVector<3>{1, 2, 3});
+          DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+          v.fill(TinyVector<3>{1, 2, 3});
 
-      REQUIRE(all_values_equal(v, TinyVector<3>{1, 2, 3}));
+          REQUIRE(all_values_equal(v, TinyVector<3>{1, 2, 3}));
 
-      DiscreteFunctionP0<Dimension, TinyMatrix<3>> A{mesh};
-      A.fill(TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9});
+          DiscreteFunctionP0<Dimension, TinyMatrix<3>> A{mesh};
+          A.fill(TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9});
 
-      REQUIRE(all_values_equal(A, TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9}));
+          REQUIRE(all_values_equal(A, TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9}));
+        }
+      }
     }
   }
 
@@ -237,286 +280,307 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
     SECTION("1D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
       constexpr size_t Dimension = 1;
 
-      SECTION("scalar")
-      {
-        const size_t value = parallel::rank() + 1;
-        const size_t zero  = 0;
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
 
-        DiscreteFunctionP0<Dimension, size_t> f{mesh};
-        f.fill(value);
+          SECTION("scalar")
+          {
+            const size_t value = parallel::rank() + 1;
+            const size_t zero  = 0;
 
-        REQUIRE(all_values_equal(f, value));
+            DiscreteFunctionP0<Dimension, size_t> f{mesh};
+            f.fill(value);
 
-        DiscreteFunctionP0 g = copy(f);
-        f.fill(zero);
+            REQUIRE(all_values_equal(f, value));
 
-        REQUIRE(all_values_equal(f, zero));
-        REQUIRE(all_values_equal(g, value));
+            DiscreteFunctionP0 g = copy(f);
+            f.fill(zero);
 
-        copy_to(g, f);
-        g.fill(zero);
+            REQUIRE(all_values_equal(f, zero));
+            REQUIRE(all_values_equal(g, value));
 
-        DiscreteFunctionP0<Dimension, const size_t> h = copy(f);
+            copy_to(g, f);
+            g.fill(zero);
 
-        REQUIRE(all_values_equal(f, value));
-        REQUIRE(all_values_equal(g, zero));
-        REQUIRE(all_values_equal(h, value));
+            DiscreteFunctionP0<Dimension, const size_t> h = copy(f);
 
-        copy_to(h, g);
+            REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(g, zero));
+            REQUIRE(all_values_equal(h, value));
 
-        REQUIRE(all_values_equal(g, value));
-      }
+            copy_to(h, g);
 
-      SECTION("vector")
-      {
-        const TinyVector<2, size_t> value{parallel::rank() + 1, 3};
-        const TinyVector<2, size_t> zero{ZeroType{}};
-        DiscreteFunctionP0<Dimension, TinyVector<2, size_t>> f{mesh};
-        f.fill(value);
+            REQUIRE(all_values_equal(g, value));
+          }
 
-        REQUIRE(all_values_equal(f, value));
+          SECTION("vector")
+          {
+            const TinyVector<2, size_t> value{parallel::rank() + 1, 3};
+            const TinyVector<2, size_t> zero{ZeroType{}};
+            DiscreteFunctionP0<Dimension, TinyVector<2, size_t>> f{mesh};
+            f.fill(value);
 
-        DiscreteFunctionP0 g = copy(f);
-        f.fill(zero);
+            REQUIRE(all_values_equal(f, value));
 
-        REQUIRE(all_values_equal(f, zero));
-        REQUIRE(all_values_equal(g, value));
+            DiscreteFunctionP0 g = copy(f);
+            f.fill(zero);
 
-        copy_to(g, f);
-        g.fill(zero);
+            REQUIRE(all_values_equal(f, zero));
+            REQUIRE(all_values_equal(g, value));
 
-        DiscreteFunctionP0<Dimension, const TinyVector<2, size_t>> h = copy(f);
+            copy_to(g, f);
+            g.fill(zero);
 
-        REQUIRE(all_values_equal(f, value));
-        REQUIRE(all_values_equal(g, zero));
-        REQUIRE(all_values_equal(h, value));
+            DiscreteFunctionP0<Dimension, const TinyVector<2, size_t>> h = copy(f);
 
-        copy_to(h, g);
+            REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(g, zero));
+            REQUIRE(all_values_equal(h, value));
 
-        REQUIRE(all_values_equal(g, value));
-      }
+            copy_to(h, g);
 
-      SECTION("matrix")
-      {
-        const TinyMatrix<3, 3, size_t> value{1, 2, 3, 4, 5, 6, 7, 8, 9};
-        const TinyMatrix<3, 3, size_t> zero{ZeroType{}};
-        DiscreteFunctionP0<Dimension, TinyMatrix<3, 3, size_t>> f{mesh};
-        f.fill(value);
+            REQUIRE(all_values_equal(g, value));
+          }
+
+          SECTION("matrix")
+          {
+            const TinyMatrix<3, 3, size_t> value{1, 2, 3, 4, 5, 6, 7, 8, 9};
+            const TinyMatrix<3, 3, size_t> zero{ZeroType{}};
+            DiscreteFunctionP0<Dimension, TinyMatrix<3, 3, size_t>> f{mesh};
+            f.fill(value);
 
-        REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(f, value));
 
-        DiscreteFunctionP0 g = copy(f);
-        f.fill(zero);
+            DiscreteFunctionP0 g = copy(f);
+            f.fill(zero);
 
-        REQUIRE(all_values_equal(f, zero));
-        REQUIRE(all_values_equal(g, value));
+            REQUIRE(all_values_equal(f, zero));
+            REQUIRE(all_values_equal(g, value));
 
-        copy_to(g, f);
-        g.fill(zero);
+            copy_to(g, f);
+            g.fill(zero);
 
-        DiscreteFunctionP0<Dimension, const TinyMatrix<3, 3, size_t>> h = copy(f);
+            DiscreteFunctionP0<Dimension, const TinyMatrix<3, 3, size_t>> h = copy(f);
 
-        REQUIRE(all_values_equal(f, value));
-        REQUIRE(all_values_equal(g, zero));
-        REQUIRE(all_values_equal(h, value));
+            REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(g, zero));
+            REQUIRE(all_values_equal(h, value));
 
-        copy_to(h, g);
+            copy_to(h, g);
 
-        REQUIRE(all_values_equal(g, value));
+            REQUIRE(all_values_equal(g, value));
+          }
+        }
       }
     }
 
     SECTION("2D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
-
       constexpr size_t Dimension = 2;
 
-      SECTION("scalar")
-      {
-        const size_t value = parallel::rank() + 1;
-        const size_t zero  = 0;
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
 
-        DiscreteFunctionP0<Dimension, size_t> f{mesh};
-        f.fill(value);
+          SECTION("scalar")
+          {
+            const size_t value = parallel::rank() + 1;
+            const size_t zero  = 0;
 
-        REQUIRE(all_values_equal(f, value));
+            DiscreteFunctionP0<Dimension, size_t> f{mesh};
+            f.fill(value);
 
-        DiscreteFunctionP0 g = copy(f);
-        f.fill(zero);
+            REQUIRE(all_values_equal(f, value));
 
-        REQUIRE(all_values_equal(f, zero));
-        REQUIRE(all_values_equal(g, value));
+            DiscreteFunctionP0 g = copy(f);
+            f.fill(zero);
 
-        copy_to(g, f);
-        g.fill(zero);
+            REQUIRE(all_values_equal(f, zero));
+            REQUIRE(all_values_equal(g, value));
 
-        DiscreteFunctionP0<Dimension, const size_t> h = copy(f);
+            copy_to(g, f);
+            g.fill(zero);
 
-        REQUIRE(all_values_equal(f, value));
-        REQUIRE(all_values_equal(g, zero));
-        REQUIRE(all_values_equal(h, value));
+            DiscreteFunctionP0<Dimension, const size_t> h = copy(f);
 
-        copy_to(h, g);
+            REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(g, zero));
+            REQUIRE(all_values_equal(h, value));
 
-        REQUIRE(all_values_equal(g, value));
-      }
+            copy_to(h, g);
 
-      SECTION("vector")
-      {
-        const TinyVector<2, size_t> value{parallel::rank() + 1, 3};
-        const TinyVector<2, size_t> zero{ZeroType{}};
-        DiscreteFunctionP0<Dimension, TinyVector<2, size_t>> f{mesh};
-        f.fill(value);
+            REQUIRE(all_values_equal(g, value));
+          }
 
-        REQUIRE(all_values_equal(f, value));
+          SECTION("vector")
+          {
+            const TinyVector<2, size_t> value{parallel::rank() + 1, 3};
+            const TinyVector<2, size_t> zero{ZeroType{}};
+            DiscreteFunctionP0<Dimension, TinyVector<2, size_t>> f{mesh};
+            f.fill(value);
 
-        DiscreteFunctionP0 g = copy(f);
-        f.fill(zero);
+            REQUIRE(all_values_equal(f, value));
 
-        REQUIRE(all_values_equal(f, zero));
-        REQUIRE(all_values_equal(g, value));
+            DiscreteFunctionP0 g = copy(f);
+            f.fill(zero);
 
-        copy_to(g, f);
-        g.fill(zero);
+            REQUIRE(all_values_equal(f, zero));
+            REQUIRE(all_values_equal(g, value));
 
-        DiscreteFunctionP0<Dimension, const TinyVector<2, size_t>> h = copy(f);
+            copy_to(g, f);
+            g.fill(zero);
 
-        REQUIRE(all_values_equal(f, value));
-        REQUIRE(all_values_equal(g, zero));
-        REQUIRE(all_values_equal(h, value));
+            DiscreteFunctionP0<Dimension, const TinyVector<2, size_t>> h = copy(f);
 
-        copy_to(h, g);
+            REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(g, zero));
+            REQUIRE(all_values_equal(h, value));
 
-        REQUIRE(all_values_equal(g, value));
-      }
+            copy_to(h, g);
 
-      SECTION("matrix")
-      {
-        const TinyMatrix<3, 3, size_t> value{1, 2, 3, 4, 5, 6, 7, 8, 9};
-        const TinyMatrix<3, 3, size_t> zero{ZeroType{}};
-        DiscreteFunctionP0<Dimension, TinyMatrix<3, 3, size_t>> f{mesh};
-        f.fill(value);
+            REQUIRE(all_values_equal(g, value));
+          }
+
+          SECTION("matrix")
+          {
+            const TinyMatrix<3, 3, size_t> value{1, 2, 3, 4, 5, 6, 7, 8, 9};
+            const TinyMatrix<3, 3, size_t> zero{ZeroType{}};
+            DiscreteFunctionP0<Dimension, TinyMatrix<3, 3, size_t>> f{mesh};
+            f.fill(value);
 
-        REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(f, value));
 
-        DiscreteFunctionP0 g = copy(f);
-        f.fill(zero);
+            DiscreteFunctionP0 g = copy(f);
+            f.fill(zero);
 
-        REQUIRE(all_values_equal(f, zero));
-        REQUIRE(all_values_equal(g, value));
+            REQUIRE(all_values_equal(f, zero));
+            REQUIRE(all_values_equal(g, value));
 
-        copy_to(g, f);
-        g.fill(zero);
+            copy_to(g, f);
+            g.fill(zero);
 
-        DiscreteFunctionP0<Dimension, const TinyMatrix<3, 3, size_t>> h = copy(f);
+            DiscreteFunctionP0<Dimension, const TinyMatrix<3, 3, size_t>> h = copy(f);
 
-        REQUIRE(all_values_equal(f, value));
-        REQUIRE(all_values_equal(g, zero));
-        REQUIRE(all_values_equal(h, value));
+            REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(g, zero));
+            REQUIRE(all_values_equal(h, value));
 
-        copy_to(h, g);
+            copy_to(h, g);
 
-        REQUIRE(all_values_equal(g, value));
+            REQUIRE(all_values_equal(g, value));
+          }
+        }
       }
     }
 
     SECTION("3D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
-
       constexpr size_t Dimension = 3;
 
-      SECTION("scalar")
-      {
-        const size_t value = parallel::rank() + 1;
-        const size_t zero  = 0;
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
 
-        DiscreteFunctionP0<Dimension, size_t> f{mesh};
-        f.fill(value);
+          SECTION("scalar")
+          {
+            const size_t value = parallel::rank() + 1;
+            const size_t zero  = 0;
 
-        REQUIRE(all_values_equal(f, value));
+            DiscreteFunctionP0<Dimension, size_t> f{mesh};
+            f.fill(value);
 
-        DiscreteFunctionP0 g = copy(f);
-        f.fill(zero);
+            REQUIRE(all_values_equal(f, value));
 
-        REQUIRE(all_values_equal(f, zero));
-        REQUIRE(all_values_equal(g, value));
+            DiscreteFunctionP0 g = copy(f);
+            f.fill(zero);
 
-        copy_to(g, f);
-        g.fill(zero);
+            REQUIRE(all_values_equal(f, zero));
+            REQUIRE(all_values_equal(g, value));
 
-        DiscreteFunctionP0<Dimension, const size_t> h = copy(f);
+            copy_to(g, f);
+            g.fill(zero);
 
-        REQUIRE(all_values_equal(f, value));
-        REQUIRE(all_values_equal(g, zero));
-        REQUIRE(all_values_equal(h, value));
+            DiscreteFunctionP0<Dimension, const size_t> h = copy(f);
 
-        copy_to(h, g);
+            REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(g, zero));
+            REQUIRE(all_values_equal(h, value));
 
-        REQUIRE(all_values_equal(g, value));
-      }
+            copy_to(h, g);
 
-      SECTION("vector")
-      {
-        const TinyVector<2, size_t> value{parallel::rank() + 1, 3};
-        const TinyVector<2, size_t> zero{ZeroType{}};
-        DiscreteFunctionP0<Dimension, TinyVector<2, size_t>> f{mesh};
-        f.fill(value);
+            REQUIRE(all_values_equal(g, value));
+          }
 
-        REQUIRE(all_values_equal(f, value));
+          SECTION("vector")
+          {
+            const TinyVector<2, size_t> value{parallel::rank() + 1, 3};
+            const TinyVector<2, size_t> zero{ZeroType{}};
+            DiscreteFunctionP0<Dimension, TinyVector<2, size_t>> f{mesh};
+            f.fill(value);
 
-        DiscreteFunctionP0 g = copy(f);
-        f.fill(zero);
+            REQUIRE(all_values_equal(f, value));
 
-        REQUIRE(all_values_equal(f, zero));
-        REQUIRE(all_values_equal(g, value));
+            DiscreteFunctionP0 g = copy(f);
+            f.fill(zero);
 
-        copy_to(g, f);
-        g.fill(zero);
+            REQUIRE(all_values_equal(f, zero));
+            REQUIRE(all_values_equal(g, value));
 
-        DiscreteFunctionP0<Dimension, const TinyVector<2, size_t>> h = copy(f);
+            copy_to(g, f);
+            g.fill(zero);
 
-        REQUIRE(all_values_equal(f, value));
-        REQUIRE(all_values_equal(g, zero));
-        REQUIRE(all_values_equal(h, value));
+            DiscreteFunctionP0<Dimension, const TinyVector<2, size_t>> h = copy(f);
 
-        copy_to(h, g);
+            REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(g, zero));
+            REQUIRE(all_values_equal(h, value));
 
-        REQUIRE(all_values_equal(g, value));
-      }
+            copy_to(h, g);
 
-      SECTION("matrix")
-      {
-        const TinyMatrix<3, 3, size_t> value{1, 2, 3, 4, 5, 6, 7, 8, 9};
-        const TinyMatrix<3, 3, size_t> zero{ZeroType{}};
-        DiscreteFunctionP0<Dimension, TinyMatrix<3, 3, size_t>> f{mesh};
-        f.fill(value);
+            REQUIRE(all_values_equal(g, value));
+          }
+
+          SECTION("matrix")
+          {
+            const TinyMatrix<3, 3, size_t> value{1, 2, 3, 4, 5, 6, 7, 8, 9};
+            const TinyMatrix<3, 3, size_t> zero{ZeroType{}};
+            DiscreteFunctionP0<Dimension, TinyMatrix<3, 3, size_t>> f{mesh};
+            f.fill(value);
 
-        REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(f, value));
 
-        DiscreteFunctionP0 g = copy(f);
-        f.fill(zero);
+            DiscreteFunctionP0 g = copy(f);
+            f.fill(zero);
 
-        REQUIRE(all_values_equal(f, zero));
-        REQUIRE(all_values_equal(g, value));
+            REQUIRE(all_values_equal(f, zero));
+            REQUIRE(all_values_equal(g, value));
 
-        copy_to(g, f);
-        g.fill(zero);
+            copy_to(g, f);
+            g.fill(zero);
 
-        DiscreteFunctionP0<Dimension, const TinyMatrix<3, 3, size_t>> h = copy(f);
+            DiscreteFunctionP0<Dimension, const TinyMatrix<3, 3, size_t>> h = copy(f);
 
-        REQUIRE(all_values_equal(f, value));
-        REQUIRE(all_values_equal(g, zero));
-        REQUIRE(all_values_equal(h, value));
+            REQUIRE(all_values_equal(f, value));
+            REQUIRE(all_values_equal(g, zero));
+            REQUIRE(all_values_equal(h, value));
 
-        copy_to(h, g);
+            copy_to(h, g);
 
-        REQUIRE(all_values_equal(g, value));
+            REQUIRE(all_values_equal(g, value));
+          }
+        }
       }
     }
   }
@@ -525,75 +589,81 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
   {
     SECTION("1D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
       constexpr size_t Dimension = 1;
+      std::array mesh_list       = MeshDataBaseForTests::get().all1DMeshes();
 
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-      SECTION("unary minus")
-      {
-        SECTION("scalar functions")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          DiscreteFunctionP0<Dimension, double> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              f[cell_id]     = 2 * x + 1;
-            });
+          auto mesh = named_mesh.mesh();
 
-          DiscreteFunctionP0<Dimension, const double> const_f = f;
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
 
-          Array<double> minus_values{mesh->numberOfCells()};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { minus_values[cell_id] = -f[cell_id]; });
+          SECTION("unary minus")
+          {
+            SECTION("scalar functions")
+            {
+              DiscreteFunctionP0<Dimension, double> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  f[cell_id]     = 2 * x + 1;
+                });
 
-          REQUIRE(same_values(-f, minus_values));
-          REQUIRE(same_values(-const_f, minus_values));
-        }
+              DiscreteFunctionP0<Dimension, const double> const_f = f;
 
-        SECTION("vector functions")
-        {
-          constexpr std::uint64_t VectorDimension = 2;
+              Array<double> minus_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { minus_values[cell_id] = -f[cell_id]; });
 
-          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyVector<VectorDimension> X{x, 2 - x};
-              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
-            });
+              REQUIRE(same_values(-f, minus_values));
+              REQUIRE(same_values(-const_f, minus_values));
+            }
 
-          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+            SECTION("vector functions")
+            {
+              constexpr std::uint64_t VectorDimension = 2;
 
-          Array<TinyVector<VectorDimension>> minus_values{mesh->numberOfCells()};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { minus_values[cell_id] = -f[cell_id]; });
+              DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyVector<VectorDimension> X{x, 2 - x};
+                  f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+                });
 
-          REQUIRE(same_values(-f, minus_values));
-          REQUIRE(same_values(-const_f, minus_values));
-        }
+              DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
 
-        SECTION("matrix functions")
-        {
-          constexpr std::uint64_t MatrixDimension = 2;
+              Array<TinyVector<VectorDimension>> minus_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { minus_values[cell_id] = -f[cell_id]; });
 
-          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
-              f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
-            });
+              REQUIRE(same_values(-f, minus_values));
+              REQUIRE(same_values(-const_f, minus_values));
+            }
 
-          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+            SECTION("matrix functions")
+            {
+              constexpr std::uint64_t MatrixDimension = 2;
 
-          Array<TinyMatrix<MatrixDimension>> minus_values{mesh->numberOfCells()};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { minus_values[cell_id] = -f[cell_id]; });
+              DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
+                  f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
+                });
+
+              DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+
+              Array<TinyMatrix<MatrixDimension>> minus_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { minus_values[cell_id] = -f[cell_id]; });
 
-          REQUIRE(same_values(-f, minus_values));
-          REQUIRE(same_values(-const_f, minus_values));
+              REQUIRE(same_values(-f, minus_values));
+              REQUIRE(same_values(-const_f, minus_values));
+            }
+          }
         }
       }
     }
@@ -603,2839 +673,2937 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
   {
     SECTION("1D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
       constexpr size_t Dimension = 1;
 
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      SECTION("inner operators")
-      {
-        SECTION("scalar functions")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          DiscreteFunctionP0<Dimension, double> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              f[cell_id]     = 2 * x + 1;
-            });
+          auto mesh = named_mesh.mesh();
 
-          DiscreteFunctionP0<Dimension, double> g{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              g[cell_id]     = std::abs((x + 1) * (x - 2)) + 1;
-            });
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
 
-          DiscreteFunctionP0<Dimension, const double> const_f = f;
-          DiscreteFunctionP0<Dimension, const double> const_g{g};
-
-          SECTION("sum")
+          SECTION("inner operators")
           {
-            Array<double> sum_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
-
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
-          }
+            SECTION("scalar functions")
+            {
+              DiscreteFunctionP0<Dimension, double> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  f[cell_id]     = 2 * x + 1;
+                });
 
-          SECTION("difference")
-          {
-            Array<double> difference_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+              DiscreteFunctionP0<Dimension, double> g{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  g[cell_id]     = std::abs((x + 1) * (x - 2)) + 1;
+                });
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
-          }
+              DiscreteFunctionP0<Dimension, const double> const_f = f;
+              DiscreteFunctionP0<Dimension, const double> const_g{g};
+
+              SECTION("sum")
+              {
+                Array<double> sum_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
+
+              SECTION("difference")
+              {
+                Array<double> difference_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+
+              SECTION("product")
+              {
+                Array<double> product_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+                REQUIRE(same_values(f * g, product_values));
+                REQUIRE(same_values(const_f * g, product_values));
+                REQUIRE(same_values(f * const_g, product_values));
+                REQUIRE(same_values(const_f * const_g, product_values));
+              }
+
+              SECTION("ratio")
+              {
+                Array<double> ratio_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / g[cell_id]; });
+
+                REQUIRE(same_values(f / g, ratio_values));
+                REQUIRE(same_values(const_f / g, ratio_values));
+                REQUIRE(same_values(f / const_g, ratio_values));
+                REQUIRE(same_values(const_f / const_g, ratio_values));
+              }
+            }
+
+            SECTION("vector functions")
+            {
+              constexpr std::uint64_t VectorDimension = 2;
+
+              DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyVector<VectorDimension> X{x, 2 - x};
+                  f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+                });
 
-          SECTION("product")
-          {
-            Array<double> product_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+              DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> g{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyVector<VectorDimension> X{3 * x + 1, 2 + x};
+                  g[cell_id] = X;
+                });
 
-            REQUIRE(same_values(f * g, product_values));
-            REQUIRE(same_values(const_f * g, product_values));
-            REQUIRE(same_values(f * const_g, product_values));
-            REQUIRE(same_values(const_f * const_g, product_values));
-          }
+              DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+              DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_g{g};
 
-          SECTION("ratio")
-          {
-            Array<double> ratio_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / g[cell_id]; });
+              SECTION("sum")
+              {
+                Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
 
-            REQUIRE(same_values(f / g, ratio_values));
-            REQUIRE(same_values(const_f / g, ratio_values));
-            REQUIRE(same_values(f / const_g, ratio_values));
-            REQUIRE(same_values(const_f / const_g, ratio_values));
-          }
-        }
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
 
-        SECTION("vector functions")
-        {
-          constexpr std::uint64_t VectorDimension = 2;
+              SECTION("difference")
+              {
+                Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
 
-          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyVector<VectorDimension> X{x, 2 - x};
-              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
-            });
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+            }
 
-          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> g{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyVector<VectorDimension> X{3 * x + 1, 2 + x};
-              g[cell_id] = X;
-            });
+            SECTION("matrix functions")
+            {
+              constexpr std::uint64_t MatrixDimension = 2;
 
-          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
-          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_g{g};
+              DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
+                  f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
+                });
 
-          SECTION("sum")
-          {
-            Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+              DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> g{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyMatrix<MatrixDimension> A{3 * x + 1, 2 + x, 1 - 2 * x, 2 * x * x};
+                  g[cell_id] = A;
+                });
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
+              DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+              DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_g{g};
+
+              SECTION("sum")
+              {
+                Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
+
+              SECTION("difference")
+              {
+                Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+
+              SECTION("product")
+              {
+                Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+                REQUIRE(same_values(f * g, product_values));
+                REQUIRE(same_values(const_f * g, product_values));
+                REQUIRE(same_values(f * const_g, product_values));
+                REQUIRE(same_values(const_f * const_g, product_values));
+              }
+            }
           }
 
-          SECTION("difference")
+          SECTION("external operators")
           {
-            Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+            SECTION("scalar functions")
+            {
+              DiscreteFunctionP0<Dimension, double> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  f[cell_id]     = std::abs(2 * x) + 1;
+                });
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
+              const double a = 3;
+
+              DiscreteFunctionP0<Dimension, const double> const_f = f;
+
+              SECTION("sum")
+              {
+                {
+                  Array<double> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = a + f[cell_id]; });
+
+                  REQUIRE(same_values(a + f, sum_values));
+                  REQUIRE(same_values(a + const_f, sum_values));
+                }
+                {
+                  Array<double> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + a; });
+
+                  REQUIRE(same_values(f + a, sum_values));
+                  REQUIRE(same_values(const_f + a, sum_values));
+                }
+              }
+
+              SECTION("difference")
+              {
+                {
+                  Array<double> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = a - f[cell_id]; });
+                  REQUIRE(same_values(a - f, difference_values));
+                  REQUIRE(same_values(a - const_f, difference_values));
+                }
+
+                {
+                  Array<double> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - a; });
+                  REQUIRE(same_values(f - a, difference_values));
+                  REQUIRE(same_values(const_f - a, difference_values));
+                }
+              }
+
+              SECTION("product")
+              {
+                {
+                  Array<double> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+                {
+                  Array<double> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * a; });
+
+                  REQUIRE(same_values(f * a, product_values));
+                  REQUIRE(same_values(const_f * a, product_values));
+                }
+
+                {
+                  Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+                  const TinyVector<3> v{1, 2, 3};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v; });
+
+                  REQUIRE(same_values(f * v, product_values));
+                  REQUIRE(same_values(const_f * v, product_values));
+                }
+
+                {
+                  Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+                  DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      v[cell_id]     = TinyVector<3>{x, 2 * x, 1 - x};
+                    });
+
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v[cell_id]; });
+
+                  REQUIRE(same_values(f * v, product_values));
+                  REQUIRE(same_values(const_f * v, product_values));
+                }
+
+                {
+                  Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+                  const TinyMatrix<2> A{1, 2, 3, 4};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+                  REQUIRE(same_values(f * A, product_values));
+                  REQUIRE(same_values(const_f * A, product_values));
+                }
+
+                {
+                  Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+                  DiscreteFunctionP0<Dimension, TinyMatrix<2>> M{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                    });
+
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * M[cell_id]; });
+
+                  REQUIRE(same_values(f * M, product_values));
+                  REQUIRE(same_values(const_f * M, product_values));
+                }
+              }
+
+              SECTION("ratio")
+              {
+                {
+                  Array<double> ratio_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = a / f[cell_id]; });
+
+                  REQUIRE(same_values(a / f, ratio_values));
+                  REQUIRE(same_values(a / const_f, ratio_values));
+                }
+                {
+                  Array<double> ratio_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / a; });
+
+                  REQUIRE(same_values(f / a, ratio_values));
+                  REQUIRE(same_values(const_f / a, ratio_values));
+                }
+              }
+            }
+
+            SECTION("vector functions")
+            {
+              constexpr std::uint64_t VectorDimension = 2;
+
+              DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyVector<VectorDimension> X{x, 2 - x};
+                  f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+                });
+
+              DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+
+              SECTION("sum")
+              {
+                const TinyVector<VectorDimension> v{1, 2};
+                {
+                  Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = v + f[cell_id]; });
+
+                  REQUIRE(same_values(v + f, sum_values));
+                  REQUIRE(same_values(v + const_f, sum_values));
+                }
+                {
+                  Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + v; });
+
+                  REQUIRE(same_values(f + v, sum_values));
+                  REQUIRE(same_values(const_f + v, sum_values));
+                }
+              }
+
+              SECTION("difference")
+              {
+                const TinyVector<VectorDimension> v{1, 2};
+                {
+                  Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = v - f[cell_id]; });
+
+                  REQUIRE(same_values(v - f, difference_values));
+                  REQUIRE(same_values(v - const_f, difference_values));
+                }
+                {
+                  Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - v; });
+
+                  REQUIRE(same_values(f - v, difference_values));
+                  REQUIRE(same_values(const_f - v, difference_values));
+                }
+              }
+
+              SECTION("product")
+              {
+                {
+                  const double a = 2.3;
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  DiscreteFunctionP0<Dimension, double> a{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      a[cell_id]     = 2 * x * x - 1;
+                    });
+
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  const TinyMatrix<VectorDimension> A{1, 2, 3, 4};
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+                  REQUIRE(same_values(A * f, product_values));
+                  REQUIRE(same_values(A * const_f, product_values));
+                }
+
+                {
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  DiscreteFunctionP0<Dimension, TinyMatrix<VectorDimension>> M{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                    });
+
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = M[cell_id] * f[cell_id]; });
+
+                  REQUIRE(same_values(M * f, product_values));
+                  REQUIRE(same_values(M * const_f, product_values));
+                }
+              }
+            }
+
+            SECTION("matrix functions")
+            {
+              constexpr std::uint64_t MatrixDimension = 2;
+
+              DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyMatrix<MatrixDimension> X{x, 2 - x, x * x, x * 3};
+                  f[cell_id] = 2 * X + TinyMatrix<2>{1, 2, 3, 4};
+                });
+
+              DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+
+              SECTION("sum")
+              {
+                const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                {
+                  Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = A + f[cell_id]; });
+
+                  REQUIRE(same_values(A + f, sum_values));
+                  REQUIRE(same_values(A + const_f, sum_values));
+                }
+                {
+                  Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + A; });
+
+                  REQUIRE(same_values(f + A, sum_values));
+                  REQUIRE(same_values(const_f + A, sum_values));
+                }
+              }
+
+              SECTION("difference")
+              {
+                const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                {
+                  Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = A - f[cell_id]; });
+
+                  REQUIRE(same_values(A - f, difference_values));
+                  REQUIRE(same_values(A - const_f, difference_values));
+                }
+                {
+                  Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - A; });
+
+                  REQUIRE(same_values(f - A, difference_values));
+                  REQUIRE(same_values(const_f - A, difference_values));
+                }
+              }
+
+              SECTION("product")
+              {
+                {
+                  const double a = 2.3;
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  DiscreteFunctionP0<Dimension, double> a{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      a[cell_id]     = 2 * x * x - 1;
+                    });
+
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+                  REQUIRE(same_values(A * f, product_values));
+                  REQUIRE(same_values(A * const_f, product_values));
+                }
+
+                {
+                  const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+                  REQUIRE(same_values(f * A, product_values));
+                  REQUIRE(same_values(const_f * A, product_values));
+                }
+              }
+            }
           }
         }
+      }
+    }
 
-        SECTION("matrix functions")
-        {
-          constexpr std::uint64_t MatrixDimension = 2;
+    SECTION("2D")
+    {
+      constexpr size_t Dimension = 2;
 
-          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
-              f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
-            });
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> g{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyMatrix<MatrixDimension> A{3 * x + 1, 2 + x, 1 - 2 * x, 2 * x * x};
-              g[cell_id] = A;
-            });
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
 
-          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
-          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_g{g};
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
 
-          SECTION("sum")
+          SECTION("inner operators")
           {
-            Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+            SECTION("scalar functions")
+            {
+              DiscreteFunctionP0<Dimension, double> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  f[cell_id]     = 2 * x + y + 1;
+                });
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
-          }
+              DiscreteFunctionP0<Dimension, double> g{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  g[cell_id]     = std::abs((x + 1) * (x - 2) + y * (1 + y)) + 1;
+                });
 
-          SECTION("difference")
-          {
-            Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+              DiscreteFunctionP0<Dimension, const double> const_f = f;
+              DiscreteFunctionP0<Dimension, const double> const_g{g};
+
+              SECTION("sum")
+              {
+                Array<double> sum_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
+
+              SECTION("difference")
+              {
+                Array<double> difference_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+
+              SECTION("product")
+              {
+                Array<double> product_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+                REQUIRE(same_values(f * g, product_values));
+                REQUIRE(same_values(const_f * g, product_values));
+                REQUIRE(same_values(f * const_g, product_values));
+                REQUIRE(same_values(const_f * const_g, product_values));
+              }
+
+              SECTION("ratio")
+              {
+                Array<double> ratio_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / g[cell_id]; });
+
+                REQUIRE(same_values(f / g, ratio_values));
+                REQUIRE(same_values(const_f / g, ratio_values));
+                REQUIRE(same_values(f / const_g, ratio_values));
+                REQUIRE(same_values(const_f / const_g, ratio_values));
+              }
+            }
+
+            SECTION("vector functions")
+            {
+              constexpr std::uint64_t VectorDimension = 2;
+
+              DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyVector<VectorDimension> X{x, 2 - x};
+                  f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+                });
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
-          }
+              DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> g{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyVector<VectorDimension> X{3 * x + 1, 2 + x};
+                  g[cell_id] = X;
+                });
 
-          SECTION("product")
-          {
-            Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+              DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+              DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_g{g};
 
-            REQUIRE(same_values(f * g, product_values));
-            REQUIRE(same_values(const_f * g, product_values));
-            REQUIRE(same_values(f * const_g, product_values));
-            REQUIRE(same_values(const_f * const_g, product_values));
-          }
-        }
-      }
+              SECTION("sum")
+              {
+                Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
 
-      SECTION("external operators")
-      {
-        SECTION("scalar functions")
-        {
-          DiscreteFunctionP0<Dimension, double> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              f[cell_id]     = std::abs(2 * x) + 1;
-            });
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
 
-          const double a = 3;
+              SECTION("difference")
+              {
+                Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
 
-          DiscreteFunctionP0<Dimension, const double> const_f = f;
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+            }
 
-          SECTION("sum")
-          {
+            SECTION("matrix functions")
             {
-              Array<double> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = a + f[cell_id]; });
+              constexpr std::uint64_t MatrixDimension = 2;
 
-              REQUIRE(same_values(a + f, sum_values));
-              REQUIRE(same_values(a + const_f, sum_values));
-            }
-            {
-              Array<double> sum_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
               parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + a; });
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
+                  f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
+                });
 
-              REQUIRE(same_values(f + a, sum_values));
-              REQUIRE(same_values(const_f + a, sum_values));
-            }
-          }
-
-          SECTION("difference")
-          {
-            {
-              Array<double> difference_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> g{mesh};
               parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = a - f[cell_id]; });
-              REQUIRE(same_values(a - f, difference_values));
-              REQUIRE(same_values(a - const_f, difference_values));
-            }
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyMatrix<MatrixDimension> A{3 * x + 1, 2 + x, 1 - 2 * x, 2 * x * x};
+                  g[cell_id] = A;
+                });
 
-            {
-              Array<double> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - a; });
-              REQUIRE(same_values(f - a, difference_values));
-              REQUIRE(same_values(const_f - a, difference_values));
+              DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+              DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_g{g};
+
+              SECTION("sum")
+              {
+                Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
+
+              SECTION("difference")
+              {
+                Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+
+              SECTION("product")
+              {
+                Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+                REQUIRE(same_values(f * g, product_values));
+                REQUIRE(same_values(const_f * g, product_values));
+                REQUIRE(same_values(f * const_g, product_values));
+                REQUIRE(same_values(const_f * const_g, product_values));
+              }
             }
           }
 
-          SECTION("product")
+          SECTION("external operators")
           {
+            SECTION("scalar functions")
             {
-              Array<double> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
-
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
-            {
-              Array<double> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * a; });
-
-              REQUIRE(same_values(f * a, product_values));
-              REQUIRE(same_values(const_f * a, product_values));
-            }
-
-            {
-              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
-              const TinyVector<3> v{1, 2, 3};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v; });
-
-              REQUIRE(same_values(f * v, product_values));
-              REQUIRE(same_values(const_f * v, product_values));
-            }
-
-            {
-              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
-              DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+              DiscreteFunctionP0<Dimension, double> f{mesh};
               parallel_for(
                 mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                   const double x = xj[cell_id][0];
-                  v[cell_id]     = TinyVector<3>{x, 2 * x, 1 - x};
+                  const double y = xj[cell_id][1];
+                  f[cell_id]     = std::abs(2 * x + y) + 1;
                 });
 
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v[cell_id]; });
-
-              REQUIRE(same_values(f * v, product_values));
-              REQUIRE(same_values(const_f * v, product_values));
-            }
-
-            {
-              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
-              const TinyMatrix<2> A{1, 2, 3, 4};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
-
-              REQUIRE(same_values(f * A, product_values));
-              REQUIRE(same_values(const_f * A, product_values));
-            }
-
-            {
-              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
-              DiscreteFunctionP0<Dimension, TinyMatrix<2>> M{mesh};
+              const double a = 3;
+
+              DiscreteFunctionP0<Dimension, const double> const_f = f;
+
+              SECTION("sum")
+              {
+                {
+                  Array<double> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = a + f[cell_id]; });
+
+                  REQUIRE(same_values(a + f, sum_values));
+                  REQUIRE(same_values(a + const_f, sum_values));
+                }
+                {
+                  Array<double> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + a; });
+
+                  REQUIRE(same_values(f + a, sum_values));
+                  REQUIRE(same_values(const_f + a, sum_values));
+                }
+              }
+
+              SECTION("difference")
+              {
+                {
+                  Array<double> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = a - f[cell_id]; });
+                  REQUIRE(same_values(a - f, difference_values));
+                  REQUIRE(same_values(a - const_f, difference_values));
+                }
+
+                {
+                  Array<double> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - a; });
+                  REQUIRE(same_values(f - a, difference_values));
+                  REQUIRE(same_values(const_f - a, difference_values));
+                }
+              }
+
+              SECTION("product")
+              {
+                {
+                  Array<double> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+                {
+                  Array<double> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * a; });
+
+                  REQUIRE(same_values(f * a, product_values));
+                  REQUIRE(same_values(const_f * a, product_values));
+                }
+
+                {
+                  Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+                  const TinyVector<3> v{1, 2, 3};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v; });
+
+                  REQUIRE(same_values(f * v, product_values));
+                  REQUIRE(same_values(const_f * v, product_values));
+                }
+
+                {
+                  Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+                  DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      v[cell_id]     = TinyVector<3>{x, 2 * x, 1 - x};
+                    });
+
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v[cell_id]; });
+
+                  REQUIRE(same_values(f * v, product_values));
+                  REQUIRE(same_values(const_f * v, product_values));
+                }
+
+                {
+                  Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+                  const TinyMatrix<2> A{1, 2, 3, 4};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+                  REQUIRE(same_values(f * A, product_values));
+                  REQUIRE(same_values(const_f * A, product_values));
+                }
+
+                {
+                  Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+                  DiscreteFunctionP0<Dimension, TinyMatrix<2>> M{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                    });
+
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * M[cell_id]; });
+
+                  REQUIRE(same_values(f * M, product_values));
+                  REQUIRE(same_values(const_f * M, product_values));
+                }
+              }
+
+              SECTION("ratio")
+              {
+                {
+                  Array<double> ratio_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = a / f[cell_id]; });
+
+                  REQUIRE(same_values(a / f, ratio_values));
+                  REQUIRE(same_values(a / const_f, ratio_values));
+                }
+                {
+                  Array<double> ratio_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / a; });
+
+                  REQUIRE(same_values(f / a, ratio_values));
+                  REQUIRE(same_values(const_f / a, ratio_values));
+                }
+              }
+            }
+
+            SECTION("vector functions")
+            {
+              constexpr std::uint64_t VectorDimension = 2;
+
+              DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
               parallel_for(
                 mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                   const double x = xj[cell_id][0];
-                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                  const double y = xj[cell_id][1];
+                  const TinyVector<VectorDimension> X{x + y, 2 - x * y};
+                  f[cell_id] = 2 * X + TinyVector<2>{1, 2};
                 });
 
+              DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+
+              SECTION("sum")
+              {
+                const TinyVector<VectorDimension> v{1, 2};
+                {
+                  Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = v + f[cell_id]; });
+
+                  REQUIRE(same_values(v + f, sum_values));
+                  REQUIRE(same_values(v + const_f, sum_values));
+                }
+                {
+                  Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + v; });
+
+                  REQUIRE(same_values(f + v, sum_values));
+                  REQUIRE(same_values(const_f + v, sum_values));
+                }
+              }
+
+              SECTION("difference")
+              {
+                const TinyVector<VectorDimension> v{1, 2};
+                {
+                  Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = v - f[cell_id]; });
+
+                  REQUIRE(same_values(v - f, difference_values));
+                  REQUIRE(same_values(v - const_f, difference_values));
+                }
+                {
+                  Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - v; });
+
+                  REQUIRE(same_values(f - v, difference_values));
+                  REQUIRE(same_values(const_f - v, difference_values));
+                }
+              }
+
+              SECTION("product")
+              {
+                {
+                  const double a = 2.3;
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  DiscreteFunctionP0<Dimension, double> a{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      a[cell_id]     = 2 * x * x - 1;
+                    });
+
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  const TinyMatrix<VectorDimension> A{1, 2, 3, 4};
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+                  REQUIRE(same_values(A * f, product_values));
+                  REQUIRE(same_values(A * const_f, product_values));
+                }
+
+                {
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  DiscreteFunctionP0<Dimension, TinyMatrix<VectorDimension>> M{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                    });
+
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = M[cell_id] * f[cell_id]; });
+
+                  REQUIRE(same_values(M * f, product_values));
+                  REQUIRE(same_values(M * const_f, product_values));
+                }
+              }
+            }
+
+            SECTION("matrix functions")
+            {
+              constexpr std::uint64_t MatrixDimension = 2;
+
+              DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
               parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * M[cell_id]; });
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  const TinyMatrix<MatrixDimension> X{x, 2 - y, x * y, y * 3};
+                  f[cell_id] = 2 * X + TinyMatrix<2>{1, 2, 3, 4};
+                });
 
-              REQUIRE(same_values(f * M, product_values));
-              REQUIRE(same_values(const_f * M, product_values));
+              DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+
+              SECTION("sum")
+              {
+                const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                {
+                  Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = A + f[cell_id]; });
+
+                  REQUIRE(same_values(A + f, sum_values));
+                  REQUIRE(same_values(A + const_f, sum_values));
+                }
+                {
+                  Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + A; });
+
+                  REQUIRE(same_values(f + A, sum_values));
+                  REQUIRE(same_values(const_f + A, sum_values));
+                }
+              }
+
+              SECTION("difference")
+              {
+                const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                {
+                  Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = A - f[cell_id]; });
+
+                  REQUIRE(same_values(A - f, difference_values));
+                  REQUIRE(same_values(A - const_f, difference_values));
+                }
+                {
+                  Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - A; });
+
+                  REQUIRE(same_values(f - A, difference_values));
+                  REQUIRE(same_values(const_f - A, difference_values));
+                }
+              }
+
+              SECTION("product")
+              {
+                {
+                  const double a = 2.3;
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  DiscreteFunctionP0<Dimension, double> a{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      a[cell_id]     = 2 * x * x - 1;
+                    });
+
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+                  REQUIRE(same_values(A * f, product_values));
+                  REQUIRE(same_values(A * const_f, product_values));
+                }
+
+                {
+                  const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+                  REQUIRE(same_values(f * A, product_values));
+                  REQUIRE(same_values(const_f * A, product_values));
+                }
+              }
             }
           }
+        }
+      }
+    }
 
-          SECTION("ratio")
-          {
-            {
-              Array<double> ratio_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = a / f[cell_id]; });
-
-              REQUIRE(same_values(a / f, ratio_values));
-              REQUIRE(same_values(a / const_f, ratio_values));
-            }
-            {
-              Array<double> ratio_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / a; });
+    SECTION("3D")
+    {
+      constexpr size_t Dimension = 3;
 
-              REQUIRE(same_values(f / a, ratio_values));
-              REQUIRE(same_values(const_f / a, ratio_values));
-            }
-          }
-        }
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-        SECTION("vector functions")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          constexpr std::uint64_t VectorDimension = 2;
-
-          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyVector<VectorDimension> X{x, 2 - x};
-              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
-            });
+          auto mesh = named_mesh.mesh();
 
-          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
 
-          SECTION("sum")
+          SECTION("inner operators")
           {
-            const TinyVector<VectorDimension> v{1, 2};
+            SECTION("scalar functions")
             {
-              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, double> f{mesh};
               parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = v + f[cell_id]; });
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  const double z = xj[cell_id][2];
+                  f[cell_id]     = 2 * x + y - z;
+                });
 
-              REQUIRE(same_values(v + f, sum_values));
-              REQUIRE(same_values(v + const_f, sum_values));
-            }
-            {
-              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, double> g{mesh};
               parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + v; });
-
-              REQUIRE(same_values(f + v, sum_values));
-              REQUIRE(same_values(const_f + v, sum_values));
-            }
-          }
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  const double z = xj[cell_id][2];
+                  g[cell_id]     = std::abs((x + 1) * (x - 2) + y * (1 + y) + 2 * z) + 1;
+                });
 
-          SECTION("difference")
-          {
-            const TinyVector<VectorDimension> v{1, 2};
-            {
-              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, const double> const_f = f;
+              DiscreteFunctionP0<Dimension, const double> const_g{g};
+
+              SECTION("sum")
+              {
+                Array<double> sum_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
+
+              SECTION("difference")
+              {
+                Array<double> difference_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+
+              SECTION("product")
+              {
+                Array<double> product_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+                REQUIRE(same_values(f * g, product_values));
+                REQUIRE(same_values(const_f * g, product_values));
+                REQUIRE(same_values(f * const_g, product_values));
+                REQUIRE(same_values(const_f * const_g, product_values));
+              }
+
+              SECTION("ratio")
+              {
+                Array<double> ratio_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / g[cell_id]; });
+
+                REQUIRE(same_values(f / g, ratio_values));
+                REQUIRE(same_values(const_f / g, ratio_values));
+                REQUIRE(same_values(f / const_g, ratio_values));
+                REQUIRE(same_values(const_f / const_g, ratio_values));
+              }
+            }
+
+            SECTION("vector functions")
+            {
+              constexpr std::uint64_t VectorDimension = 2;
+
+              DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
               parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = v - f[cell_id]; });
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyVector<VectorDimension> X{x, 2 - x};
+                  f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+                });
 
-              REQUIRE(same_values(v - f, difference_values));
-              REQUIRE(same_values(v - const_f, difference_values));
-            }
-            {
-              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> g{mesh};
               parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - v; });
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyVector<VectorDimension> X{3 * x + 1, 2 + x};
+                  g[cell_id] = X;
+                });
 
-              REQUIRE(same_values(f - v, difference_values));
-              REQUIRE(same_values(const_f - v, difference_values));
-            }
-          }
+              DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+              DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_g{g};
 
-          SECTION("product")
-          {
-            {
-              const double a = 2.3;
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+              SECTION("sum")
+              {
+                Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
 
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
+              SECTION("difference")
+              {
+                Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
             }
 
+            SECTION("matrix functions")
             {
-              DiscreteFunctionP0<Dimension, double> a{mesh};
+              constexpr std::uint64_t MatrixDimension = 2;
+
+              DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
               parallel_for(
                 mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                   const double x = xj[cell_id][0];
-                  a[cell_id]     = 2 * x * x - 1;
+                  const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
+                  f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
                 });
 
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> g{mesh};
               parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const TinyMatrix<MatrixDimension> A{3 * x + 1, 2 + x, 1 - 2 * x, 2 * x * x};
+                  g[cell_id] = A;
+                });
 
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
+              DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+              DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_g{g};
+
+              SECTION("sum")
+              {
+                Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
+
+              SECTION("difference")
+              {
+                Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+
+              SECTION("product")
+              {
+                Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                parallel_for(
+                  mesh->numberOfCells(),
+                  PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+                REQUIRE(same_values(f * g, product_values));
+                REQUIRE(same_values(const_f * g, product_values));
+                REQUIRE(same_values(f * const_g, product_values));
+                REQUIRE(same_values(const_f * const_g, product_values));
+              }
             }
+          }
 
+          SECTION("external operators")
+          {
+            SECTION("scalar functions")
             {
-              const TinyMatrix<VectorDimension> A{1, 2, 3, 4};
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, double> f{mesh};
               parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
-
-              REQUIRE(same_values(A * f, product_values));
-              REQUIRE(same_values(A * const_f, product_values));
-            }
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  const double z = xj[cell_id][2];
+                  f[cell_id]     = std::abs(2 * x + y * z) + 1;
+                });
 
-            {
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
-              DiscreteFunctionP0<Dimension, TinyMatrix<VectorDimension>> M{mesh};
+              const double a = 3;
+
+              DiscreteFunctionP0<Dimension, const double> const_f = f;
+
+              SECTION("sum")
+              {
+                {
+                  Array<double> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = a + f[cell_id]; });
+
+                  REQUIRE(same_values(a + f, sum_values));
+                  REQUIRE(same_values(a + const_f, sum_values));
+                }
+                {
+                  Array<double> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + a; });
+
+                  REQUIRE(same_values(f + a, sum_values));
+                  REQUIRE(same_values(const_f + a, sum_values));
+                }
+              }
+
+              SECTION("difference")
+              {
+                {
+                  Array<double> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = a - f[cell_id]; });
+                  REQUIRE(same_values(a - f, difference_values));
+                  REQUIRE(same_values(a - const_f, difference_values));
+                }
+
+                {
+                  Array<double> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - a; });
+                  REQUIRE(same_values(f - a, difference_values));
+                  REQUIRE(same_values(const_f - a, difference_values));
+                }
+              }
+
+              SECTION("product")
+              {
+                {
+                  Array<double> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+                {
+                  Array<double> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * a; });
+
+                  REQUIRE(same_values(f * a, product_values));
+                  REQUIRE(same_values(const_f * a, product_values));
+                }
+
+                {
+                  Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+                  const TinyVector<3> v{1, 2, 3};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v; });
+
+                  REQUIRE(same_values(f * v, product_values));
+                  REQUIRE(same_values(const_f * v, product_values));
+                }
+
+                {
+                  Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+                  DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      v[cell_id]     = TinyVector<3>{x, 2 * x, 1 - x};
+                    });
+
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v[cell_id]; });
+
+                  REQUIRE(same_values(f * v, product_values));
+                  REQUIRE(same_values(const_f * v, product_values));
+                }
+
+                {
+                  Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+                  const TinyMatrix<2> A{1, 2, 3, 4};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+                  REQUIRE(same_values(f * A, product_values));
+                  REQUIRE(same_values(const_f * A, product_values));
+                }
+
+                {
+                  Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+                  DiscreteFunctionP0<Dimension, TinyMatrix<2>> M{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                    });
+
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * M[cell_id]; });
+
+                  REQUIRE(same_values(f * M, product_values));
+                  REQUIRE(same_values(const_f * M, product_values));
+                }
+              }
+
+              SECTION("ratio")
+              {
+                {
+                  Array<double> ratio_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = a / f[cell_id]; });
+
+                  REQUIRE(same_values(a / f, ratio_values));
+                  REQUIRE(same_values(a / const_f, ratio_values));
+                }
+                {
+                  Array<double> ratio_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / a; });
+
+                  REQUIRE(same_values(f / a, ratio_values));
+                  REQUIRE(same_values(const_f / a, ratio_values));
+                }
+              }
+            }
+
+            SECTION("vector functions")
+            {
+              constexpr std::uint64_t VectorDimension = 2;
+
+              DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
               parallel_for(
                 mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                   const double x = xj[cell_id][0];
-                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                  const double y = xj[cell_id][1];
+                  const double z = xj[cell_id][2];
+                  const TinyVector<VectorDimension> X{x + y - z, 2 - x * y};
+                  f[cell_id] = 2 * X + TinyVector<2>{1, 2};
                 });
 
+              DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+
+              SECTION("sum")
+              {
+                const TinyVector<VectorDimension> v{1, 2};
+                {
+                  Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = v + f[cell_id]; });
+
+                  REQUIRE(same_values(v + f, sum_values));
+                  REQUIRE(same_values(v + const_f, sum_values));
+                }
+                {
+                  Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + v; });
+
+                  REQUIRE(same_values(f + v, sum_values));
+                  REQUIRE(same_values(const_f + v, sum_values));
+                }
+              }
+
+              SECTION("difference")
+              {
+                const TinyVector<VectorDimension> v{1, 2};
+                {
+                  Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = v - f[cell_id]; });
+
+                  REQUIRE(same_values(v - f, difference_values));
+                  REQUIRE(same_values(v - const_f, difference_values));
+                }
+                {
+                  Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - v; });
+
+                  REQUIRE(same_values(f - v, difference_values));
+                  REQUIRE(same_values(const_f - v, difference_values));
+                }
+              }
+
+              SECTION("product")
+              {
+                {
+                  const double a = 2.3;
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  DiscreteFunctionP0<Dimension, double> a{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      a[cell_id]     = 2 * x * x - 1;
+                    });
+
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  const TinyMatrix<VectorDimension> A{1, 2, 3, 4};
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+                  REQUIRE(same_values(A * f, product_values));
+                  REQUIRE(same_values(A * const_f, product_values));
+                }
+
+                {
+                  Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+                  DiscreteFunctionP0<Dimension, TinyMatrix<VectorDimension>> M{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                    });
+
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = M[cell_id] * f[cell_id]; });
+
+                  REQUIRE(same_values(M * f, product_values));
+                  REQUIRE(same_values(M * const_f, product_values));
+                }
+              }
+            }
+
+            SECTION("matrix functions")
+            {
+              constexpr std::uint64_t MatrixDimension = 2;
+
+              DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
               parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = M[cell_id] * f[cell_id]; });
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  const double z = xj[cell_id][2];
+                  const TinyMatrix<MatrixDimension> X{x, 2 - y, x * y, y * z + 3};
+                  f[cell_id] = 2 * X + TinyMatrix<2>{1, 2, 3, 4};
+                });
 
-              REQUIRE(same_values(M * f, product_values));
-              REQUIRE(same_values(M * const_f, product_values));
+              DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+
+              SECTION("sum")
+              {
+                const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                {
+                  Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = A + f[cell_id]; });
+
+                  REQUIRE(same_values(A + f, sum_values));
+                  REQUIRE(same_values(A + const_f, sum_values));
+                }
+                {
+                  Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + A; });
+
+                  REQUIRE(same_values(f + A, sum_values));
+                  REQUIRE(same_values(const_f + A, sum_values));
+                }
+              }
+
+              SECTION("difference")
+              {
+                const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                {
+                  Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = A - f[cell_id]; });
+
+                  REQUIRE(same_values(A - f, difference_values));
+                  REQUIRE(same_values(A - const_f, difference_values));
+                }
+                {
+                  Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - A; });
+
+                  REQUIRE(same_values(f - A, difference_values));
+                  REQUIRE(same_values(const_f - A, difference_values));
+                }
+              }
+
+              SECTION("product")
+              {
+                {
+                  const double a = 2.3;
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  DiscreteFunctionP0<Dimension, double> a{mesh};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                      const double x = xj[cell_id][0];
+                      a[cell_id]     = 2 * x * x - 1;
+                    });
+
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(),
+                    PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+                  REQUIRE(same_values(a * f, product_values));
+                  REQUIRE(same_values(a * const_f, product_values));
+                }
+
+                {
+                  const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+                  REQUIRE(same_values(A * f, product_values));
+                  REQUIRE(same_values(A * const_f, product_values));
+                }
+
+                {
+                  const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+                  Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+                  parallel_for(
+                    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+                  REQUIRE(same_values(f * A, product_values));
+                  REQUIRE(same_values(const_f * A, product_values));
+                }
+              }
             }
           }
         }
+      }
+    }
+  }
 
-        SECTION("matrix functions")
-        {
-          constexpr std::uint64_t MatrixDimension = 2;
+  SECTION("math functions")
+  {
+#define CHECK_STD_MATH_FUNCTION(data_expression, FCT)                           \
+  {                                                                             \
+    DiscreteFunctionP0 data   = data_expression;                                \
+    DiscreteFunctionP0 result = FCT(data);                                      \
+    bool is_same              = true;                                           \
+    parallel_for(data.cellValues().numberOfItems(), [&](const CellId cell_id) { \
+      if (result[cell_id] != std::FCT(data[cell_id])) {                         \
+        is_same = false;                                                        \
+      }                                                                         \
+    });                                                                         \
+    REQUIRE(is_same);                                                           \
+  }
 
-          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyMatrix<MatrixDimension> X{x, 2 - x, x * x, x * 3};
-              f[cell_id] = 2 * X + TinyMatrix<2>{1, 2, 3, 4};
-            });
+#define CHECK_STD_BINARY_MATH_FUNCTION(lhs_expression, rhs_expression, FCT)    \
+  {                                                                            \
+    DiscreteFunctionP0 lhs    = lhs_expression;                                \
+    DiscreteFunctionP0 rhs    = rhs_expression;                                \
+    DiscreteFunctionP0 result = FCT(lhs, rhs);                                 \
+    using namespace std;                                                       \
+    bool is_same = true;                                                       \
+    parallel_for(lhs.cellValues().numberOfItems(), [&](const CellId cell_id) { \
+      if (result[cell_id] != FCT(lhs[cell_id], rhs[cell_id])) {                \
+        is_same = false;                                                       \
+      }                                                                        \
+    });                                                                        \
+    REQUIRE(is_same);                                                          \
+  }
 
-          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+#define CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(lhs, rhs_expression, FCT) \
+  {                                                                             \
+    DiscreteFunctionP0 rhs    = rhs_expression;                                 \
+    DiscreteFunctionP0 result = FCT(lhs, rhs);                                  \
+    bool is_same              = true;                                           \
+    using namespace std;                                                        \
+    parallel_for(rhs.cellValues().numberOfItems(), [&](const CellId cell_id) {  \
+      if (result[cell_id] != FCT(lhs, rhs[cell_id])) {                          \
+        is_same = false;                                                        \
+      }                                                                         \
+    });                                                                         \
+    REQUIRE(is_same);                                                           \
+  }
 
-          SECTION("sum")
-          {
-            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-            {
-              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = A + f[cell_id]; });
+#define CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(lhs_expression, rhs, FCT) \
+  {                                                                             \
+    DiscreteFunctionP0 lhs    = lhs_expression;                                 \
+    DiscreteFunctionP0 result = FCT(lhs, rhs);                                  \
+    bool is_same              = true;                                           \
+    using namespace std;                                                        \
+    parallel_for(lhs.cellValues().numberOfItems(), [&](const CellId cell_id) {  \
+      if (result[cell_id] != FCT(lhs[cell_id], rhs)) {                          \
+        is_same = false;                                                        \
+      }                                                                         \
+    });                                                                         \
+    REQUIRE(is_same);                                                           \
+  }
 
-              REQUIRE(same_values(A + f, sum_values));
-              REQUIRE(same_values(A + const_f, sum_values));
-            }
-            {
-              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + A; });
+    SECTION("1D")
+    {
+      constexpr size_t Dimension = 1;
+      std::array mesh_list       = MeshDataBaseForTests::get().all1DMeshes();
 
-              REQUIRE(same_values(f + A, sum_values));
-              REQUIRE(same_values(const_f + A, sum_values));
-            }
-          }
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
 
-          SECTION("difference")
-          {
-            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-            {
-              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = A - f[cell_id]; });
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
 
-              REQUIRE(same_values(A - f, difference_values));
-              REQUIRE(same_values(A - const_f, difference_values));
-            }
-            {
-              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - A; });
+          DiscreteFunctionP0<Dimension, double> positive_function{mesh};
+
+          parallel_for(
+            mesh->numberOfCells(),
+            PUGS_LAMBDA(const CellId cell_id) { positive_function[cell_id] = 1 + std::abs(xj[cell_id][0]); });
 
-              REQUIRE(same_values(f - A, difference_values));
-              REQUIRE(same_values(const_f - A, difference_values));
+          const double min_value = min(positive_function);
+          SECTION("min")
+          {
+            double local_min = std::numeric_limits<double>::max();
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              local_min = std::min(local_min, positive_function[cell_id]);
             }
+            REQUIRE(min_value == parallel::allReduceMin(local_min));
           }
 
-          SECTION("product")
+          const double max_value = max(positive_function);
+          SECTION("max")
           {
-            {
-              const double a = 2.3;
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
-
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
+            double local_max = -std::numeric_limits<double>::max();
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              local_max = std::max(local_max, positive_function[cell_id]);
             }
+            REQUIRE(max_value == parallel::allReduceMax(local_max));
+          }
 
-            {
-              DiscreteFunctionP0<Dimension, double> a{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  a[cell_id]     = 2 * x * x - 1;
-                });
-
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
-
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
+          REQUIRE(min_value < max_value);
 
-            {
-              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+          DiscreteFunctionP0 unsigned_function = positive_function - 0.5 * (min_value + max_value);
 
-              REQUIRE(same_values(A * f, product_values));
-              REQUIRE(same_values(A * const_f, product_values));
-            }
+          SECTION("sqrt")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, sqrt);
+          }
 
-            {
-              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+          SECTION("abs")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, abs);
+          }
 
-              REQUIRE(same_values(f * A, product_values));
-              REQUIRE(same_values(const_f * A, product_values));
-            }
+          SECTION("cos")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, cos);
           }
-        }
-      }
-    }
 
-    SECTION("2D")
-    {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
+          SECTION("sin")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, sin);
+          }
 
-      constexpr size_t Dimension = 2;
+          SECTION("tan")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, tan);
+          }
 
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+          DiscreteFunctionP0<Dimension, double> unit_function{mesh};
 
-      SECTION("inner operators")
-      {
-        SECTION("scalar functions")
-        {
-          DiscreteFunctionP0<Dimension, double> f{mesh};
           parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              f[cell_id]     = 2 * x + y + 1;
+            mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+              unit_function[cell_id] =
+                (2 * (positive_function[cell_id] - min_value) / (max_value - min_value) - 1) * 0.95;
             });
 
-          DiscreteFunctionP0<Dimension, double> g{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              g[cell_id]     = std::abs((x + 1) * (x - 2) + y * (1 + y)) + 1;
-            });
+          SECTION("acos")
+          {
+            CHECK_STD_MATH_FUNCTION(unit_function, acos);
+          }
 
-          DiscreteFunctionP0<Dimension, const double> const_f = f;
-          DiscreteFunctionP0<Dimension, const double> const_g{g};
+          SECTION("asin")
+          {
+            CHECK_STD_MATH_FUNCTION(unit_function, asin);
+          }
 
-          SECTION("sum")
+          SECTION("atan")
           {
-            Array<double> sum_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(unit_function, atan);
+          }
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
+          SECTION("cosh")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, cosh);
           }
 
-          SECTION("difference")
+          SECTION("sinh")
           {
-            Array<double> difference_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(positive_function, sinh);
+          }
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
+          SECTION("tanh")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, tanh);
           }
 
-          SECTION("product")
+          SECTION("acosh")
           {
-            Array<double> product_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(positive_function, acosh);
+          }
 
-            REQUIRE(same_values(f * g, product_values));
-            REQUIRE(same_values(const_f * g, product_values));
-            REQUIRE(same_values(f * const_g, product_values));
-            REQUIRE(same_values(const_f * const_g, product_values));
+          SECTION("asinh")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, asinh);
           }
 
-          SECTION("ratio")
+          SECTION("atanh")
           {
-            Array<double> ratio_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / g[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(unit_function, atanh);
+          }
 
-            REQUIRE(same_values(f / g, ratio_values));
-            REQUIRE(same_values(const_f / g, ratio_values));
-            REQUIRE(same_values(f / const_g, ratio_values));
-            REQUIRE(same_values(const_f / const_g, ratio_values));
+          SECTION("exp")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, exp);
           }
-        }
 
-        SECTION("vector functions")
-        {
-          constexpr std::uint64_t VectorDimension = 2;
+          SECTION("log")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, log);
+          }
 
-          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyVector<VectorDimension> X{x, 2 - x};
-              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
-            });
+          SECTION("max(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(cos(positive_function), sin(positive_function), max);
+          }
 
-          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> g{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyVector<VectorDimension> X{3 * x + 1, 2 + x};
-              g[cell_id] = X;
-            });
+          SECTION("max(0.2,vh)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.2, sin(positive_function), max);
+          }
 
-          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
-          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_g{g};
+          SECTION("max(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(cos(positive_function), 0.2, max);
+          }
 
-          SECTION("sum")
+          SECTION("atan2(uh,hv)")
           {
-            Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+            CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 2 + positive_function, atan2);
+          }
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
+          SECTION("atan2(0.5,uh)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, 2 + positive_function, atan2);
           }
 
-          SECTION("difference")
+          SECTION("atan2(uh,0.2)")
           {
-            Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(2 + cos(positive_function), 0.2, atan2);
+          }
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
+          SECTION("pow(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 0.5 * positive_function, pow);
           }
-        }
 
-        SECTION("matrix functions")
-        {
-          constexpr std::uint64_t MatrixDimension = 2;
+          SECTION("pow(uh,0.5)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, positive_function, pow);
+          }
 
-          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
-              f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
-            });
+          SECTION("pow(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(positive_function, 1.3, pow);
+          }
 
-          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> g{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyMatrix<MatrixDimension> A{3 * x + 1, 2 + x, 1 - 2 * x, 2 * x * x};
-              g[cell_id] = A;
-            });
+          SECTION("min(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), min);
+          }
 
-          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
-          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_g{g};
+          SECTION("min(uh,0.5)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, cos(positive_function), min);
+          }
 
-          SECTION("sum")
+          SECTION("min(uh,0.2)")
           {
-            Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.5, min);
+          }
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
+          SECTION("max(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), max);
           }
 
-          SECTION("difference")
+          SECTION("min(uh,0.5)")
           {
-            Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.1, cos(positive_function), max);
+          }
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
+          SECTION("min(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.1, max);
           }
 
-          SECTION("product")
+          SECTION("dot(uh,hv)")
           {
-            Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
             parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
+
+            DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
+              });
 
-            REQUIRE(same_values(f * g, product_values));
-            REQUIRE(same_values(const_f * g, product_values));
-            REQUIRE(same_values(f * const_g, product_values));
-            REQUIRE(same_values(const_f * const_g, product_values));
+            CHECK_STD_BINARY_MATH_FUNCTION(uh, vh, dot);
           }
-        }
-      }
 
-      SECTION("external operators")
-      {
-        SECTION("scalar functions")
-        {
-          DiscreteFunctionP0<Dimension, double> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              f[cell_id]     = std::abs(2 * x + y) + 1;
-            });
+          SECTION("dot(uh,v)")
+          {
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
 
-          const double a = 3;
+            const TinyVector<2> v{1, 2};
 
-          DiscreteFunctionP0<Dimension, const double> const_f = f;
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(uh, v, dot);
+          }
 
-          SECTION("sum")
+          SECTION("dot(u,hv)")
           {
-            {
-              Array<double> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = a + f[cell_id]; });
+            const TinyVector<2> u{3, -2};
 
-              REQUIRE(same_values(a + f, sum_values));
-              REQUIRE(same_values(a + const_f, sum_values));
-            }
-            {
-              Array<double> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + a; });
+            DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
+              });
 
-              REQUIRE(same_values(f + a, sum_values));
-              REQUIRE(same_values(const_f + a, sum_values));
-            }
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(u, vh, dot);
           }
 
-          SECTION("difference")
+          SECTION("scalar sum")
           {
-            {
-              Array<double> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = a - f[cell_id]; });
-              REQUIRE(same_values(a - f, difference_values));
-              REQUIRE(same_values(a - const_f, difference_values));
-            }
+            const CellValue<const double> cell_value = positive_function.cellValues();
 
-            {
-              Array<double> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - a; });
-              REQUIRE(same_values(f - a, difference_values));
-              REQUIRE(same_values(const_f - a, difference_values));
-            }
+            REQUIRE(sum(cell_value) == sum(positive_function));
           }
 
-          SECTION("product")
+          SECTION("vector sum")
           {
-            {
-              Array<double> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
-
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
-            {
-              Array<double> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * a; });
-
-              REQUIRE(same_values(f * a, product_values));
-              REQUIRE(same_values(const_f * a, product_values));
-            }
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
+            const CellValue<const TinyVector<2>> cell_value = uh.cellValues();
 
-            {
-              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
-              const TinyVector<3> v{1, 2, 3};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v; });
+            REQUIRE(sum(cell_value) == sum(uh));
+          }
 
-              REQUIRE(same_values(f * v, product_values));
-              REQUIRE(same_values(const_f * v, product_values));
-            }
+          SECTION("matrix sum")
+          {
+            DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 3 * x - 1};
+              });
+            const CellValue<const TinyMatrix<2>> cell_value = uh.cellValues();
 
-            {
-              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
-              DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  v[cell_id]     = TinyVector<3>{x, 2 * x, 1 - x};
-                });
+            REQUIRE(sum(cell_value) == sum(uh));
+          }
 
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v[cell_id]; });
+          SECTION("integrate scalar")
+          {
+            const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
 
-              REQUIRE(same_values(f * v, product_values));
-              REQUIRE(same_values(const_f * v, product_values));
-            }
+            CellValue<double> cell_value{mesh->connectivity()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                cell_value[cell_id] = cell_volume[cell_id] * positive_function[cell_id];
+              });
 
-            {
-              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
-              const TinyMatrix<2> A{1, 2, 3, 4};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+            REQUIRE(integrate(positive_function) == Catch::Approx(sum(cell_value)));
+          }
 
-              REQUIRE(same_values(f * A, product_values));
-              REQUIRE(same_values(const_f * A, product_values));
-            }
+          SECTION("integrate vector")
+          {
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
 
-            {
-              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
-              DiscreteFunctionP0<Dimension, TinyMatrix<2>> M{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
-                });
+            const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
 
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * M[cell_id]; });
+            CellValue<TinyVector<2>> cell_value{mesh->connectivity()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
 
-              REQUIRE(same_values(f * M, product_values));
-              REQUIRE(same_values(const_f * M, product_values));
-            }
+            REQUIRE(integrate(uh)[0] == Catch::Approx(sum(cell_value)[0]));
+            REQUIRE(integrate(uh)[1] == Catch::Approx(sum(cell_value)[1]));
           }
 
-          SECTION("ratio")
+          SECTION("integrate matrix")
           {
-            {
-              Array<double> ratio_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = a / f[cell_id]; });
+            DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 1 - x};
+              });
 
-              REQUIRE(same_values(a / f, ratio_values));
-              REQUIRE(same_values(a / const_f, ratio_values));
-            }
-            {
-              Array<double> ratio_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / a; });
+            const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
 
-              REQUIRE(same_values(f / a, ratio_values));
-              REQUIRE(same_values(const_f / a, ratio_values));
-            }
+            CellValue<TinyMatrix<2>> cell_value{mesh->connectivity()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
+
+            REQUIRE(integrate(uh)(0, 0) == Catch::Approx(sum(cell_value)(0, 0)));
+            REQUIRE(integrate(uh)(0, 1) == Catch::Approx(sum(cell_value)(0, 1)));
+            REQUIRE(integrate(uh)(1, 0) == Catch::Approx(sum(cell_value)(1, 0)));
+            REQUIRE(integrate(uh)(1, 1) == Catch::Approx(sum(cell_value)(1, 1)));
           }
         }
+      }
+    }
+
+    SECTION("2D")
+    {
+      constexpr size_t Dimension = 2;
 
-        SECTION("vector functions")
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          constexpr std::uint64_t VectorDimension = 2;
+          auto mesh = named_mesh.mesh();
 
-          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              const TinyVector<VectorDimension> X{x + y, 2 - x * y};
-              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
-            });
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
 
-          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+          DiscreteFunctionP0<Dimension, double> positive_function{mesh};
 
-          SECTION("sum")
-          {
-            const TinyVector<VectorDimension> v{1, 2};
-            {
-              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = v + f[cell_id]; });
+          parallel_for(
+            mesh->numberOfCells(),
+            PUGS_LAMBDA(const CellId cell_id) { positive_function[cell_id] = 1 + std::abs(xj[cell_id][0]); });
 
-              REQUIRE(same_values(v + f, sum_values));
-              REQUIRE(same_values(v + const_f, sum_values));
+          const double min_value = min(positive_function);
+          SECTION("min")
+          {
+            double local_min = std::numeric_limits<double>::max();
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              local_min = std::min(local_min, positive_function[cell_id]);
             }
-            {
-              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + v; });
+            REQUIRE(min_value == parallel::allReduceMin(local_min));
+          }
 
-              REQUIRE(same_values(f + v, sum_values));
-              REQUIRE(same_values(const_f + v, sum_values));
+          const double max_value = max(positive_function);
+          SECTION("max")
+          {
+            double local_max = -std::numeric_limits<double>::max();
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              local_max = std::max(local_max, positive_function[cell_id]);
             }
+            REQUIRE(max_value == parallel::allReduceMax(local_max));
           }
 
-          SECTION("difference")
-          {
-            const TinyVector<VectorDimension> v{1, 2};
-            {
-              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = v - f[cell_id]; });
+          REQUIRE(min_value < max_value);
 
-              REQUIRE(same_values(v - f, difference_values));
-              REQUIRE(same_values(v - const_f, difference_values));
-            }
-            {
-              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - v; });
+          DiscreteFunctionP0 unsigned_function = positive_function - 0.5 * (min_value + max_value);
 
-              REQUIRE(same_values(f - v, difference_values));
-              REQUIRE(same_values(const_f - v, difference_values));
-            }
+          SECTION("sqrt")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, sqrt);
           }
 
-          SECTION("product")
+          SECTION("abs")
           {
-            {
-              const double a = 2.3;
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(positive_function, abs);
+          }
 
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
+          SECTION("cos")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, cos);
+          }
 
-            {
-              DiscreteFunctionP0<Dimension, double> a{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  a[cell_id]     = 2 * x * x - 1;
-                });
+          SECTION("sin")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, sin);
+          }
 
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+          SECTION("tan")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, tan);
+          }
 
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
+          DiscreteFunctionP0<Dimension, double> unit_function{mesh};
 
-            {
-              const TinyMatrix<VectorDimension> A{1, 2, 3, 4};
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+              unit_function[cell_id] =
+                (2 * (positive_function[cell_id] - min_value) / (max_value - min_value) - 1) * 0.95;
+            });
 
-              REQUIRE(same_values(A * f, product_values));
-              REQUIRE(same_values(A * const_f, product_values));
-            }
+          SECTION("acos")
+          {
+            CHECK_STD_MATH_FUNCTION(unit_function, acos);
+          }
 
-            {
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
-              DiscreteFunctionP0<Dimension, TinyMatrix<VectorDimension>> M{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
-                });
+          SECTION("asin")
+          {
+            CHECK_STD_MATH_FUNCTION(unit_function, asin);
+          }
 
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = M[cell_id] * f[cell_id]; });
+          SECTION("atan")
+          {
+            CHECK_STD_MATH_FUNCTION(unit_function, atan);
+          }
 
-              REQUIRE(same_values(M * f, product_values));
-              REQUIRE(same_values(M * const_f, product_values));
-            }
+          SECTION("cosh")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, cosh);
           }
-        }
 
-        SECTION("matrix functions")
-        {
-          constexpr std::uint64_t MatrixDimension = 2;
+          SECTION("sinh")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, sinh);
+          }
 
-          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              const TinyMatrix<MatrixDimension> X{x, 2 - y, x * y, y * 3};
-              f[cell_id] = 2 * X + TinyMatrix<2>{1, 2, 3, 4};
-            });
+          SECTION("tanh")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, tanh);
+          }
 
-          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+          SECTION("acosh")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, acosh);
+          }
 
-          SECTION("sum")
+          SECTION("asinh")
           {
-            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-            {
-              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = A + f[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(positive_function, asinh);
+          }
 
-              REQUIRE(same_values(A + f, sum_values));
-              REQUIRE(same_values(A + const_f, sum_values));
-            }
-            {
-              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + A; });
+          SECTION("atanh")
+          {
+            CHECK_STD_MATH_FUNCTION(unit_function, atanh);
+          }
 
-              REQUIRE(same_values(f + A, sum_values));
-              REQUIRE(same_values(const_f + A, sum_values));
-            }
+          SECTION("exp")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, exp);
           }
 
-          SECTION("difference")
+          SECTION("log")
           {
-            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-            {
-              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = A - f[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(positive_function, log);
+          }
 
-              REQUIRE(same_values(A - f, difference_values));
-              REQUIRE(same_values(A - const_f, difference_values));
-            }
-            {
-              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - A; });
+          SECTION("max(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(cos(positive_function), sin(positive_function), max);
+          }
 
-              REQUIRE(same_values(f - A, difference_values));
-              REQUIRE(same_values(const_f - A, difference_values));
-            }
+          SECTION("max(0.2,vh)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.2, sin(positive_function), max);
           }
 
-          SECTION("product")
+          SECTION("max(uh,0.2)")
           {
-            {
-              const double a = 2.3;
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(cos(positive_function), 0.2, max);
+          }
 
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
+          SECTION("atan2(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 2 + positive_function, atan2);
+          }
 
-            {
-              DiscreteFunctionP0<Dimension, double> a{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  a[cell_id]     = 2 * x * x - 1;
-                });
+          SECTION("atan2(0.5,uh)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, 2 + positive_function, atan2);
+          }
 
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+          SECTION("atan2(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(2 + cos(positive_function), 0.2, atan2);
+          }
 
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
+          SECTION("pow(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 0.5 * positive_function, pow);
+          }
 
-            {
-              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+          SECTION("pow(uh,0.5)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, positive_function, pow);
+          }
 
-              REQUIRE(same_values(A * f, product_values));
-              REQUIRE(same_values(A * const_f, product_values));
-            }
+          SECTION("pow(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(positive_function, 1.3, pow);
+          }
 
-            {
-              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+          SECTION("min(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), min);
+          }
 
-              REQUIRE(same_values(f * A, product_values));
-              REQUIRE(same_values(const_f * A, product_values));
-            }
+          SECTION("min(uh,0.5)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, cos(positive_function), min);
           }
-        }
-      }
-    }
 
-    SECTION("3D")
-    {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
+          SECTION("min(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.5, min);
+          }
 
-      constexpr size_t Dimension = 3;
+          SECTION("max(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), max);
+          }
 
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+          SECTION("min(uh,0.5)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.1, cos(positive_function), max);
+          }
 
-      SECTION("inner operators")
-      {
-        SECTION("scalar functions")
-        {
-          DiscreteFunctionP0<Dimension, double> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              const double z = xj[cell_id][2];
-              f[cell_id]     = 2 * x + y - z;
-            });
+          SECTION("min(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.1, max);
+          }
 
-          DiscreteFunctionP0<Dimension, double> g{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              const double z = xj[cell_id][2];
-              g[cell_id]     = std::abs((x + 1) * (x - 2) + y * (1 + y) + 2 * z) + 1;
-            });
+          SECTION("dot(uh,hv)")
+          {
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
+
+            DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
+              });
 
-          DiscreteFunctionP0<Dimension, const double> const_f = f;
-          DiscreteFunctionP0<Dimension, const double> const_g{g};
+            CHECK_STD_BINARY_MATH_FUNCTION(uh, vh, dot);
+          }
 
-          SECTION("sum")
+          SECTION("dot(uh,v)")
           {
-            Array<double> sum_values{mesh->numberOfCells()};
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
             parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
+            const TinyVector<2> v{1, 2};
+
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(uh, v, dot);
           }
 
-          SECTION("difference")
+          SECTION("dot(u,hv)")
           {
-            Array<double> difference_values{mesh->numberOfCells()};
+            const TinyVector<2> u{3, -2};
+
+            DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
             parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
+              });
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(u, vh, dot);
           }
 
-          SECTION("product")
+          SECTION("scalar sum")
           {
-            Array<double> product_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+            const CellValue<const double> cell_value = positive_function.cellValues();
 
-            REQUIRE(same_values(f * g, product_values));
-            REQUIRE(same_values(const_f * g, product_values));
-            REQUIRE(same_values(f * const_g, product_values));
-            REQUIRE(same_values(const_f * const_g, product_values));
+            REQUIRE(sum(cell_value) == sum(positive_function));
           }
 
-          SECTION("ratio")
+          SECTION("vector sum")
           {
-            Array<double> ratio_values{mesh->numberOfCells()};
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
             parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / g[cell_id]; });
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
+            const CellValue<const TinyVector<2>> cell_value = uh.cellValues();
 
-            REQUIRE(same_values(f / g, ratio_values));
-            REQUIRE(same_values(const_f / g, ratio_values));
-            REQUIRE(same_values(f / const_g, ratio_values));
-            REQUIRE(same_values(const_f / const_g, ratio_values));
+            REQUIRE(sum(cell_value) == sum(uh));
           }
-        }
 
-        SECTION("vector functions")
-        {
-          constexpr std::uint64_t VectorDimension = 2;
+          SECTION("matrix sum")
+          {
+            DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 3 * x - 1};
+              });
+            const CellValue<const TinyMatrix<2>> cell_value = uh.cellValues();
 
-          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyVector<VectorDimension> X{x, 2 - x};
-              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
-            });
+            REQUIRE(sum(cell_value) == sum(uh));
+          }
 
-          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> g{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyVector<VectorDimension> X{3 * x + 1, 2 + x};
-              g[cell_id] = X;
-            });
+          SECTION("integrate scalar")
+          {
+            const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
+
+            CellValue<double> cell_value{mesh->connectivity()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                cell_value[cell_id] = cell_volume[cell_id] * positive_function[cell_id];
+              });
 
-          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
-          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_g{g};
+            REQUIRE(integrate(positive_function) == Catch::Approx(sum(cell_value)));
+          }
 
-          SECTION("sum")
+          SECTION("integrate vector")
           {
-            Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
             parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
+
+            const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
+
+            CellValue<TinyVector<2>> cell_value{mesh->connectivity()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
+            REQUIRE(integrate(uh)[0] == Catch::Approx(sum(cell_value)[0]));
+            REQUIRE(integrate(uh)[1] == Catch::Approx(sum(cell_value)[1]));
           }
 
-          SECTION("difference")
+          SECTION("integrate matrix")
           {
-            Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+            DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 1 - x};
+              });
+
+            const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
+
+            CellValue<TinyMatrix<2>> cell_value{mesh->connectivity()};
             parallel_for(
               mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+              PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
+            REQUIRE(integrate(uh)(0, 0) == Catch::Approx(sum(cell_value)(0, 0)));
+            REQUIRE(integrate(uh)(0, 1) == Catch::Approx(sum(cell_value)(0, 1)));
+            REQUIRE(integrate(uh)(1, 0) == Catch::Approx(sum(cell_value)(1, 0)));
+            REQUIRE(integrate(uh)(1, 1) == Catch::Approx(sum(cell_value)(1, 1)));
           }
         }
+      }
+    }
+
+    SECTION("3D")
+    {
+      constexpr size_t Dimension = 3;
+
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-        SECTION("matrix functions")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          constexpr std::uint64_t MatrixDimension = 2;
+          auto mesh = named_mesh.mesh();
 
-          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
-              f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
-            });
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          DiscreteFunctionP0<Dimension, double> positive_function{mesh};
 
-          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> g{mesh};
           parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const TinyMatrix<MatrixDimension> A{3 * x + 1, 2 + x, 1 - 2 * x, 2 * x * x};
-              g[cell_id] = A;
-            });
+            mesh->numberOfCells(),
+            PUGS_LAMBDA(const CellId cell_id) { positive_function[cell_id] = 1 + std::abs(xj[cell_id][0]); });
 
-          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
-          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_g{g};
+          const double min_value = min(positive_function);
+          SECTION("min")
+          {
+            double local_min = std::numeric_limits<double>::max();
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              local_min = std::min(local_min, positive_function[cell_id]);
+            }
+            REQUIRE(min_value == parallel::allReduceMin(local_min));
+          }
 
-          SECTION("sum")
+          const double max_value = max(positive_function);
+          SECTION("max")
           {
-            Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+            double local_max = -std::numeric_limits<double>::max();
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              local_max = std::max(local_max, positive_function[cell_id]);
+            }
+            REQUIRE(max_value == parallel::allReduceMax(local_max));
+          }
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
+          REQUIRE(min_value < max_value);
+
+          DiscreteFunctionP0 unsigned_function = positive_function - 0.5 * (min_value + max_value);
+
+          SECTION("sqrt")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, sqrt);
           }
 
-          SECTION("difference")
+          SECTION("abs")
           {
-            Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(positive_function, abs);
+          }
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
+          SECTION("cos")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, cos);
           }
 
-          SECTION("product")
+          SECTION("sin")
           {
-            Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-            parallel_for(
-              mesh->numberOfCells(),
-              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(positive_function, sin);
+          }
 
-            REQUIRE(same_values(f * g, product_values));
-            REQUIRE(same_values(const_f * g, product_values));
-            REQUIRE(same_values(f * const_g, product_values));
-            REQUIRE(same_values(const_f * const_g, product_values));
+          SECTION("tan")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, tan);
           }
-        }
-      }
 
-      SECTION("external operators")
-      {
-        SECTION("scalar functions")
-        {
-          DiscreteFunctionP0<Dimension, double> f{mesh};
+          DiscreteFunctionP0<Dimension, double> unit_function{mesh};
+
           parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              const double z = xj[cell_id][2];
-              f[cell_id]     = std::abs(2 * x + y * z) + 1;
+            mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+              unit_function[cell_id] =
+                (2 * (positive_function[cell_id] - min_value) / (max_value - min_value) - 1) * 0.95;
             });
 
-          const double a = 3;
+          SECTION("acos")
+          {
+            CHECK_STD_MATH_FUNCTION(unit_function, acos);
+          }
 
-          DiscreteFunctionP0<Dimension, const double> const_f = f;
+          SECTION("asin")
+          {
+            CHECK_STD_MATH_FUNCTION(unit_function, asin);
+          }
 
-          SECTION("sum")
+          SECTION("atan")
           {
-            {
-              Array<double> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = a + f[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(unit_function, atan);
+          }
 
-              REQUIRE(same_values(a + f, sum_values));
-              REQUIRE(same_values(a + const_f, sum_values));
-            }
-            {
-              Array<double> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + a; });
+          SECTION("cosh")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, cosh);
+          }
 
-              REQUIRE(same_values(f + a, sum_values));
-              REQUIRE(same_values(const_f + a, sum_values));
-            }
+          SECTION("sinh")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, sinh);
           }
 
-          SECTION("difference")
+          SECTION("tanh")
           {
-            {
-              Array<double> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = a - f[cell_id]; });
-              REQUIRE(same_values(a - f, difference_values));
-              REQUIRE(same_values(a - const_f, difference_values));
-            }
+            CHECK_STD_MATH_FUNCTION(positive_function, tanh);
+          }
 
-            {
-              Array<double> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - a; });
-              REQUIRE(same_values(f - a, difference_values));
-              REQUIRE(same_values(const_f - a, difference_values));
-            }
+          SECTION("acosh")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, acosh);
           }
 
-          SECTION("product")
+          SECTION("asinh")
           {
-            {
-              Array<double> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+            CHECK_STD_MATH_FUNCTION(positive_function, asinh);
+          }
 
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
-            {
-              Array<double> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * a; });
+          SECTION("atanh")
+          {
+            CHECK_STD_MATH_FUNCTION(unit_function, atanh);
+          }
 
-              REQUIRE(same_values(f * a, product_values));
-              REQUIRE(same_values(const_f * a, product_values));
-            }
+          SECTION("exp")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, exp);
+          }
 
-            {
-              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
-              const TinyVector<3> v{1, 2, 3};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v; });
+          SECTION("log")
+          {
+            CHECK_STD_MATH_FUNCTION(positive_function, log);
+          }
 
-              REQUIRE(same_values(f * v, product_values));
-              REQUIRE(same_values(const_f * v, product_values));
-            }
+          SECTION("max(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(cos(positive_function), sin(positive_function), max);
+          }
 
-            {
-              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
-              DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  v[cell_id]     = TinyVector<3>{x, 2 * x, 1 - x};
-                });
+          SECTION("max(0.2,vh)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.2, sin(positive_function), max);
+          }
 
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v[cell_id]; });
+          SECTION("max(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(cos(positive_function), 0.2, max);
+          }
 
-              REQUIRE(same_values(f * v, product_values));
-              REQUIRE(same_values(const_f * v, product_values));
-            }
+          SECTION("atan2(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 2 + positive_function, atan2);
+          }
 
-            {
-              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
-              const TinyMatrix<2> A{1, 2, 3, 4};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+          SECTION("atan2(0.5,uh)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, 2 + positive_function, atan2);
+          }
 
-              REQUIRE(same_values(f * A, product_values));
-              REQUIRE(same_values(const_f * A, product_values));
-            }
+          SECTION("atan2(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(2 + cos(positive_function), 0.2, atan2);
+          }
 
-            {
-              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
-              DiscreteFunctionP0<Dimension, TinyMatrix<2>> M{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
-                });
+          SECTION("pow(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 0.5 * positive_function, pow);
+          }
 
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * M[cell_id]; });
+          SECTION("pow(uh,0.5)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, positive_function, pow);
+          }
 
-              REQUIRE(same_values(f * M, product_values));
-              REQUIRE(same_values(const_f * M, product_values));
-            }
+          SECTION("pow(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(positive_function, 1.3, pow);
           }
 
-          SECTION("ratio")
+          SECTION("min(uh,hv)")
           {
-            {
-              Array<double> ratio_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = a / f[cell_id]; });
+            CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), min);
+          }
 
-              REQUIRE(same_values(a / f, ratio_values));
-              REQUIRE(same_values(a / const_f, ratio_values));
-            }
-            {
-              Array<double> ratio_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / a; });
+          SECTION("min(uh,0.5)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, cos(positive_function), min);
+          }
 
-              REQUIRE(same_values(f / a, ratio_values));
-              REQUIRE(same_values(const_f / a, ratio_values));
-            }
+          SECTION("min(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.5, min);
           }
-        }
 
-        SECTION("vector functions")
-        {
-          constexpr std::uint64_t VectorDimension = 2;
+          SECTION("max(uh,hv)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), max);
+          }
 
-          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              const double z = xj[cell_id][2];
-              const TinyVector<VectorDimension> X{x + y - z, 2 - x * y};
-              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
-            });
+          SECTION("min(uh,0.5)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.1, cos(positive_function), max);
+          }
 
-          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+          SECTION("min(uh,0.2)")
+          {
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.1, max);
+          }
 
-          SECTION("sum")
+          SECTION("dot(uh,hv)")
           {
-            const TinyVector<VectorDimension> v{1, 2};
-            {
-              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = v + f[cell_id]; });
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
 
-              REQUIRE(same_values(v + f, sum_values));
-              REQUIRE(same_values(v + const_f, sum_values));
-            }
-            {
-              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + v; });
+            DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
+              });
 
-              REQUIRE(same_values(f + v, sum_values));
-              REQUIRE(same_values(const_f + v, sum_values));
-            }
+            CHECK_STD_BINARY_MATH_FUNCTION(uh, vh, dot);
           }
 
-          SECTION("difference")
+          SECTION("dot(uh,v)")
           {
-            const TinyVector<VectorDimension> v{1, 2};
-            {
-              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = v - f[cell_id]; });
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
 
-              REQUIRE(same_values(v - f, difference_values));
-              REQUIRE(same_values(v - const_f, difference_values));
-            }
-            {
-              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - v; });
+            const TinyVector<2> v{1, 2};
 
-              REQUIRE(same_values(f - v, difference_values));
-              REQUIRE(same_values(const_f - v, difference_values));
-            }
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(uh, v, dot);
           }
 
-          SECTION("product")
+          SECTION("dot(u,hv)")
           {
-            {
-              const double a = 2.3;
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
-
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
+            const TinyVector<2> u{3, -2};
 
-            {
-              DiscreteFunctionP0<Dimension, double> a{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  a[cell_id]     = 2 * x * x - 1;
-                });
+            DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
+              });
 
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+            CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(u, vh, dot);
+          }
 
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
+          SECTION("scalar sum")
+          {
+            const CellValue<const double> cell_value = positive_function.cellValues();
 
-            {
-              const TinyMatrix<VectorDimension> A{1, 2, 3, 4};
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+            REQUIRE(sum(cell_value) == sum(positive_function));
+          }
 
-              REQUIRE(same_values(A * f, product_values));
-              REQUIRE(same_values(A * const_f, product_values));
-            }
+          SECTION("vector sum")
+          {
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
+            const CellValue<const TinyVector<2>> cell_value = uh.cellValues();
 
-            {
-              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
-              DiscreteFunctionP0<Dimension, TinyMatrix<VectorDimension>> M{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
-                });
+            REQUIRE(sum(cell_value) == sum(uh));
+          }
 
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = M[cell_id] * f[cell_id]; });
+          SECTION("matrix sum")
+          {
+            DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 3 * x - 1};
+              });
+            const CellValue<const TinyMatrix<2>> cell_value = uh.cellValues();
 
-              REQUIRE(same_values(M * f, product_values));
-              REQUIRE(same_values(M * const_f, product_values));
-            }
+            REQUIRE(sum(cell_value) == sum(uh));
           }
-        }
 
-        SECTION("matrix functions")
-        {
-          constexpr std::uint64_t MatrixDimension = 2;
+          SECTION("integrate scalar")
+          {
+            const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
 
-          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              const double z = xj[cell_id][2];
-              const TinyMatrix<MatrixDimension> X{x, 2 - y, x * y, y * z + 3};
-              f[cell_id] = 2 * X + TinyMatrix<2>{1, 2, 3, 4};
-            });
+            CellValue<double> cell_value{mesh->connectivity()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                cell_value[cell_id] = cell_volume[cell_id] * positive_function[cell_id];
+              });
 
-          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+            REQUIRE(integrate(positive_function) == Catch::Approx(sum(cell_value)));
+          }
 
-          SECTION("sum")
+          SECTION("integrate vector")
           {
-            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-            {
-              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = A + f[cell_id]; });
+            DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
+              });
 
-              REQUIRE(same_values(A + f, sum_values));
-              REQUIRE(same_values(A + const_f, sum_values));
-            }
-            {
-              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + A; });
+            const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
 
-              REQUIRE(same_values(f + A, sum_values));
-              REQUIRE(same_values(const_f + A, sum_values));
-            }
+            CellValue<TinyVector<2>> cell_value{mesh->connectivity()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
+
+            REQUIRE(integrate(uh)[0] == Catch::Approx(sum(cell_value)[0]));
+            REQUIRE(integrate(uh)[1] == Catch::Approx(sum(cell_value)[1]));
           }
 
-          SECTION("difference")
+          SECTION("integrate matrix")
           {
-            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-            {
-              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = A - f[cell_id]; });
+            DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 1 - x};
+              });
 
-              REQUIRE(same_values(A - f, difference_values));
-              REQUIRE(same_values(A - const_f, difference_values));
-            }
-            {
-              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - A; });
+            const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
 
-              REQUIRE(same_values(f - A, difference_values));
-              REQUIRE(same_values(const_f - A, difference_values));
-            }
+            CellValue<TinyMatrix<2>> cell_value{mesh->connectivity()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
+
+            REQUIRE(integrate(uh)(0, 0) == Catch::Approx(sum(cell_value)(0, 0)));
+            REQUIRE(integrate(uh)(0, 1) == Catch::Approx(sum(cell_value)(0, 1)));
+            REQUIRE(integrate(uh)(1, 0) == Catch::Approx(sum(cell_value)(1, 0)));
+            REQUIRE(integrate(uh)(1, 1) == Catch::Approx(sum(cell_value)(1, 1)));
           }
+        }
+      }
+    }
+  }
+
+#ifndef NDEBUG
+  SECTION("error")
+  {
+    SECTION("different meshes")
+    {
+      SECTION("1D")
+      {
+        constexpr size_t Dimension = 1;
 
-          SECTION("product")
+        std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+        for (auto named_mesh : mesh_list) {
+          SECTION(named_mesh.name())
           {
-            {
-              const double a = 2.3;
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+            auto mesh_1 = named_mesh.mesh();
 
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
+            std::shared_ptr mesh_2 =
+              std::make_shared<Mesh<Connectivity<Dimension>>>(mesh_1->shared_connectivity(), mesh_1->xr());
 
-            {
-              DiscreteFunctionP0<Dimension, double> a{mesh};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                  const double x = xj[cell_id][0];
-                  a[cell_id]     = 2 * x * x - 1;
-                });
+            DiscreteFunctionP0<Dimension, double> f1{mesh_1};
+            DiscreteFunctionP0<Dimension, double> f2{mesh_2};
 
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(),
-                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+            REQUIRE_THROWS_AS(f1 = f2, AssertError);
+            REQUIRE_THROWS_AS(copy_to(f1, f2), AssertError);
+            REQUIRE_THROWS_AS(f1 + f2, AssertError);
+            REQUIRE_THROWS_AS(f1 - f2, AssertError);
+            REQUIRE_THROWS_AS(f1 * f2, AssertError);
+            REQUIRE_THROWS_AS(f1 / f2, AssertError);
+          }
+        }
+      }
 
-              REQUIRE(same_values(a * f, product_values));
-              REQUIRE(same_values(a * const_f, product_values));
-            }
+      SECTION("2D")
+      {
+        constexpr size_t Dimension = 2;
 
-            {
-              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+        std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-              REQUIRE(same_values(A * f, product_values));
-              REQUIRE(same_values(A * const_f, product_values));
-            }
+        for (auto named_mesh : mesh_list) {
+          SECTION(named_mesh.name())
+          {
+            auto mesh_1 = named_mesh.mesh();
 
-            {
-              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
-              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
-              parallel_for(
-                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+            std::shared_ptr mesh_2 =
+              std::make_shared<Mesh<Connectivity<Dimension>>>(mesh_1->shared_connectivity(), mesh_1->xr());
 
-              REQUIRE(same_values(f * A, product_values));
-              REQUIRE(same_values(const_f * A, product_values));
-            }
+            DiscreteFunctionP0<Dimension, double> f1{mesh_1};
+            DiscreteFunctionP0<Dimension, double> f2{mesh_2};
+
+            REQUIRE_THROWS_AS(f1 = f2, AssertError);
+            REQUIRE_THROWS_AS(copy_to(f1, f2), AssertError);
+            REQUIRE_THROWS_AS(f1 + f2, AssertError);
+            REQUIRE_THROWS_AS(f1 - f2, AssertError);
+            REQUIRE_THROWS_AS(f1 * f2, AssertError);
+            REQUIRE_THROWS_AS(f1 / f2, AssertError);
           }
         }
       }
-    }
-  }
-
-  SECTION("math functions")
-  {
-#define CHECK_STD_MATH_FUNCTION(data_expression, FCT)                           \
-  {                                                                             \
-    DiscreteFunctionP0 data   = data_expression;                                \
-    DiscreteFunctionP0 result = FCT(data);                                      \
-    bool is_same              = true;                                           \
-    parallel_for(data.cellValues().numberOfItems(), [&](const CellId cell_id) { \
-      if (result[cell_id] != std::FCT(data[cell_id])) {                         \
-        is_same = false;                                                        \
-      }                                                                         \
-    });                                                                         \
-    REQUIRE(is_same);                                                           \
-  }
-
-#define CHECK_STD_BINARY_MATH_FUNCTION(lhs_expression, rhs_expression, FCT)    \
-  {                                                                            \
-    DiscreteFunctionP0 lhs    = lhs_expression;                                \
-    DiscreteFunctionP0 rhs    = rhs_expression;                                \
-    DiscreteFunctionP0 result = FCT(lhs, rhs);                                 \
-    using namespace std;                                                       \
-    bool is_same = true;                                                       \
-    parallel_for(lhs.cellValues().numberOfItems(), [&](const CellId cell_id) { \
-      if (result[cell_id] != FCT(lhs[cell_id], rhs[cell_id])) {                \
-        is_same = false;                                                       \
-      }                                                                        \
-    });                                                                        \
-    REQUIRE(is_same);                                                          \
-  }
-
-#define CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(lhs, rhs_expression, FCT) \
-  {                                                                             \
-    DiscreteFunctionP0 rhs    = rhs_expression;                                 \
-    DiscreteFunctionP0 result = FCT(lhs, rhs);                                  \
-    bool is_same              = true;                                           \
-    using namespace std;                                                        \
-    parallel_for(rhs.cellValues().numberOfItems(), [&](const CellId cell_id) {  \
-      if (result[cell_id] != FCT(lhs, rhs[cell_id])) {                          \
-        is_same = false;                                                        \
-      }                                                                         \
-    });                                                                         \
-    REQUIRE(is_same);                                                           \
-  }
-
-#define CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(lhs_expression, rhs, FCT) \
-  {                                                                             \
-    DiscreteFunctionP0 lhs    = lhs_expression;                                 \
-    DiscreteFunctionP0 result = FCT(lhs, rhs);                                  \
-    bool is_same              = true;                                           \
-    using namespace std;                                                        \
-    parallel_for(lhs.cellValues().numberOfItems(), [&](const CellId cell_id) {  \
-      if (result[cell_id] != FCT(lhs[cell_id], rhs)) {                          \
-        is_same = false;                                                        \
-      }                                                                         \
-    });                                                                         \
-    REQUIRE(is_same);                                                           \
-  }
-
-    SECTION("1D")
-    {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
-      constexpr size_t Dimension = 1;
-
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-      DiscreteFunctionP0<Dimension, double> positive_function{mesh};
-
-      parallel_for(
-        mesh->numberOfCells(),
-        PUGS_LAMBDA(const CellId cell_id) { positive_function[cell_id] = 1 + std::abs(xj[cell_id][0]); });
-
-      const double min_value = min(positive_function);
-      SECTION("min")
-      {
-        double local_min = std::numeric_limits<double>::max();
-        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-          local_min = std::min(local_min, positive_function[cell_id]);
-        }
-        REQUIRE(min_value == parallel::allReduceMin(local_min));
-      }
-
-      const double max_value = max(positive_function);
-      SECTION("max")
-      {
-        double local_max = -std::numeric_limits<double>::max();
-        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-          local_max = std::max(local_max, positive_function[cell_id]);
-        }
-        REQUIRE(max_value == parallel::allReduceMax(local_max));
-      }
-
-      REQUIRE(min_value < max_value);
-
-      DiscreteFunctionP0 unsigned_function = positive_function - 0.5 * (min_value + max_value);
-
-      SECTION("sqrt")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, sqrt);
-      }
-
-      SECTION("abs")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, abs);
-      }
-
-      SECTION("cos")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, cos);
-      }
-
-      SECTION("sin")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, sin);
-      }
-
-      SECTION("tan")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, tan);
-      }
-
-      DiscreteFunctionP0<Dimension, double> unit_function{mesh};
-
-      parallel_for(
-        mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-          unit_function[cell_id] = (2 * (positive_function[cell_id] - min_value) / (max_value - min_value) - 1) * 0.95;
-        });
-
-      SECTION("acos")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, acos);
-      }
-
-      SECTION("asin")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, asin);
-      }
-
-      SECTION("atan")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, atan);
-      }
-
-      SECTION("cosh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, cosh);
-      }
-
-      SECTION("sinh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, sinh);
-      }
-
-      SECTION("tanh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, tanh);
-      }
-
-      SECTION("acosh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, acosh);
-      }
-
-      SECTION("asinh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, asinh);
-      }
-
-      SECTION("atanh")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, atanh);
-      }
-
-      SECTION("exp")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, exp);
-      }
-
-      SECTION("log")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, log);
-      }
-
-      SECTION("max(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(cos(positive_function), sin(positive_function), max);
-      }
-
-      SECTION("max(0.2,vh)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.2, sin(positive_function), max);
-      }
-
-      SECTION("max(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(cos(positive_function), 0.2, max);
-      }
-
-      SECTION("atan2(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 2 + positive_function, atan2);
-      }
-
-      SECTION("atan2(0.5,uh)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, 2 + positive_function, atan2);
-      }
-
-      SECTION("atan2(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(2 + cos(positive_function), 0.2, atan2);
-      }
-
-      SECTION("pow(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 0.5 * positive_function, pow);
-      }
-
-      SECTION("pow(uh,0.5)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, positive_function, pow);
-      }
-
-      SECTION("pow(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(positive_function, 1.3, pow);
-      }
-
-      SECTION("min(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), min);
-      }
-
-      SECTION("min(uh,0.5)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, cos(positive_function), min);
-      }
-
-      SECTION("min(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.5, min);
-      }
-
-      SECTION("max(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), max);
-      }
-
-      SECTION("min(uh,0.5)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.1, cos(positive_function), max);
-      }
-
-      SECTION("min(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.1, max);
-      }
-
-      SECTION("dot(uh,hv)")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-
-        DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
-          });
-
-        CHECK_STD_BINARY_MATH_FUNCTION(uh, vh, dot);
-      }
-
-      SECTION("dot(uh,v)")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-
-        const TinyVector<2> v{1, 2};
-
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(uh, v, dot);
-      }
-
-      SECTION("dot(u,hv)")
-      {
-        const TinyVector<2> u{3, -2};
-
-        DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
-          });
-
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(u, vh, dot);
-      }
-
-      SECTION("scalar sum")
-      {
-        const CellValue<const double> cell_value = positive_function.cellValues();
-
-        REQUIRE(sum(cell_value) == sum(positive_function));
-      }
-
-      SECTION("vector sum")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-        const CellValue<const TinyVector<2>> cell_value = uh.cellValues();
-
-        REQUIRE(sum(cell_value) == sum(uh));
-      }
-
-      SECTION("matrix sum")
-      {
-        DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 3 * x - 1};
-          });
-        const CellValue<const TinyMatrix<2>> cell_value = uh.cellValues();
-
-        REQUIRE(sum(cell_value) == sum(uh));
-      }
-
-      SECTION("integrate scalar")
-      {
-        const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
-
-        CellValue<double> cell_value{mesh->connectivity()};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            cell_value[cell_id] = cell_volume[cell_id] * positive_function[cell_id];
-          });
-
-        REQUIRE(integrate(positive_function) == Catch::Approx(sum(cell_value)));
-      }
-
-      SECTION("integrate vector")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-
-        const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
-
-        CellValue<TinyVector<2>> cell_value{mesh->connectivity()};
-        parallel_for(
-          mesh->numberOfCells(),
-          PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
-
-        REQUIRE(integrate(uh)[0] == Catch::Approx(sum(cell_value)[0]));
-        REQUIRE(integrate(uh)[1] == Catch::Approx(sum(cell_value)[1]));
-      }
-
-      SECTION("integrate matrix")
-      {
-        DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 1 - x};
-          });
-
-        const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
-
-        CellValue<TinyMatrix<2>> cell_value{mesh->connectivity()};
-        parallel_for(
-          mesh->numberOfCells(),
-          PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
-
-        REQUIRE(integrate(uh)(0, 0) == Catch::Approx(sum(cell_value)(0, 0)));
-        REQUIRE(integrate(uh)(0, 1) == Catch::Approx(sum(cell_value)(0, 1)));
-        REQUIRE(integrate(uh)(1, 0) == Catch::Approx(sum(cell_value)(1, 0)));
-        REQUIRE(integrate(uh)(1, 1) == Catch::Approx(sum(cell_value)(1, 1)));
-      }
-    }
-
-    SECTION("2D")
-    {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
-
-      constexpr size_t Dimension = 2;
-
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-      DiscreteFunctionP0<Dimension, double> positive_function{mesh};
-
-      parallel_for(
-        mesh->numberOfCells(),
-        PUGS_LAMBDA(const CellId cell_id) { positive_function[cell_id] = 1 + std::abs(xj[cell_id][0]); });
-
-      const double min_value = min(positive_function);
-      SECTION("min")
-      {
-        double local_min = std::numeric_limits<double>::max();
-        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-          local_min = std::min(local_min, positive_function[cell_id]);
-        }
-        REQUIRE(min_value == parallel::allReduceMin(local_min));
-      }
-
-      const double max_value = max(positive_function);
-      SECTION("max")
-      {
-        double local_max = -std::numeric_limits<double>::max();
-        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-          local_max = std::max(local_max, positive_function[cell_id]);
-        }
-        REQUIRE(max_value == parallel::allReduceMax(local_max));
-      }
-
-      REQUIRE(min_value < max_value);
-
-      DiscreteFunctionP0 unsigned_function = positive_function - 0.5 * (min_value + max_value);
-
-      SECTION("sqrt")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, sqrt);
-      }
-
-      SECTION("abs")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, abs);
-      }
-
-      SECTION("cos")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, cos);
-      }
-
-      SECTION("sin")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, sin);
-      }
-
-      SECTION("tan")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, tan);
-      }
-
-      DiscreteFunctionP0<Dimension, double> unit_function{mesh};
-
-      parallel_for(
-        mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-          unit_function[cell_id] = (2 * (positive_function[cell_id] - min_value) / (max_value - min_value) - 1) * 0.95;
-        });
-
-      SECTION("acos")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, acos);
-      }
-
-      SECTION("asin")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, asin);
-      }
-
-      SECTION("atan")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, atan);
-      }
-
-      SECTION("cosh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, cosh);
-      }
-
-      SECTION("sinh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, sinh);
-      }
-
-      SECTION("tanh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, tanh);
-      }
-
-      SECTION("acosh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, acosh);
-      }
-
-      SECTION("asinh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, asinh);
-      }
-
-      SECTION("atanh")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, atanh);
-      }
-
-      SECTION("exp")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, exp);
-      }
 
-      SECTION("log")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, log);
-      }
-
-      SECTION("max(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(cos(positive_function), sin(positive_function), max);
-      }
-
-      SECTION("max(0.2,vh)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.2, sin(positive_function), max);
-      }
-
-      SECTION("max(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(cos(positive_function), 0.2, max);
-      }
-
-      SECTION("atan2(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 2 + positive_function, atan2);
-      }
-
-      SECTION("atan2(0.5,uh)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, 2 + positive_function, atan2);
-      }
-
-      SECTION("atan2(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(2 + cos(positive_function), 0.2, atan2);
-      }
-
-      SECTION("pow(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 0.5 * positive_function, pow);
-      }
-
-      SECTION("pow(uh,0.5)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, positive_function, pow);
-      }
-
-      SECTION("pow(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(positive_function, 1.3, pow);
-      }
-
-      SECTION("min(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), min);
-      }
-
-      SECTION("min(uh,0.5)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, cos(positive_function), min);
-      }
-
-      SECTION("min(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.5, min);
-      }
-
-      SECTION("max(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), max);
-      }
-
-      SECTION("min(uh,0.5)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.1, cos(positive_function), max);
-      }
-
-      SECTION("min(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.1, max);
-      }
-
-      SECTION("dot(uh,hv)")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-
-        DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
-          });
-
-        CHECK_STD_BINARY_MATH_FUNCTION(uh, vh, dot);
-      }
-
-      SECTION("dot(uh,v)")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-
-        const TinyVector<2> v{1, 2};
-
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(uh, v, dot);
-      }
-
-      SECTION("dot(u,hv)")
-      {
-        const TinyVector<2> u{3, -2};
-
-        DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
-          });
-
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(u, vh, dot);
-      }
-
-      SECTION("scalar sum")
-      {
-        const CellValue<const double> cell_value = positive_function.cellValues();
-
-        REQUIRE(sum(cell_value) == sum(positive_function));
-      }
-
-      SECTION("vector sum")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-        const CellValue<const TinyVector<2>> cell_value = uh.cellValues();
-
-        REQUIRE(sum(cell_value) == sum(uh));
-      }
-
-      SECTION("matrix sum")
-      {
-        DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 3 * x - 1};
-          });
-        const CellValue<const TinyMatrix<2>> cell_value = uh.cellValues();
-
-        REQUIRE(sum(cell_value) == sum(uh));
-      }
-
-      SECTION("integrate scalar")
-      {
-        const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
-
-        CellValue<double> cell_value{mesh->connectivity()};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            cell_value[cell_id] = cell_volume[cell_id] * positive_function[cell_id];
-          });
-
-        REQUIRE(integrate(positive_function) == Catch::Approx(sum(cell_value)));
-      }
-
-      SECTION("integrate vector")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-
-        const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
-
-        CellValue<TinyVector<2>> cell_value{mesh->connectivity()};
-        parallel_for(
-          mesh->numberOfCells(),
-          PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
-
-        REQUIRE(integrate(uh)[0] == Catch::Approx(sum(cell_value)[0]));
-        REQUIRE(integrate(uh)[1] == Catch::Approx(sum(cell_value)[1]));
-      }
-
-      SECTION("integrate matrix")
+      SECTION("3D")
       {
-        DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 1 - x};
-          });
-
-        const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
-
-        CellValue<TinyMatrix<2>> cell_value{mesh->connectivity()};
-        parallel_for(
-          mesh->numberOfCells(),
-          PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
-
-        REQUIRE(integrate(uh)(0, 0) == Catch::Approx(sum(cell_value)(0, 0)));
-        REQUIRE(integrate(uh)(0, 1) == Catch::Approx(sum(cell_value)(0, 1)));
-        REQUIRE(integrate(uh)(1, 0) == Catch::Approx(sum(cell_value)(1, 0)));
-        REQUIRE(integrate(uh)(1, 1) == Catch::Approx(sum(cell_value)(1, 1)));
-      }
-    }
-
-    SECTION("3D")
-    {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
-
-      constexpr size_t Dimension = 3;
+        constexpr size_t Dimension = 3;
 
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+        std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      DiscreteFunctionP0<Dimension, double> positive_function{mesh};
+        for (auto named_mesh : mesh_list) {
+          SECTION(named_mesh.name())
+          {
+            auto mesh_1 = named_mesh.mesh();
 
-      parallel_for(
-        mesh->numberOfCells(),
-        PUGS_LAMBDA(const CellId cell_id) { positive_function[cell_id] = 1 + std::abs(xj[cell_id][0]); });
+            std::shared_ptr mesh_2 =
+              std::make_shared<Mesh<Connectivity<Dimension>>>(mesh_1->shared_connectivity(), mesh_1->xr());
 
-      const double min_value = min(positive_function);
-      SECTION("min")
-      {
-        double local_min = std::numeric_limits<double>::max();
-        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-          local_min = std::min(local_min, positive_function[cell_id]);
-        }
-        REQUIRE(min_value == parallel::allReduceMin(local_min));
-      }
+            DiscreteFunctionP0<Dimension, double> f1{mesh_1};
+            DiscreteFunctionP0<Dimension, double> f2{mesh_2};
 
-      const double max_value = max(positive_function);
-      SECTION("max")
-      {
-        double local_max = -std::numeric_limits<double>::max();
-        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-          local_max = std::max(local_max, positive_function[cell_id]);
+            REQUIRE_THROWS_AS(f1 = f2, AssertError);
+            REQUIRE_THROWS_AS(copy_to(f1, f2), AssertError);
+            REQUIRE_THROWS_AS(f1 + f2, AssertError);
+            REQUIRE_THROWS_AS(f1 - f2, AssertError);
+            REQUIRE_THROWS_AS(f1 * f2, AssertError);
+            REQUIRE_THROWS_AS(f1 / f2, AssertError);
+          }
         }
-        REQUIRE(max_value == parallel::allReduceMax(local_max));
-      }
-
-      REQUIRE(min_value < max_value);
-
-      DiscreteFunctionP0 unsigned_function = positive_function - 0.5 * (min_value + max_value);
-
-      SECTION("sqrt")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, sqrt);
-      }
-
-      SECTION("abs")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, abs);
-      }
-
-      SECTION("cos")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, cos);
-      }
-
-      SECTION("sin")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, sin);
-      }
-
-      SECTION("tan")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, tan);
-      }
-
-      DiscreteFunctionP0<Dimension, double> unit_function{mesh};
-
-      parallel_for(
-        mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-          unit_function[cell_id] = (2 * (positive_function[cell_id] - min_value) / (max_value - min_value) - 1) * 0.95;
-        });
-
-      SECTION("acos")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, acos);
-      }
-
-      SECTION("asin")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, asin);
-      }
-
-      SECTION("atan")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, atan);
-      }
-
-      SECTION("cosh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, cosh);
-      }
-
-      SECTION("sinh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, sinh);
-      }
-
-      SECTION("tanh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, tanh);
-      }
-
-      SECTION("acosh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, acosh);
-      }
-
-      SECTION("asinh")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, asinh);
-      }
-
-      SECTION("atanh")
-      {
-        CHECK_STD_MATH_FUNCTION(unit_function, atanh);
-      }
-
-      SECTION("exp")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, exp);
-      }
-
-      SECTION("log")
-      {
-        CHECK_STD_MATH_FUNCTION(positive_function, log);
-      }
-
-      SECTION("max(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(cos(positive_function), sin(positive_function), max);
-      }
-
-      SECTION("max(0.2,vh)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.2, sin(positive_function), max);
-      }
-
-      SECTION("max(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(cos(positive_function), 0.2, max);
-      }
-
-      SECTION("atan2(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 2 + positive_function, atan2);
-      }
-
-      SECTION("atan2(0.5,uh)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, 2 + positive_function, atan2);
-      }
-
-      SECTION("atan2(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(2 + cos(positive_function), 0.2, atan2);
-      }
-
-      SECTION("pow(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(positive_function, 0.5 * positive_function, pow);
-      }
-
-      SECTION("pow(uh,0.5)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, positive_function, pow);
-      }
-
-      SECTION("pow(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(positive_function, 1.3, pow);
-      }
-
-      SECTION("min(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), min);
-      }
-
-      SECTION("min(uh,0.5)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.5, cos(positive_function), min);
-      }
-
-      SECTION("min(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.5, min);
-      }
-
-      SECTION("max(uh,hv)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION(sin(positive_function), cos(positive_function), max);
-      }
-
-      SECTION("min(uh,0.5)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(0.1, cos(positive_function), max);
-      }
-
-      SECTION("min(uh,0.2)")
-      {
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(sin(positive_function), 0.1, max);
-      }
-
-      SECTION("dot(uh,hv)")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-
-        DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
-          });
-
-        CHECK_STD_BINARY_MATH_FUNCTION(uh, vh, dot);
-      }
-
-      SECTION("dot(uh,v)")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-
-        const TinyVector<2> v{1, 2};
-
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_RHS_VALUE(uh, v, dot);
-      }
-
-      SECTION("dot(u,hv)")
-      {
-        const TinyVector<2> u{3, -2};
-
-        DiscreteFunctionP0<Dimension, TinyVector<2>> vh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            vh[cell_id]    = TinyVector<2>{2.3 * x, 1 - x};
-          });
-
-        CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(u, vh, dot);
-      }
-
-      SECTION("scalar sum")
-      {
-        const CellValue<const double> cell_value = positive_function.cellValues();
-
-        REQUIRE(sum(cell_value) == sum(positive_function));
-      }
-
-      SECTION("vector sum")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-        const CellValue<const TinyVector<2>> cell_value = uh.cellValues();
-
-        REQUIRE(sum(cell_value) == sum(uh));
-      }
-
-      SECTION("matrix sum")
-      {
-        DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 3 * x - 1};
-          });
-        const CellValue<const TinyMatrix<2>> cell_value = uh.cellValues();
-
-        REQUIRE(sum(cell_value) == sum(uh));
-      }
-
-      SECTION("integrate scalar")
-      {
-        const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
-
-        CellValue<double> cell_value{mesh->connectivity()};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            cell_value[cell_id] = cell_volume[cell_id] * positive_function[cell_id];
-          });
-
-        REQUIRE(integrate(positive_function) == Catch::Approx(sum(cell_value)));
-      }
-
-      SECTION("integrate vector")
-      {
-        DiscreteFunctionP0<Dimension, TinyVector<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyVector<2>{x + 1, 2 * x - 3};
-          });
-
-        const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
-
-        CellValue<TinyVector<2>> cell_value{mesh->connectivity()};
-        parallel_for(
-          mesh->numberOfCells(),
-          PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
-
-        REQUIRE(integrate(uh)[0] == Catch::Approx(sum(cell_value)[0]));
-        REQUIRE(integrate(uh)[1] == Catch::Approx(sum(cell_value)[1]));
-      }
-
-      SECTION("integrate matrix")
-      {
-        DiscreteFunctionP0<Dimension, TinyMatrix<2>> uh{mesh};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-            const double x = xj[cell_id][0];
-            uh[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3, 2 * x, 1 - x};
-          });
-
-        const CellValue<const double> cell_volume = MeshDataManager::instance().getMeshData(*mesh).Vj();
-
-        CellValue<TinyMatrix<2>> cell_value{mesh->connectivity()};
-        parallel_for(
-          mesh->numberOfCells(),
-          PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_volume[cell_id] * uh[cell_id]; });
-
-        REQUIRE(integrate(uh)(0, 0) == Catch::Approx(sum(cell_value)(0, 0)));
-        REQUIRE(integrate(uh)(0, 1) == Catch::Approx(sum(cell_value)(0, 1)));
-        REQUIRE(integrate(uh)(1, 0) == Catch::Approx(sum(cell_value)(1, 0)));
-        REQUIRE(integrate(uh)(1, 1) == Catch::Approx(sum(cell_value)(1, 1)));
-      }
-    }
-  }
-
-#ifndef NDEBUG
-  SECTION("error")
-  {
-    SECTION("different meshes")
-    {
-      SECTION("1D")
-      {
-        constexpr size_t Dimension = 1;
-
-        std::shared_ptr mesh_1 = MeshDataBaseForTests::get().cartesianMesh1D();
-        std::shared_ptr mesh_2 =
-          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh_1->shared_connectivity(), mesh_1->xr());
-
-        DiscreteFunctionP0<Dimension, double> f1{mesh_1};
-        DiscreteFunctionP0<Dimension, double> f2{mesh_2};
-
-        REQUIRE_THROWS_AS(f1 = f2, AssertError);
-        REQUIRE_THROWS_AS(copy_to(f1, f2), AssertError);
-        REQUIRE_THROWS_AS(f1 + f2, AssertError);
-        REQUIRE_THROWS_AS(f1 - f2, AssertError);
-        REQUIRE_THROWS_AS(f1 * f2, AssertError);
-        REQUIRE_THROWS_AS(f1 / f2, AssertError);
-      }
-
-      SECTION("2D")
-      {
-        constexpr size_t Dimension = 2;
-
-        std::shared_ptr mesh_1 = MeshDataBaseForTests::get().cartesianMesh2D();
-        std::shared_ptr mesh_2 =
-          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh_1->shared_connectivity(), mesh_1->xr());
-
-        DiscreteFunctionP0<Dimension, double> f1{mesh_1};
-        DiscreteFunctionP0<Dimension, double> f2{mesh_2};
-
-        REQUIRE_THROWS_AS(f1 = f2, AssertError);
-        REQUIRE_THROWS_AS(copy_to(f1, f2), AssertError);
-        REQUIRE_THROWS_AS(f1 + f2, AssertError);
-        REQUIRE_THROWS_AS(f1 - f2, AssertError);
-        REQUIRE_THROWS_AS(f1 * f2, AssertError);
-        REQUIRE_THROWS_AS(f1 / f2, AssertError);
-      }
-
-      SECTION("3D")
-      {
-        constexpr size_t Dimension = 3;
-
-        std::shared_ptr mesh_1 = MeshDataBaseForTests::get().cartesianMesh3D();
-        std::shared_ptr mesh_2 =
-          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh_1->shared_connectivity(), mesh_1->xr());
-
-        DiscreteFunctionP0<Dimension, double> f1{mesh_1};
-        DiscreteFunctionP0<Dimension, double> f2{mesh_2};
-
-        REQUIRE_THROWS_AS(f1 = f2, AssertError);
-        REQUIRE_THROWS_AS(copy_to(f1, f2), AssertError);
-        REQUIRE_THROWS_AS(f1 + f2, AssertError);
-        REQUIRE_THROWS_AS(f1 - f2, AssertError);
-        REQUIRE_THROWS_AS(f1 * f2, AssertError);
-        REQUIRE_THROWS_AS(f1 / f2, AssertError);
       }
     }
   }
diff --git a/tests/test_DiscreteFunctionP0Vector.cpp b/tests/test_DiscreteFunctionP0Vector.cpp
index c004ba5732c41dce1af56d3074b9c038b3a1bf8c..6e351c404a7b5f57d7b39e5a3f70d43cf62a5635 100644
--- a/tests/test_DiscreteFunctionP0Vector.cpp
+++ b/tests/test_DiscreteFunctionP0Vector.cpp
@@ -27,123 +27,144 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
     {
       const size_t size = 3;
 
-      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh1D();
       constexpr size_t Dimension = 1;
 
-      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
-      REQUIRE(f.size() == size);
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      REQUIRE(f.mesh().get() == mesh.get());
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
+          REQUIRE(f.size() == size);
 
-      DiscreteFunctionP0Vector g{f};
-      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
-      REQUIRE(g.size() == size);
+          REQUIRE(f.mesh().get() == mesh.get());
 
-      CellArray<double> h_arrays{mesh->connectivity(), size};
-      h_arrays.fill(0);
+          DiscreteFunctionP0Vector g{f};
+          REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
+          REQUIRE(g.size() == size);
 
-      DiscreteFunctionP0Vector zero{mesh, [&] {
-                                      CellArray<double> cell_array{mesh->connectivity(), size};
-                                      cell_array.fill(0);
-                                      return cell_array;
-                                    }()};
+          CellArray<double> h_arrays{mesh->connectivity(), size};
+          h_arrays.fill(0);
 
-      DiscreteFunctionP0Vector h{mesh, h_arrays};
-      REQUIRE(same_values(h, zero));
-      REQUIRE(same_values(h, h_arrays));
+          DiscreteFunctionP0Vector zero{mesh, [&] {
+                                          CellArray<double> cell_array{mesh->connectivity(), size};
+                                          cell_array.fill(0);
+                                          return cell_array;
+                                        }()};
 
-      h_arrays.fill(1);
+          DiscreteFunctionP0Vector h{mesh, h_arrays};
+          REQUIRE(same_values(h, zero));
+          REQUIRE(same_values(h, h_arrays));
 
-      REQUIRE(same_values(h, h_arrays));
-      REQUIRE(not same_values(h, zero));
+          h_arrays.fill(1);
 
-      DiscreteFunctionP0Vector moved_h{std::move(h)};
-      REQUIRE(same_values(moved_h, h_arrays));
+          REQUIRE(same_values(h, h_arrays));
+          REQUIRE(not same_values(h, zero));
+
+          DiscreteFunctionP0Vector moved_h{std::move(h)};
+          REQUIRE(same_values(moved_h, h_arrays));
+        }
+      }
     }
 
     SECTION("2D")
     {
       const size_t size = 3;
 
-      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh2D();
       constexpr size_t Dimension = 2;
 
-      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
-      REQUIRE(f.size() == size);
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      REQUIRE(f.mesh().get() == mesh.get());
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
+          REQUIRE(f.size() == size);
 
-      DiscreteFunctionP0Vector g{f};
-      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
-      REQUIRE(g.size() == size);
+          REQUIRE(f.mesh().get() == mesh.get());
 
-      CellArray<double> h_arrays{mesh->connectivity(), size};
-      h_arrays.fill(0);
+          DiscreteFunctionP0Vector g{f};
+          REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
+          REQUIRE(g.size() == size);
 
-      DiscreteFunctionP0Vector zero{mesh, [&] {
-                                      CellArray<double> cell_array{mesh->connectivity(), size};
-                                      cell_array.fill(0);
-                                      return cell_array;
-                                    }()};
+          CellArray<double> h_arrays{mesh->connectivity(), size};
+          h_arrays.fill(0);
 
-      DiscreteFunctionP0Vector h{mesh, h_arrays};
-      REQUIRE(same_values(h, zero));
-      REQUIRE(same_values(h, h_arrays));
+          DiscreteFunctionP0Vector zero{mesh, [&] {
+                                          CellArray<double> cell_array{mesh->connectivity(), size};
+                                          cell_array.fill(0);
+                                          return cell_array;
+                                        }()};
 
-      h_arrays.fill(1);
+          DiscreteFunctionP0Vector h{mesh, h_arrays};
+          REQUIRE(same_values(h, zero));
+          REQUIRE(same_values(h, h_arrays));
 
-      REQUIRE(same_values(h, h_arrays));
-      REQUIRE(not same_values(h, zero));
+          h_arrays.fill(1);
 
-      DiscreteFunctionP0Vector moved_h{std::move(h)};
-      REQUIRE(same_values(moved_h, h_arrays));
+          REQUIRE(same_values(h, h_arrays));
+          REQUIRE(not same_values(h, zero));
+
+          DiscreteFunctionP0Vector moved_h{std::move(h)};
+          REQUIRE(same_values(moved_h, h_arrays));
+        }
+      }
     }
 
     SECTION("3D")
     {
       const size_t size = 2;
 
-      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh3D();
       constexpr size_t Dimension = 3;
 
-      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
-      REQUIRE(f.size() == size);
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
+          REQUIRE(f.size() == size);
 
-      REQUIRE(f.mesh().get() == mesh.get());
+          REQUIRE(f.mesh().get() == mesh.get());
 
-      DiscreteFunctionP0Vector g{f};
-      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
-      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
-      REQUIRE(g.size() == size);
+          DiscreteFunctionP0Vector g{f};
+          REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+          REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
+          REQUIRE(g.size() == size);
 
-      CellArray<double> h_arrays{mesh->connectivity(), size};
-      h_arrays.fill(0);
+          CellArray<double> h_arrays{mesh->connectivity(), size};
+          h_arrays.fill(0);
 
-      DiscreteFunctionP0Vector zero{mesh, [&] {
-                                      CellArray<double> cell_array{mesh->connectivity(), size};
-                                      cell_array.fill(0);
-                                      return cell_array;
-                                    }()};
+          DiscreteFunctionP0Vector zero{mesh, [&] {
+                                          CellArray<double> cell_array{mesh->connectivity(), size};
+                                          cell_array.fill(0);
+                                          return cell_array;
+                                        }()};
 
-      DiscreteFunctionP0Vector h{mesh, h_arrays};
-      REQUIRE(same_values(h, zero));
-      REQUIRE(same_values(h, h_arrays));
+          DiscreteFunctionP0Vector h{mesh, h_arrays};
+          REQUIRE(same_values(h, zero));
+          REQUIRE(same_values(h, h_arrays));
 
-      h_arrays.fill(1);
+          h_arrays.fill(1);
 
-      REQUIRE(same_values(h, h_arrays));
-      REQUIRE(not same_values(h, zero));
+          REQUIRE(same_values(h, h_arrays));
+          REQUIRE(not same_values(h, zero));
 
-      DiscreteFunctionP0Vector moved_h{std::move(h)};
-      REQUIRE(same_values(moved_h, h_arrays));
+          DiscreteFunctionP0Vector moved_h{std::move(h)};
+          REQUIRE(same_values(moved_h, h_arrays));
+        }
+      }
     }
   }
 
@@ -166,39 +187,60 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
     {
       const size_t size = 3;
 
-      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh1D();
-      constexpr size_t Dimension = 1;
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-      f.fill(3);
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh                  = named_mesh.mesh();
+          constexpr size_t Dimension = 1;
+
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          f.fill(3);
 
-      REQUIRE(all_values_equal(f, 3));
+          REQUIRE(all_values_equal(f, 3));
+        }
+      }
     }
 
     SECTION("2D")
     {
       const size_t size = 3;
 
-      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh2D();
       constexpr size_t Dimension = 2;
 
-      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-      f.fill(2.3);
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          f.fill(2.3);
 
-      REQUIRE(all_values_equal(f, 2.3));
+          REQUIRE(all_values_equal(f, 2.3));
+        }
+      }
     }
 
     SECTION("3D")
     {
       const size_t size = 2;
 
-      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh3D();
       constexpr size_t Dimension = 3;
 
-      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-      f.fill(3.2);
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          f.fill(3.2);
 
-      REQUIRE(all_values_equal(f, 3.2));
+          REQUIRE(all_values_equal(f, 3.2));
+        }
+      }
     }
   }
 
@@ -220,623 +262,688 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
     SECTION("1D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
       constexpr size_t Dimension = 1;
 
-      const size_t size  = 3;
-      const size_t value = parallel::rank() + 1;
-      const size_t zero  = 0;
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+
+          const size_t size  = 3;
+          const size_t value = parallel::rank() + 1;
+          const size_t zero  = 0;
 
-      DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
-      f.fill(value);
+          DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
+          f.fill(value);
 
-      REQUIRE(all_values_equal(f, value));
+          REQUIRE(all_values_equal(f, value));
 
-      DiscreteFunctionP0Vector g = copy(f);
-      f.fill(zero);
+          DiscreteFunctionP0Vector g = copy(f);
+          f.fill(zero);
 
-      REQUIRE(all_values_equal(f, zero));
-      REQUIRE(all_values_equal(g, value));
+          REQUIRE(all_values_equal(f, zero));
+          REQUIRE(all_values_equal(g, value));
 
-      copy_to(g, f);
-      g.fill(zero);
+          copy_to(g, f);
+          g.fill(zero);
 
-      DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);
+          DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);
 
-      DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
-      shallow_g = g;
+          DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
+          shallow_g = g;
 
-      REQUIRE(all_values_equal(f, value));
-      REQUIRE(all_values_equal(g, zero));
-      REQUIRE(all_values_equal(shallow_g, zero));
-      REQUIRE(all_values_equal(h, value));
+          REQUIRE(all_values_equal(f, value));
+          REQUIRE(all_values_equal(g, zero));
+          REQUIRE(all_values_equal(shallow_g, zero));
+          REQUIRE(all_values_equal(h, value));
 
-      copy_to(h, g);
+          copy_to(h, g);
 
-      REQUIRE(all_values_equal(g, value));
-      REQUIRE(all_values_equal(shallow_g, value));
+          REQUIRE(all_values_equal(g, value));
+          REQUIRE(all_values_equal(shallow_g, value));
+        }
+      }
     }
 
     SECTION("2D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
-
       constexpr size_t Dimension = 2;
+      std::array mesh_list       = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
 
-      const size_t size  = 3;
-      const size_t value = parallel::rank() + 1;
-      const size_t zero  = 0;
+          const size_t size  = 3;
+          const size_t value = parallel::rank() + 1;
+          const size_t zero  = 0;
 
-      DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
-      f.fill(value);
+          DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
+          f.fill(value);
 
-      REQUIRE(all_values_equal(f, value));
+          REQUIRE(all_values_equal(f, value));
 
-      DiscreteFunctionP0Vector g = copy(f);
-      f.fill(zero);
+          DiscreteFunctionP0Vector g = copy(f);
+          f.fill(zero);
 
-      REQUIRE(all_values_equal(f, zero));
-      REQUIRE(all_values_equal(g, value));
+          REQUIRE(all_values_equal(f, zero));
+          REQUIRE(all_values_equal(g, value));
 
-      copy_to(g, f);
-      g.fill(zero);
+          copy_to(g, f);
+          g.fill(zero);
 
-      DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);
+          DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);
 
-      DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
-      shallow_g = g;
+          DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
+          shallow_g = g;
 
-      REQUIRE(all_values_equal(f, value));
-      REQUIRE(all_values_equal(g, zero));
-      REQUIRE(all_values_equal(shallow_g, zero));
-      REQUIRE(all_values_equal(h, value));
+          REQUIRE(all_values_equal(f, value));
+          REQUIRE(all_values_equal(g, zero));
+          REQUIRE(all_values_equal(shallow_g, zero));
+          REQUIRE(all_values_equal(h, value));
 
-      copy_to(h, g);
+          copy_to(h, g);
 
-      REQUIRE(all_values_equal(g, value));
-      REQUIRE(all_values_equal(shallow_g, value));
+          REQUIRE(all_values_equal(g, value));
+          REQUIRE(all_values_equal(shallow_g, value));
+        }
+      }
     }
 
     SECTION("3D")
     {
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
-
       constexpr size_t Dimension = 3;
 
-      const size_t size  = 3;
-      const size_t value = parallel::rank() + 1;
-      const size_t zero  = 0;
-
-      DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
-      f.fill(value);
-
-      REQUIRE(all_values_equal(f, value));
-
-      DiscreteFunctionP0Vector g = copy(f);
-      f.fill(zero);
-
-      REQUIRE(all_values_equal(f, zero));
-      REQUIRE(all_values_equal(g, value));
-
-      copy_to(g, f);
-      g.fill(zero);
-
-      DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
-      shallow_g = g;
-
-      REQUIRE(all_values_equal(f, value));
-      REQUIRE(all_values_equal(g, zero));
-      REQUIRE(all_values_equal(h, value));
-
-      copy_to(h, g);
-
-      REQUIRE(all_values_equal(g, value));
-      REQUIRE(all_values_equal(shallow_g, value));
-    }
-  }
-
-  SECTION("unary operators")
-  {
-    SECTION("1D")
-    {
-      const size_t size    = 3;
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
-      constexpr size_t Dimension = 1;
-
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
 
-      SECTION("unary minus")
-      {
-        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-            const double x = xj[cell_id][0];
-            for (size_t i = 0; i < size; ++i) {
-              f[cell_id][i] = 2 * x + i;
-            }
-          });
+          const size_t size  = 3;
+          const size_t value = parallel::rank() + 1;
+          const size_t zero  = 0;
 
-        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+          DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
+          f.fill(value);
 
-        Table<double> minus_values{mesh->numberOfCells(), size};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-            for (size_t i = 0; i < size; ++i) {
-              minus_values[cell_id][i] = -f[cell_id][i];
-            }
-          });
+          REQUIRE(all_values_equal(f, value));
 
-        REQUIRE(same_values(-f, minus_values));
-        REQUIRE(same_values(-const_f, minus_values));
-      }
-    }
+          DiscreteFunctionP0Vector g = copy(f);
+          f.fill(zero);
 
-    SECTION("2D")
-    {
-      const size_t size    = 3;
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
+          REQUIRE(all_values_equal(f, zero));
+          REQUIRE(all_values_equal(g, value));
 
-      constexpr size_t Dimension = 2;
+          copy_to(g, f);
+          g.fill(zero);
 
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-      SECTION("unary minus")
-      {
-        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-            const double x = xj[cell_id][0];
-            const double y = xj[cell_id][1];
-            for (size_t i = 0; i < size; ++i) {
-              f[cell_id][i] = 2 * x + i * y;
-            }
-          });
+          DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);
 
-        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+          DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
+          shallow_g = g;
 
-        Table<double> minus_values{mesh->numberOfCells(), size};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-            for (size_t i = 0; i < size; ++i) {
-              minus_values[cell_id][i] = -f[cell_id][i];
-            }
-          });
+          REQUIRE(all_values_equal(f, value));
+          REQUIRE(all_values_equal(g, zero));
+          REQUIRE(all_values_equal(h, value));
 
-        REQUIRE(same_values(-f, minus_values));
-        REQUIRE(same_values(-const_f, minus_values));
-      }
-    }
+          copy_to(h, g);
 
-    SECTION("3D")
-    {
-      const size_t size    = 2;
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
-
-      constexpr size_t Dimension = 3;
-
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-      SECTION("unary minus")
-      {
-        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-            const double x = xj[cell_id][0];
-            const double y = xj[cell_id][1];
-            const double z = xj[cell_id][2];
-            for (size_t i = 0; i < size; ++i) {
-              f[cell_id][i] = 2 * x + i * y - z;
-            }
-          });
-
-        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
-
-        Table<double> minus_values{mesh->numberOfCells(), size};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-            for (size_t i = 0; i < size; ++i) {
-              minus_values[cell_id][i] = -f[cell_id][i];
-            }
-          });
-
-        REQUIRE(same_values(-f, minus_values));
-        REQUIRE(same_values(-const_f, minus_values));
+          REQUIRE(all_values_equal(g, value));
+          REQUIRE(all_values_equal(shallow_g, value));
+        }
       }
     }
   }
 
-  SECTION("binary operators")
+  SECTION("unary operators")
   {
     SECTION("1D")
     {
       const size_t size = 3;
 
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
       constexpr size_t Dimension = 1;
 
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      SECTION("inner operators")
-      {
-        SECTION("scalar functions")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              f[cell_id][0]  = 2 * x + 1;
-              f[cell_id][1]  = x * x - 1;
-              f[cell_id][2]  = 2 + x;
-            });
-
-          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              g[cell_id][0]  = (x + 1) * (x - 2) + 1;
-              g[cell_id][1]  = 3 * (x + 2) - 1;
-              g[cell_id][2]  = (x + 3) * 5;
-            });
-
-          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
-          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
-
-          SECTION("sum")
+          auto mesh = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          SECTION("unary minus")
           {
-            Table<double> sum_values{mesh->numberOfCells(), size};
+            DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
             parallel_for(
               mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                const double x = xj[cell_id][0];
                 for (size_t i = 0; i < size; ++i) {
-                  sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
+                  f[cell_id][i] = 2 * x + i;
                 }
               });
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
-          }
+            DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
 
-          SECTION("difference")
-          {
-            Table<double> difference_values{mesh->numberOfCells(), size};
+            Table<double> minus_values{mesh->numberOfCells(), size};
             parallel_for(
               mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                 for (size_t i = 0; i < size; ++i) {
-                  difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
+                  minus_values[cell_id][i] = -f[cell_id][i];
                 }
               });
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
+            REQUIRE(same_values(-f, minus_values));
+            REQUIRE(same_values(-const_f, minus_values));
           }
         }
       }
+    }
 
-      SECTION("external operators")
-      {
-        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-            const double x = xj[cell_id][0];
-            for (size_t i = 0; i < size; ++i) {
-              f[cell_id][i] = std::abs(2 * x) + i;
-            }
-          });
+    SECTION("2D")
+    {
+      const size_t size = 3;
+
+      constexpr size_t Dimension = 2;
 
-        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        SECTION("product")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          SECTION("scalar lhs")
-          {
-            const double a = 3.2;
-            Table<double> product_values{mesh->numberOfCells(), size};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                for (size_t i = 0; i < size; ++i) {
-                  product_values[cell_id][i] = a * f[cell_id][i];
-                }
-              });
+          auto mesh = named_mesh.mesh();
 
-            REQUIRE(same_values(a * f, product_values));
-            REQUIRE(same_values(a * const_f, product_values));
-          }
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
 
-          SECTION("DiscreteFunctionP0 lhs")
+          SECTION("unary minus")
           {
-            DiscreteFunctionP0<Dimension, double> a{mesh};
+            DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
             parallel_for(
               mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                 const double x = xj[cell_id][0];
-                a[cell_id]     = 2 * x + 1;
+                const double y = xj[cell_id][1];
+                for (size_t i = 0; i < size; ++i) {
+                  f[cell_id][i] = 2 * x + i * y;
+                }
               });
 
-            Table<double> product_values{mesh->numberOfCells(), size};
+            DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+
+            Table<double> minus_values{mesh->numberOfCells(), size};
             parallel_for(
               mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                 for (size_t i = 0; i < size; ++i) {
-                  product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
+                  minus_values[cell_id][i] = -f[cell_id][i];
                 }
               });
 
-            REQUIRE(same_values(a * f, product_values));
-            REQUIRE(same_values(a * const_f, product_values));
-
-            DiscreteFunctionP0<Dimension, const double> const_a = a;
-            REQUIRE(same_values(const_a * f, product_values));
-            REQUIRE(same_values(const_a * const_f, product_values));
+            REQUIRE(same_values(-f, minus_values));
+            REQUIRE(same_values(-const_f, minus_values));
           }
         }
       }
     }
 
-    SECTION("2D")
+    SECTION("3D")
     {
-      const size_t size = 3;
-
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
+      const size_t size = 2;
 
-      constexpr size_t Dimension = 2;
+      constexpr size_t Dimension = 3;
 
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      SECTION("inner operators")
-      {
-        SECTION("scalar functions")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              f[cell_id][0]  = 2 * x + 1;
-              f[cell_id][1]  = x * x - y;
-              f[cell_id][2]  = 2 + x * y;
-            });
-
-          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              g[cell_id][0]  = (x + 1) * (y - 2) + 1;
-              g[cell_id][1]  = 3 * (x + 2) - y;
-              g[cell_id][2]  = (x + 3) + 5 * y;
-            });
-
-          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
-          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
-
-          SECTION("sum")
+          auto mesh = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          SECTION("unary minus")
           {
-            Table<double> sum_values{mesh->numberOfCells(), size};
+            DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
             parallel_for(
               mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                const double x = xj[cell_id][0];
+                const double y = xj[cell_id][1];
+                const double z = xj[cell_id][2];
                 for (size_t i = 0; i < size; ++i) {
-                  sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
+                  f[cell_id][i] = 2 * x + i * y - z;
                 }
               });
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
-          }
+            DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
 
-          SECTION("difference")
-          {
-            Table<double> difference_values{mesh->numberOfCells(), size};
+            Table<double> minus_values{mesh->numberOfCells(), size};
             parallel_for(
               mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                 for (size_t i = 0; i < size; ++i) {
-                  difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
+                  minus_values[cell_id][i] = -f[cell_id][i];
                 }
               });
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
+            REQUIRE(same_values(-f, minus_values));
+            REQUIRE(same_values(-const_f, minus_values));
           }
         }
       }
+    }
+  }
 
-      SECTION("external operators")
-      {
-        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-            const double x = xj[cell_id][0];
-            const double y = xj[cell_id][1];
-            for (size_t i = 0; i < size; ++i) {
-              f[cell_id][i] = std::abs(2 * x) + i * y;
-            }
-          });
+  SECTION("binary operators")
+  {
+    SECTION("1D")
+    {
+      const size_t size = 3;
+
+      constexpr size_t Dimension = 1;
 
-        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-        SECTION("product")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          SECTION("scalar lhs")
-          {
-            const double a = 3.2;
-            Table<double> product_values{mesh->numberOfCells(), size};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                for (size_t i = 0; i < size; ++i) {
-                  product_values[cell_id][i] = a * f[cell_id][i];
-                }
-              });
+          auto mesh = named_mesh.mesh();
 
-            REQUIRE(same_values(a * f, product_values));
-            REQUIRE(same_values(a * const_f, product_values));
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          SECTION("inner operators")
+          {
+            SECTION("scalar functions")
+            {
+              DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  f[cell_id][0]  = 2 * x + 1;
+                  f[cell_id][1]  = x * x - 1;
+                  f[cell_id][2]  = 2 + x;
+                });
+
+              DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  g[cell_id][0]  = (x + 1) * (x - 2) + 1;
+                  g[cell_id][1]  = 3 * (x + 2) - 1;
+                  g[cell_id][2]  = (x + 3) * 5;
+                });
+
+              DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+              DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
+
+              SECTION("sum")
+              {
+                Table<double> sum_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
+
+              SECTION("difference")
+              {
+                Table<double> difference_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+            }
           }
 
-          SECTION("DiscreteFunctionP0 lhs")
+          SECTION("external operators")
           {
-            DiscreteFunctionP0<Dimension, double> a{mesh};
+            DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
             parallel_for(
               mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                 const double x = xj[cell_id][0];
-                const double y = xj[cell_id][1];
-                a[cell_id]     = 2 * x + 1 - y;
-              });
-
-            Table<double> product_values{mesh->numberOfCells(), size};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                 for (size_t i = 0; i < size; ++i) {
-                  product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
+                  f[cell_id][i] = std::abs(2 * x) + i;
                 }
               });
 
-            REQUIRE(same_values(a * f, product_values));
-            REQUIRE(same_values(a * const_f, product_values));
-
-            DiscreteFunctionP0<Dimension, const double> const_a = a;
-            REQUIRE(same_values(const_a * f, product_values));
-            REQUIRE(same_values(const_a * const_f, product_values));
+            DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+
+            SECTION("product")
+            {
+              SECTION("scalar lhs")
+              {
+                const double a = 3.2;
+                Table<double> product_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      product_values[cell_id][i] = a * f[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(a * f, product_values));
+                REQUIRE(same_values(a * const_f, product_values));
+              }
+
+              SECTION("DiscreteFunctionP0 lhs")
+              {
+                DiscreteFunctionP0<Dimension, double> a{mesh};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    const double x = xj[cell_id][0];
+                    a[cell_id]     = 2 * x + 1;
+                  });
+
+                Table<double> product_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(a * f, product_values));
+                REQUIRE(same_values(a * const_f, product_values));
+
+                DiscreteFunctionP0<Dimension, const double> const_a = a;
+                REQUIRE(same_values(const_a * f, product_values));
+                REQUIRE(same_values(const_a * const_f, product_values));
+              }
+            }
           }
         }
       }
     }
 
-    SECTION("3D")
+    SECTION("2D")
     {
-      const size_t size = 2;
-
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
+      const size_t size = 3;
 
-      constexpr size_t Dimension = 3;
+      constexpr size_t Dimension = 2;
 
-      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      SECTION("inner operators")
-      {
-        SECTION("scalar functions")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              const double z = xj[cell_id][2];
-              f[cell_id][0]  = 2 * x * z + 1;
-              f[cell_id][1]  = x * z - y;
-            });
-
-          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
-          parallel_for(
-            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-              const double x = xj[cell_id][0];
-              const double y = xj[cell_id][1];
-              const double z = xj[cell_id][2];
-              g[cell_id][0]  = (x + 1) * (y - 2) + 1 - z;
-              g[cell_id][1]  = 3 * (x + 2) - y * z;
-            });
-
-          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
-          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
-
-          SECTION("sum")
-          {
-            Table<double> sum_values{mesh->numberOfCells(), size};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                for (size_t i = 0; i < size; ++i) {
-                  sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
-                }
-              });
+          auto mesh = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
 
-            REQUIRE(same_values(f + g, sum_values));
-            REQUIRE(same_values(const_f + g, sum_values));
-            REQUIRE(same_values(f + const_g, sum_values));
-            REQUIRE(same_values(const_f + const_g, sum_values));
+          SECTION("inner operators")
+          {
+            SECTION("scalar functions")
+            {
+              DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  f[cell_id][0]  = 2 * x + 1;
+                  f[cell_id][1]  = x * x - y;
+                  f[cell_id][2]  = 2 + x * y;
+                });
+
+              DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  g[cell_id][0]  = (x + 1) * (y - 2) + 1;
+                  g[cell_id][1]  = 3 * (x + 2) - y;
+                  g[cell_id][2]  = (x + 3) + 5 * y;
+                });
+
+              DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+              DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
+
+              SECTION("sum")
+              {
+                Table<double> sum_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
+
+              SECTION("difference")
+              {
+                Table<double> difference_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+            }
           }
 
-          SECTION("difference")
+          SECTION("external operators")
           {
-            Table<double> difference_values{mesh->numberOfCells(), size};
+            DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
             parallel_for(
               mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                const double x = xj[cell_id][0];
+                const double y = xj[cell_id][1];
                 for (size_t i = 0; i < size; ++i) {
-                  difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
+                  f[cell_id][i] = std::abs(2 * x) + i * y;
                 }
               });
 
-            REQUIRE(same_values(f - g, difference_values));
-            REQUIRE(same_values(const_f - g, difference_values));
-            REQUIRE(same_values(f - const_g, difference_values));
-            REQUIRE(same_values(const_f - const_g, difference_values));
+            DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+
+            SECTION("product")
+            {
+              SECTION("scalar lhs")
+              {
+                const double a = 3.2;
+                Table<double> product_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      product_values[cell_id][i] = a * f[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(a * f, product_values));
+                REQUIRE(same_values(a * const_f, product_values));
+              }
+
+              SECTION("DiscreteFunctionP0 lhs")
+              {
+                DiscreteFunctionP0<Dimension, double> a{mesh};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    const double x = xj[cell_id][0];
+                    const double y = xj[cell_id][1];
+                    a[cell_id]     = 2 * x + 1 - y;
+                  });
+
+                Table<double> product_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(a * f, product_values));
+                REQUIRE(same_values(a * const_f, product_values));
+
+                DiscreteFunctionP0<Dimension, const double> const_a = a;
+                REQUIRE(same_values(const_a * f, product_values));
+                REQUIRE(same_values(const_a * const_f, product_values));
+              }
+            }
           }
         }
       }
+    }
 
-      SECTION("external operators")
-      {
-        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
-        parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-            const double x = xj[cell_id][0];
-            const double y = xj[cell_id][1];
-            const double z = xj[cell_id][2];
-            for (size_t i = 0; i < size; ++i) {
-              f[cell_id][i] = std::abs(2 * x) + i * y + z;
-            }
-          });
+    SECTION("3D")
+    {
+      const size_t size = 2;
+
+      constexpr size_t Dimension = 3;
 
-        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-        SECTION("product")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          SECTION("scalar lhs")
-          {
-            const double a = 3.2;
-            Table<double> product_values{mesh->numberOfCells(), size};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-                for (size_t i = 0; i < size; ++i) {
-                  product_values[cell_id][i] = a * f[cell_id][i];
-                }
-              });
+          auto mesh = named_mesh.mesh();
 
-            REQUIRE(same_values(a * f, product_values));
-            REQUIRE(same_values(a * const_f, product_values));
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          SECTION("inner operators")
+          {
+            SECTION("scalar functions")
+            {
+              DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  const double z = xj[cell_id][2];
+                  f[cell_id][0]  = 2 * x * z + 1;
+                  f[cell_id][1]  = x * z - y;
+                });
+
+              DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  const double y = xj[cell_id][1];
+                  const double z = xj[cell_id][2];
+                  g[cell_id][0]  = (x + 1) * (y - 2) + 1 - z;
+                  g[cell_id][1]  = 3 * (x + 2) - y * z;
+                });
+
+              DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+              DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
+
+              SECTION("sum")
+              {
+                Table<double> sum_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(f + g, sum_values));
+                REQUIRE(same_values(const_f + g, sum_values));
+                REQUIRE(same_values(f + const_g, sum_values));
+                REQUIRE(same_values(const_f + const_g, sum_values));
+              }
+
+              SECTION("difference")
+              {
+                Table<double> difference_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(f - g, difference_values));
+                REQUIRE(same_values(const_f - g, difference_values));
+                REQUIRE(same_values(f - const_g, difference_values));
+                REQUIRE(same_values(const_f - const_g, difference_values));
+              }
+            }
           }
 
-          SECTION("DiscreteFunctionP0 lhs")
+          SECTION("external operators")
           {
-            DiscreteFunctionP0<Dimension, double> a{mesh};
+            DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
             parallel_for(
               mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                 const double x = xj[cell_id][0];
                 const double y = xj[cell_id][1];
                 const double z = xj[cell_id][2];
-                a[cell_id]     = 2 * x + 1 - y * z;
-              });
-
-            Table<double> product_values{mesh->numberOfCells(), size};
-            parallel_for(
-              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                 for (size_t i = 0; i < size; ++i) {
-                  product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
+                  f[cell_id][i] = std::abs(2 * x) + i * y + z;
                 }
               });
 
-            REQUIRE(same_values(a * f, product_values));
-            REQUIRE(same_values(a * const_f, product_values));
-
-            DiscreteFunctionP0<Dimension, const double> const_a = a;
-            REQUIRE(same_values(const_a * f, product_values));
-            REQUIRE(same_values(const_a * const_f, product_values));
+            DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+
+            SECTION("product")
+            {
+              SECTION("scalar lhs")
+              {
+                const double a = 3.2;
+                Table<double> product_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      product_values[cell_id][i] = a * f[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(a * f, product_values));
+                REQUIRE(same_values(a * const_f, product_values));
+              }
+
+              SECTION("DiscreteFunctionP0 lhs")
+              {
+                DiscreteFunctionP0<Dimension, double> a{mesh};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    const double x = xj[cell_id][0];
+                    const double y = xj[cell_id][1];
+                    const double z = xj[cell_id][2];
+                    a[cell_id]     = 2 * x + 1 - y * z;
+                  });
+
+                Table<double> product_values{mesh->numberOfCells(), size};
+                parallel_for(
+                  mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                    for (size_t i = 0; i < size; ++i) {
+                      product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
+                    }
+                  });
+
+                REQUIRE(same_values(a * f, product_values));
+                REQUIRE(same_values(a * const_f, product_values));
+
+                DiscreteFunctionP0<Dimension, const double> const_a = a;
+                REQUIRE(same_values(const_a * f, product_values));
+                REQUIRE(same_values(const_a * const_f, product_values));
+              }
+            }
           }
         }
       }
diff --git a/tests/test_DiscreteFunctionUtils.cpp b/tests/test_DiscreteFunctionUtils.cpp
index 6587fcba0e201539552e78f4550523b9dbe277eb..3783a19c99cb67066565c507abfeca6ce0f5d61f 100644
--- a/tests/test_DiscreteFunctionUtils.cpp
+++ b/tests/test_DiscreteFunctionUtils.cpp
@@ -16,144 +16,152 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
   {
     constexpr size_t Dimension = 1;
 
-    std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-    std::shared_ptr mesh_copy =
-      std::make_shared<std::decay_t<decltype(*mesh)>>(mesh->shared_connectivity(), mesh->xr());
+    std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    SECTION("common mesh")
-    {
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr wh = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh);
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh = named_mesh.mesh();
 
-      std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
+        std::shared_ptr mesh_copy =
+          std::make_shared<std::decay_t<decltype(*mesh)>>(mesh->shared_connectivity(), mesh->xr());
 
-      REQUIRE(getCommonMesh({uh, vh, wh}).get() == mesh.get());
-      REQUIRE(getCommonMesh({uh, vh, wh, qh}).use_count() == 0);
-    }
+        SECTION("common mesh")
+        {
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr wh = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh);
 
-    SECTION("check discretization type")
-    {
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-
-      std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
-
-      std::shared_ptr Uh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-      std::shared_ptr Vh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-
-      REQUIRE(checkDiscretizationType({uh}, DiscreteFunctionType::P0));
-      REQUIRE(checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0));
-      REQUIRE(not checkDiscretizationType({uh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(not checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(checkDiscretizationType({Uh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(not checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0));
-      REQUIRE(not checkDiscretizationType({Uh}, DiscreteFunctionType::P0));
-    }
+          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
 
-    SECTION("scalar function shallow copy")
-    {
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(getCommonMesh({uh, vh, wh}).get() == mesh.get());
+          REQUIRE(getCommonMesh({uh, vh, wh, qh}).use_count() == 0);
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("check discretization type")
+        {
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr Uh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
+          std::shared_ptr Vh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
 
-    SECTION("R^1 function shallow copy")
-    {
-      using DataType     = TinyVector<1>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(checkDiscretizationType({uh}, DiscreteFunctionType::P0));
+          REQUIRE(checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({uh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({Uh}, DiscreteFunctionType::P0));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("scalar function shallow copy")
+        {
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^2 function shallow copy")
-    {
-      using DataType     = TinyVector<2>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^1 function shallow copy")
+        {
+          using DataType     = TinyVector<1>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^3 function shallow copy")
-    {
-      using DataType     = TinyVector<3>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^2 function shallow copy")
+        {
+          using DataType     = TinyVector<2>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^1x1 function shallow copy")
-    {
-      using DataType     = TinyMatrix<1>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^3 function shallow copy")
+        {
+          using DataType     = TinyVector<3>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^2x2 function shallow copy")
-    {
-      using DataType     = TinyMatrix<2>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^1x1 function shallow copy")
+        {
+          using DataType     = TinyMatrix<1>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^3x3 function shallow copy")
-    {
-      using DataType     = TinyMatrix<3>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^2x2 function shallow copy")
+        {
+          using DataType     = TinyMatrix<2>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
+
+        SECTION("R^3x3 function shallow copy")
+        {
+          using DataType     = TinyMatrix<3>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
+
+          REQUIRE(uh == vh);
+
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
+      }
     }
   }
 
@@ -161,144 +169,152 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
   {
     constexpr size_t Dimension = 2;
 
-    std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
-    std::shared_ptr mesh_copy =
-      std::make_shared<std::decay_t<decltype(*mesh)>>(mesh->shared_connectivity(), mesh->xr());
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    SECTION("common mesh")
-    {
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr wh = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh);
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh = named_mesh.mesh();
 
-      std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
+        std::shared_ptr mesh_copy =
+          std::make_shared<std::decay_t<decltype(*mesh)>>(mesh->shared_connectivity(), mesh->xr());
 
-      REQUIRE(getCommonMesh({uh, vh, wh}).get() == mesh.get());
-      REQUIRE(getCommonMesh({uh, vh, wh, qh}).use_count() == 0);
-    }
+        SECTION("common mesh")
+        {
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr wh = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh);
 
-    SECTION("check discretization type")
-    {
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-
-      std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
-
-      std::shared_ptr Uh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-      std::shared_ptr Vh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-
-      REQUIRE(checkDiscretizationType({uh}, DiscreteFunctionType::P0));
-      REQUIRE(checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0));
-      REQUIRE(not checkDiscretizationType({uh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(not checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(checkDiscretizationType({Uh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(not checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0));
-      REQUIRE(not checkDiscretizationType({Uh}, DiscreteFunctionType::P0));
-    }
+          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
 
-    SECTION("scalar function shallow copy")
-    {
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(getCommonMesh({uh, vh, wh}).get() == mesh.get());
+          REQUIRE(getCommonMesh({uh, vh, wh, qh}).use_count() == 0);
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("check discretization type")
+        {
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr Uh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
+          std::shared_ptr Vh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
 
-    SECTION("R^1 function shallow copy")
-    {
-      using DataType     = TinyVector<1>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(checkDiscretizationType({uh}, DiscreteFunctionType::P0));
+          REQUIRE(checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({uh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({Uh}, DiscreteFunctionType::P0));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("scalar function shallow copy")
+        {
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^2 function shallow copy")
-    {
-      using DataType     = TinyVector<2>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^1 function shallow copy")
+        {
+          using DataType     = TinyVector<1>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^3 function shallow copy")
-    {
-      using DataType     = TinyVector<3>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^2 function shallow copy")
+        {
+          using DataType     = TinyVector<2>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^1x1 function shallow copy")
-    {
-      using DataType     = TinyMatrix<1>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^3 function shallow copy")
+        {
+          using DataType     = TinyVector<3>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^2x2 function shallow copy")
-    {
-      using DataType     = TinyMatrix<2>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^1x1 function shallow copy")
+        {
+          using DataType     = TinyMatrix<1>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^3x3 function shallow copy")
-    {
-      using DataType     = TinyMatrix<3>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
+
+        SECTION("R^2x2 function shallow copy")
+        {
+          using DataType     = TinyMatrix<2>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
+
+          REQUIRE(uh == vh);
+
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^3x3 function shallow copy")
+        {
+          using DataType     = TinyMatrix<3>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
+      }
     }
   }
 
@@ -306,144 +322,152 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
   {
     constexpr size_t Dimension = 3;
 
-    std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
-    std::shared_ptr mesh_copy =
-      std::make_shared<std::decay_t<decltype(*mesh)>>(mesh->shared_connectivity(), mesh->xr());
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    SECTION("common mesh")
-    {
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr wh = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh);
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh = named_mesh.mesh();
 
-      std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
+        std::shared_ptr mesh_copy =
+          std::make_shared<std::decay_t<decltype(*mesh)>>(mesh->shared_connectivity(), mesh->xr());
 
-      REQUIRE(getCommonMesh({uh, vh, wh}).get() == mesh.get());
-      REQUIRE(getCommonMesh({uh, vh, wh, qh}).use_count() == 0);
-    }
+        SECTION("common mesh")
+        {
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr wh = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh);
 
-    SECTION("check discretization type")
-    {
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-
-      std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
-
-      std::shared_ptr Uh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-      std::shared_ptr Vh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-
-      REQUIRE(checkDiscretizationType({uh}, DiscreteFunctionType::P0));
-      REQUIRE(checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0));
-      REQUIRE(not checkDiscretizationType({uh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(not checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(checkDiscretizationType({Uh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0Vector));
-      REQUIRE(not checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0));
-      REQUIRE(not checkDiscretizationType({Uh}, DiscreteFunctionType::P0));
-    }
+          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
 
-    SECTION("scalar function shallow copy")
-    {
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(getCommonMesh({uh, vh, wh}).get() == mesh.get());
+          REQUIRE(getCommonMesh({uh, vh, wh, qh}).use_count() == 0);
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("check discretization type")
+        {
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr Uh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
+          std::shared_ptr Vh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
 
-    SECTION("R^1 function shallow copy")
-    {
-      using DataType     = TinyVector<1>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(checkDiscretizationType({uh}, DiscreteFunctionType::P0));
+          REQUIRE(checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({uh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({Uh}, DiscreteFunctionType::P0));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("scalar function shallow copy")
+        {
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^2 function shallow copy")
-    {
-      using DataType     = TinyVector<2>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^1 function shallow copy")
+        {
+          using DataType     = TinyVector<1>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^3 function shallow copy")
-    {
-      using DataType     = TinyVector<3>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^2 function shallow copy")
+        {
+          using DataType     = TinyVector<2>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^1x1 function shallow copy")
-    {
-      using DataType     = TinyMatrix<1>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^3 function shallow copy")
+        {
+          using DataType     = TinyVector<3>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^2x2 function shallow copy")
-    {
-      using DataType     = TinyMatrix<2>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      REQUIRE(uh == vh);
+        SECTION("R^1x1 function shallow copy")
+        {
+          using DataType     = TinyMatrix<1>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+          REQUIRE(uh == vh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
-    }
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-    SECTION("R^3x3 function shallow copy")
-    {
-      using DataType     = TinyMatrix<3>;
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
-      std::shared_ptr vh = shallowCopy(mesh, uh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
+
+        SECTION("R^2x2 function shallow copy")
+        {
+          using DataType     = TinyMatrix<2>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
+
+          REQUIRE(uh == vh);
+
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
-      REQUIRE(uh == vh);
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
 
-      std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+        SECTION("R^3x3 function shallow copy")
+        {
+          using DataType     = TinyMatrix<3>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          std::shared_ptr vh = shallowCopy(mesh, uh);
 
-      REQUIRE(uh != wh);
-      REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-              &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(uh == vh);
+
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
+                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+        }
+      }
     }
   }
 
@@ -453,21 +477,29 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
     {
       constexpr size_t Dimension = 1;
 
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-      std::shared_ptr other_mesh =
-        CartesianMeshBuilder{TinyVector<1>{-1}, TinyVector<1>{3}, TinyVector<1, size_t>{19}}.mesh();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+
+          std::shared_ptr other_mesh =
+            CartesianMeshBuilder{TinyVector<1>{-1}, TinyVector<1>{3}, TinyVector<1, size_t>{19}}.mesh();
 
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
 
-      REQUIRE_THROWS_WITH(shallowCopy(other_mesh, uh), "error: cannot shallow copy when connectivity changes");
+          REQUIRE_THROWS_WITH(shallowCopy(other_mesh, uh), "error: cannot shallow copy when connectivity changes");
+        }
+      }
     }
 
     SECTION("incompatible mesh dimension")
     {
       constexpr size_t Dimension = 1;
 
-      std::shared_ptr mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
-      std::shared_ptr mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
+      std::shared_ptr mesh_1d = MeshDataBaseForTests::get().cartesian1DMesh();
+      std::shared_ptr mesh_2d = MeshDataBaseForTests::get().cartesian2DMesh();
 
       std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_1d);
 
diff --git a/tests/test_DiscreteFunctionVectorInterpoler.cpp b/tests/test_DiscreteFunctionVectorInterpoler.cpp
index 6a1a5a94b55302aa36fbd85b7d6e7b1522a4ef6b..692dd8c6c18ad85f87f5c85dcc41a6bcb0838312 100644
--- a/tests/test_DiscreteFunctionVectorInterpoler.cpp
+++ b/tests/test_DiscreteFunctionVectorInterpoler.cpp
@@ -53,296 +53,338 @@ TEST_CASE("DiscreteFunctionVectorInterpoler", "[scheme]")
   {
     constexpr size_t Dimension = 1;
 
-    const auto& mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
-    auto xj             = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+    std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    std::string_view data = R"(
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_1d = named_mesh.mesh();
+
+        auto xj = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+
+        std::string_view data = R"(
 import math;
 let B_scalar_non_linear_1d: R^1 -> B, x -> (exp(2 * x[0]) + 3 > 4);
 let N_scalar_non_linear_1d: R^1 -> N, x -> floor(3 * x[0] * x[0] + 2);
 let Z_scalar_non_linear_1d: R^1 -> Z, x -> floor(exp(2 * x[0]) - 1);
 let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
 )";
-    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-    auto ast = ASTBuilder::build(input);
-
-    ASTModulesImporter{*ast};
-    ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-    ASTSymbolTableBuilder{*ast};
-    ASTNodeDataTypeBuilder{*ast};
-
-    ASTNodeTypeCleaner<language::var_declaration>{*ast};
-    ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-    ASTNodeExpressionBuilder{*ast};
-
-    TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-    position.byte = data.size();   // ensure that variables are declared at this point
-
-    std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
-
-    std::vector<FunctionSymbolId> function_id_list;
-    register_function(position, symbol_table, "B_scalar_non_linear_1d", function_id_list);
-    register_function(position, symbol_table, "N_scalar_non_linear_1d", function_id_list);
-    register_function(position, symbol_table, "Z_scalar_non_linear_1d", function_id_list);
-    register_function(position, symbol_table, "R_scalar_non_linear_1d", function_id_list);
-
-    DiscreteFunctionVectorInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
-                                                function_id_list);
-    std::shared_ptr discrete_function = interpoler.interpolate();
-
-    size_t i = 0;
-
-    {
-      CellValue<double> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::exp(2 * x[0]) + 3 > 4;
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
-    }
-
-    {
-      CellValue<double> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(3 * x[0] * x[0] + 2);
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
-    }
-
-    {
-      CellValue<double> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(std::exp(2 * x[0]) - 1);
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
-    }
-
-    {
-      CellValue<double> cell_value{mesh_1d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = 2 * std::exp(x[0]) + 3;
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+        ASTSymbolTableBuilder{*ast};
+        ASTNodeDataTypeBuilder{*ast};
+
+        ASTNodeTypeCleaner<language::var_declaration>{*ast};
+        ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+        ASTNodeExpressionBuilder{*ast};
+
+        TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+        position.byte = data.size();   // ensure that variables are declared at this point
+
+        std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+        std::vector<FunctionSymbolId> function_id_list;
+        register_function(position, symbol_table, "B_scalar_non_linear_1d", function_id_list);
+        register_function(position, symbol_table, "N_scalar_non_linear_1d", function_id_list);
+        register_function(position, symbol_table, "Z_scalar_non_linear_1d", function_id_list);
+        register_function(position, symbol_table, "R_scalar_non_linear_1d", function_id_list);
+
+        DiscreteFunctionVectorInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
+                                                    function_id_list);
+        std::shared_ptr discrete_function = interpoler.interpolate();
+
+        size_t i = 0;
+
+        {
+          CellValue<double> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::exp(2 * x[0]) + 3 > 4;
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        {
+          CellValue<double> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(3 * x[0] * x[0] + 2);
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        {
+          CellValue<double> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(std::exp(2 * x[0]) - 1);
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        {
+          CellValue<double> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = 2 * std::exp(x[0]) + 3;
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        REQUIRE(i == function_id_list.size());
+      }
     }
-
-    REQUIRE(i == function_id_list.size());
   }
 
   SECTION("2D")
   {
     constexpr size_t Dimension = 2;
 
-    const auto& mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
-    auto xj             = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
 
-    std::string_view data = R"(
+        auto xj = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+
+        std::string_view data = R"(
 import math;
 let B_scalar_non_linear_2d: R^2 -> B, x -> (exp(2 * x[0]) + 3 > 4);
 let N_scalar_non_linear_2d: R^2 -> N, x -> floor(3 * (x[0] * x[1]) * (x[0] * x[1]) + 2);
 let Z_scalar_non_linear_2d: R^2 -> Z, x -> floor(exp(2 * x[1]) - 1);
 let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
 )";
-    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-    auto ast = ASTBuilder::build(input);
-
-    ASTModulesImporter{*ast};
-    ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-    ASTSymbolTableBuilder{*ast};
-    ASTNodeDataTypeBuilder{*ast};
-
-    ASTNodeTypeCleaner<language::var_declaration>{*ast};
-    ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-    ASTNodeExpressionBuilder{*ast};
-
-    TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-    position.byte = data.size();   // ensure that variables are declared at this point
-
-    std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
-
-    std::vector<FunctionSymbolId> function_id_list;
-    register_function(position, symbol_table, "B_scalar_non_linear_2d", function_id_list);
-    register_function(position, symbol_table, "N_scalar_non_linear_2d", function_id_list);
-    register_function(position, symbol_table, "Z_scalar_non_linear_2d", function_id_list);
-    register_function(position, symbol_table, "R_scalar_non_linear_2d", function_id_list);
-
-    DiscreteFunctionVectorInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
-                                                function_id_list);
-    std::shared_ptr discrete_function = interpoler.interpolate();
-
-    size_t i = 0;
-
-    {
-      CellValue<double> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::exp(2 * x[0]) + 3 > 4;
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
-    }
-
-    {
-      CellValue<double> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(3 * (x[0] * x[1]) * (x[0] * x[1]) + 2);
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
-    }
-
-    {
-      CellValue<double> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(std::exp(2 * x[1]) - 1);
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
-    }
-
-    {
-      CellValue<double> cell_value{mesh_2d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = 2 * std::exp(x[0] + x[1]) + 3;
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+        ASTSymbolTableBuilder{*ast};
+        ASTNodeDataTypeBuilder{*ast};
+
+        ASTNodeTypeCleaner<language::var_declaration>{*ast};
+        ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+        ASTNodeExpressionBuilder{*ast};
+
+        TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+        position.byte = data.size();   // ensure that variables are declared at this point
+
+        std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+        std::vector<FunctionSymbolId> function_id_list;
+        register_function(position, symbol_table, "B_scalar_non_linear_2d", function_id_list);
+        register_function(position, symbol_table, "N_scalar_non_linear_2d", function_id_list);
+        register_function(position, symbol_table, "Z_scalar_non_linear_2d", function_id_list);
+        register_function(position, symbol_table, "R_scalar_non_linear_2d", function_id_list);
+
+        DiscreteFunctionVectorInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
+                                                    function_id_list);
+        std::shared_ptr discrete_function = interpoler.interpolate();
+
+        size_t i = 0;
+
+        {
+          CellValue<double> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::exp(2 * x[0]) + 3 > 4;
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        {
+          CellValue<double> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(3 * (x[0] * x[1]) * (x[0] * x[1]) + 2);
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        {
+          CellValue<double> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(std::exp(2 * x[1]) - 1);
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        {
+          CellValue<double> cell_value{mesh_2d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = 2 * std::exp(x[0] + x[1]) + 3;
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        REQUIRE(i == function_id_list.size());
+      }
     }
-
-    REQUIRE(i == function_id_list.size());
   }
 
   SECTION("3D")
   {
     constexpr size_t Dimension = 3;
 
-    const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
-    auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
 
-    std::string_view data = R"(
+        auto xj = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+
+        std::string_view data = R"(
 import math;
 let B_scalar_non_linear_3d: R^3 -> B, x -> (exp(2 * x[0] + x[2]) + 3 > 4);
 let N_scalar_non_linear_3d: R^3 -> N, x -> floor(3 * (x[0] * x[1]) * (x[0] * x[1]) + 2);
 let Z_scalar_non_linear_3d: R^3 -> Z, x -> floor(exp(2 * x[1]) - x[2]);
 let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
 )";
-    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-    auto ast = ASTBuilder::build(input);
-
-    ASTModulesImporter{*ast};
-    ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-    ASTSymbolTableBuilder{*ast};
-    ASTNodeDataTypeBuilder{*ast};
-
-    ASTNodeTypeCleaner<language::var_declaration>{*ast};
-    ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-    ASTNodeExpressionBuilder{*ast};
-
-    TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-    position.byte = data.size();   // ensure that variables are declared at this point
-
-    std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
-
-    std::vector<FunctionSymbolId> function_id_list;
-    register_function(position, symbol_table, "B_scalar_non_linear_3d", function_id_list);
-    register_function(position, symbol_table, "N_scalar_non_linear_3d", function_id_list);
-    register_function(position, symbol_table, "Z_scalar_non_linear_3d", function_id_list);
-    register_function(position, symbol_table, "R_scalar_non_linear_3d", function_id_list);
-
-    DiscreteFunctionVectorInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
-                                                function_id_list);
-    std::shared_ptr discrete_function = interpoler.interpolate();
-
-    size_t i = 0;
-
-    {
-      CellValue<double> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::exp(2 * x[0] + x[2]) + 3 > 4;
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
-    }
-
-    {
-      CellValue<double> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(3 * (x[0] * x[1]) * (x[0] * x[1]) + 2);
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
-    }
-
-    {
-      CellValue<double> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = std::floor(std::exp(2 * x[1]) - x[2]);
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
-    }
-
-    {
-      CellValue<double> cell_value{mesh_3d->connectivity()};
-      parallel_for(
-        cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_value[cell_id]            = 2 * std::exp(x[0] + x[1]) + 3 * x[2];
-        });
-
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+        ASTSymbolTableBuilder{*ast};
+        ASTNodeDataTypeBuilder{*ast};
+
+        ASTNodeTypeCleaner<language::var_declaration>{*ast};
+        ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+        ASTNodeExpressionBuilder{*ast};
+
+        TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+        position.byte = data.size();   // ensure that variables are declared at this point
+
+        std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+        std::vector<FunctionSymbolId> function_id_list;
+        register_function(position, symbol_table, "B_scalar_non_linear_3d", function_id_list);
+        register_function(position, symbol_table, "N_scalar_non_linear_3d", function_id_list);
+        register_function(position, symbol_table, "Z_scalar_non_linear_3d", function_id_list);
+        register_function(position, symbol_table, "R_scalar_non_linear_3d", function_id_list);
+
+        DiscreteFunctionVectorInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
+                                                    function_id_list);
+        std::shared_ptr discrete_function = interpoler.interpolate();
+
+        size_t i = 0;
+
+        {
+          CellValue<double> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::exp(2 * x[0] + x[2]) + 3 > 4;
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        {
+          CellValue<double> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(3 * (x[0] * x[1]) * (x[0] * x[1]) + 2);
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        {
+          CellValue<double> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(std::exp(2 * x[1]) - x[2]);
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        {
+          CellValue<double> cell_value{mesh_3d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = 2 * std::exp(x[0] + x[1]) + 3 * x[2];
+            });
+
+          REQUIRE(
+            same_cell_value(cell_value, i++,
+                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+        }
+
+        REQUIRE(i == function_id_list.size());
+      }
     }
-
-    REQUIRE(i == function_id_list.size());
   }
 
   SECTION("errors")
   {
-    const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
-    auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
 
-    std::string_view data = R"(
+        auto xj = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+
+        std::string_view data = R"(
 import math;
 let B_scalar_non_linear_2d: R^2 -> B, x -> (exp(2 * x[0] + x[1]) + 3 > 4);
 let N_scalar_non_linear_1d: R^1 -> N, x -> floor(3 * x[0] * x[0] + 2);
@@ -350,65 +392,67 @@ let Z_scalar_non_linear_3d: R^3 -> Z, x -> floor(exp(2 * x[1]) - x[2]);
 let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
 let R2_scalar_non_linear_3d: R^3 -> R^2, x -> (2 * exp(x[0] + x[1]) + 3 * x[2], x[0] - x[1]);
 )";
-    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
 
-    auto ast = ASTBuilder::build(input);
+        auto ast = ASTBuilder::build(input);
 
-    ASTModulesImporter{*ast};
-    ASTNodeTypeCleaner<language::import_instruction>{*ast};
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-    ASTSymbolTableBuilder{*ast};
-    ASTNodeDataTypeBuilder{*ast};
+        ASTSymbolTableBuilder{*ast};
+        ASTNodeDataTypeBuilder{*ast};
 
-    ASTNodeTypeCleaner<language::var_declaration>{*ast};
-    ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-    ASTNodeExpressionBuilder{*ast};
+        ASTNodeTypeCleaner<language::var_declaration>{*ast};
+        ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+        ASTNodeExpressionBuilder{*ast};
 
-    TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-    position.byte = data.size();   // ensure that variables are declared at this point
+        TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+        position.byte = data.size();   // ensure that variables are declared at this point
 
-    std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+        std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
 
-    SECTION("invalid function type")
-    {
-      std::vector<FunctionSymbolId> function_id_list;
-      register_function(position, symbol_table, "B_scalar_non_linear_2d", function_id_list);
-      register_function(position, symbol_table, "N_scalar_non_linear_1d", function_id_list);
-      register_function(position, symbol_table, "Z_scalar_non_linear_3d", function_id_list);
-      register_function(position, symbol_table, "R_scalar_non_linear_3d", function_id_list);
+        SECTION("invalid function type")
+        {
+          std::vector<FunctionSymbolId> function_id_list;
+          register_function(position, symbol_table, "B_scalar_non_linear_2d", function_id_list);
+          register_function(position, symbol_table, "N_scalar_non_linear_1d", function_id_list);
+          register_function(position, symbol_table, "Z_scalar_non_linear_3d", function_id_list);
+          register_function(position, symbol_table, "R_scalar_non_linear_3d", function_id_list);
 
-      DiscreteFunctionVectorInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
-                                                  function_id_list);
+          DiscreteFunctionVectorInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
+                                                      function_id_list);
 
-      const std::string error_msg = R"(error: invalid function type
+          const std::string error_msg = R"(error: invalid function type
 note: expecting R^3 -> R
 note: provided function B_scalar_non_linear_2d: R^2 -> B)";
 
-      REQUIRE_THROWS_WITH(interpoler.interpolate(), error_msg);
-    }
+          REQUIRE_THROWS_WITH(interpoler.interpolate(), error_msg);
+        }
 
-    SECTION("invalid value type")
-    {
-      std::vector<FunctionSymbolId> function_id_list;
-      register_function(position, symbol_table, "Z_scalar_non_linear_3d", function_id_list);
-      register_function(position, symbol_table, "R_scalar_non_linear_3d", function_id_list);
-      register_function(position, symbol_table, "R2_scalar_non_linear_3d", function_id_list);
+        SECTION("invalid value type")
+        {
+          std::vector<FunctionSymbolId> function_id_list;
+          register_function(position, symbol_table, "Z_scalar_non_linear_3d", function_id_list);
+          register_function(position, symbol_table, "R_scalar_non_linear_3d", function_id_list);
+          register_function(position, symbol_table, "R2_scalar_non_linear_3d", function_id_list);
 
-      DiscreteFunctionVectorInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
-                                                  function_id_list);
+          DiscreteFunctionVectorInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
+                                                      function_id_list);
 
-      const std::string error_msg = R"(error: vector functions require scalar value type.
+          const std::string error_msg = R"(error: vector functions require scalar value type.
 Invalid interpolation value type: R^2)";
 
-      REQUIRE_THROWS_WITH(interpoler.interpolate(), error_msg);
-    }
+          REQUIRE_THROWS_WITH(interpoler.interpolate(), error_msg);
+        }
 
-    SECTION("invalid discrete function type")
-    {
-      const std::string error_msg = "error: invalid discrete function type for vector interpolation";
+        SECTION("invalid discrete function type")
+        {
+          const std::string error_msg = "error: invalid discrete function type for vector interpolation";
 
-      DiscreteFunctionVectorInterpoler interpoler{mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(), {}};
-      REQUIRE_THROWS_WITH(interpoler.interpolate(), error_msg);
+          DiscreteFunctionVectorInterpoler interpoler{mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(), {}};
+          REQUIRE_THROWS_WITH(interpoler.interpolate(), error_msg);
+        }
+      }
     }
   }
 }
diff --git a/tests/test_EdgeIntegrator.cpp b/tests/test_EdgeIntegrator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0cd2feb9d0765407ee9e6346b91ca768dbe53722
--- /dev/null
+++ b/tests/test_EdgeIntegrator.cpp
@@ -0,0 +1,261 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussLobattoQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <mesh/DiamondDualMeshManager.hpp>
+#include <mesh/ItemValue.hpp>
+#include <mesh/Mesh.hpp>
+#include <scheme/EdgeIntegrator.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("EdgeIntegrator", "[scheme]")
+{
+  SECTION("3D")
+  {
+    using R3 = TinyVector<3>;
+
+    auto hybrid_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+
+    auto f = [](const R3& X) -> double {
+      const double x = X[0];
+      const double y = X[1];
+      const double z = X[2];
+      return x * x + 2 * x * y + 3 * y * y + 2 * z * z - z + 1;
+    };
+
+    std::vector<std::pair<std::string, decltype(hybrid_mesh)>> mesh_list;
+    mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
+    mesh_list.push_back(
+      std::make_pair("diamond mesh", DiamondDualMeshManager::instance().getDiamondDualMesh(hybrid_mesh)));
+
+    for (auto mesh_info : mesh_list) {
+      auto mesh_name = mesh_info.first;
+      auto mesh      = mesh_info.second;
+
+      Array<const double> int_f_per_edge = [=] {
+        Array<double> int_f(mesh->numberOfEdges());
+        auto edge_to_node_matrix = mesh->connectivity().edgeToNodeMatrix();
+
+        parallel_for(
+          mesh->numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            auto edge_node_list = edge_to_node_matrix[edge_id];
+            auto xr             = mesh->xr();
+            double integral     = 0;
+
+            LineTransformation<3> T(xr[edge_node_list[0]], xr[edge_node_list[1]]);
+            auto qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor(2));
+            for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+              const auto& xi = qf.point(i);
+              integral += qf.weight(i) * T.velocityNorm() * f(T(xi));
+            }
+
+            int_f[edge_id] = integral;
+          });
+
+        return int_f;
+      }();
+
+      SECTION(mesh_name)
+      {
+        SECTION("direct formula")
+        {
+          SECTION("all edges")
+          {
+            SECTION("EdgeValue")
+            {
+              EdgeValue<double> values(mesh->connectivity());
+              EdgeIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussQuadratureDescriptor(4), *mesh,
+                                          values);
+
+              double error = 0;
+              for (EdgeId edge_id = 0; edge_id < mesh->numberOfEdges(); ++edge_id) {
+                error += std::abs(int_f_per_edge[edge_id] - values[edge_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("Array")
+            {
+              Array<double> values(mesh->numberOfEdges());
+
+              EdgeIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (EdgeId edge_id = 0; edge_id < mesh->numberOfEdges(); ++edge_id) {
+                error += std::abs(int_f_per_edge[edge_id] - values[edge_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<double> values(mesh->numberOfEdges());
+              EdgeIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (EdgeId edge_id = 0; edge_id < mesh->numberOfEdges(); ++edge_id) {
+                error += std::abs(int_f_per_edge[edge_id] - values[edge_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+          }
+
+          SECTION("edge list")
+          {
+            SECTION("Array")
+            {
+              Array<EdgeId> edge_list{mesh->numberOfEdges() / 2 + mesh->numberOfEdges() % 2};
+
+              {
+                size_t k = 0;
+                for (EdgeId edge_id = 0; edge_id < mesh->numberOfEdges(); ++(++edge_id), ++k) {
+                  edge_list[k] = edge_id;
+                }
+
+                REQUIRE(k == edge_list.size());
+              }
+
+              Array<double> values = EdgeIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, edge_list);
+
+              double error = 0;
+              for (size_t i = 0; i < edge_list.size(); ++i) {
+                error += std::abs(int_f_per_edge[edge_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<EdgeId> edge_list{mesh->numberOfEdges() / 2 + mesh->numberOfEdges() % 2};
+
+              {
+                size_t k = 0;
+                for (EdgeId edge_id = 0; edge_id < mesh->numberOfEdges(); ++(++edge_id), ++k) {
+                  edge_list[k] = edge_id;
+                }
+
+                REQUIRE(k == edge_list.size());
+              }
+
+              SmallArray<double> values = EdgeIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, edge_list);
+
+              double error = 0;
+              for (size_t i = 0; i < edge_list.size(); ++i) {
+                error += std::abs(int_f_per_edge[edge_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+          }
+        }
+
+        SECTION("tensorial formula")
+        {
+          SECTION("all edges")
+          {
+            SECTION("EdgeValue")
+            {
+              EdgeValue<double> values(mesh->connectivity());
+              EdgeIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussLegendreQuadratureDescriptor(10),
+                                          *mesh, values);
+
+              double error = 0;
+              for (EdgeId edge_id = 0; edge_id < mesh->numberOfEdges(); ++edge_id) {
+                error += std::abs(int_f_per_edge[edge_id] - values[edge_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("Array")
+            {
+              Array<double> values(mesh->numberOfEdges());
+
+              EdgeIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (EdgeId edge_id = 0; edge_id < mesh->numberOfEdges(); ++edge_id) {
+                error += std::abs(int_f_per_edge[edge_id] - values[edge_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<double> values(mesh->numberOfEdges());
+              EdgeIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (EdgeId edge_id = 0; edge_id < mesh->numberOfEdges(); ++edge_id) {
+                error += std::abs(int_f_per_edge[edge_id] - values[edge_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+          }
+
+          SECTION("edge list")
+          {
+            SECTION("Array")
+            {
+              Array<EdgeId> edge_list{mesh->numberOfEdges() / 2 + mesh->numberOfEdges() % 2};
+
+              {
+                size_t k = 0;
+                for (EdgeId edge_id = 0; edge_id < mesh->numberOfEdges(); ++(++edge_id), ++k) {
+                  edge_list[k] = edge_id;
+                }
+
+                REQUIRE(k == edge_list.size());
+              }
+
+              Array<double> values =
+                EdgeIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, edge_list);
+
+              double error = 0;
+              for (size_t i = 0; i < edge_list.size(); ++i) {
+                error += std::abs(int_f_per_edge[edge_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<EdgeId> edge_list{mesh->numberOfEdges() / 2 + mesh->numberOfEdges() % 2};
+
+              {
+                size_t k = 0;
+                for (EdgeId edge_id = 0; edge_id < mesh->numberOfEdges(); ++(++edge_id), ++k) {
+                  edge_list[k] = edge_id;
+                }
+
+                REQUIRE(k == edge_list.size());
+              }
+
+              SmallArray<double> values =
+                EdgeIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, edge_list);
+
+              double error = 0;
+              for (size_t i = 0; i < edge_list.size(); ++i) {
+                error += std::abs(int_f_per_edge[edge_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_EmbeddedIDiscreteFunctionMathFunctions.cpp b/tests/test_EmbeddedIDiscreteFunctionMathFunctions.cpp
index d41be6566f5bcbc1205c5561abf474f02780b6af..251424f6a7340244b6ac60879cc85653b3115351 100644
--- a/tests/test_EmbeddedIDiscreteFunctionMathFunctions.cpp
+++ b/tests/test_EmbeddedIDiscreteFunctionMathFunctions.cpp
@@ -109,494 +109,506 @@ TEST_CASE("EmbeddedIDiscreteFunctionMathFunctions", "[scheme]")
 
     using Rd = TinyVector<Dimension>;
 
-    std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
-    std::shared_ptr other_mesh =
-      std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-    CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-    CellValue<double> values = [=] {
-      CellValue<double> build_values{mesh->connectivity()};
-      parallel_for(
-        build_values.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-      return build_values;
-    }();
-
-    CellValue<double> positive_values = [=] {
-      CellValue<double> build_values{mesh->connectivity()};
-      parallel_for(
-        build_values.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
-      return build_values;
-    }();
-
-    CellValue<double> bounded_values = [=] {
-      CellValue<double> build_values{mesh->connectivity()};
-      parallel_for(
-        build_values.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
-      return build_values;
-    }();
-
-    std::shared_ptr p_u            = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, values);
-    std::shared_ptr p_other_mesh_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, values);
-    std::shared_ptr p_positive_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, positive_values);
-    std::shared_ptr p_bounded_u  = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, bounded_values);
-
-    std::shared_ptr p_R1_u = [=] {
-      CellValue<TinyVector<1>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R1_v = [=] {
-      CellValue<TinyVector<1>> vj{mesh->connectivity()};
-      parallel_for(
-        vj.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-    }();
-
-    std::shared_ptr p_other_mesh_R1_u =
-      std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-    constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-      if constexpr (Dimension == 1) {
-        return {x[0], 1 + x[0] * x[0]};
-      } else if constexpr (Dimension == 2) {
-        return {x[0], x[1]};
-      } else if constexpr (Dimension == 3) {
-        return {x[0], x[1] + x[2]};
-      }
-    };
-
-    std::shared_ptr p_R2_u = [=] {
-      CellValue<TinyVector<2>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<2> x = to_2d(xj[cell_id]);
-          uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R2_v = [=] {
-      CellValue<TinyVector<2>> vj{mesh->connectivity()};
-      parallel_for(
-        vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<2> x = to_2d(xj[cell_id]);
-          vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-    }();
-
-    std::shared_ptr p_other_mesh_R2_u =
-      std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-    constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-      if constexpr (Dimension == 1) {
-        return {x[0], 1 + x[0] * x[0], 2 - x[0]};
-      } else if constexpr (Dimension == 2) {
-        return {x[0], x[1], x[0] + x[1]};
-      } else if constexpr (Dimension == 3) {
-        return {x[0], x[1], x[2]};
-      }
-    };
-
-    std::shared_ptr p_R3_u = [=] {
-      CellValue<TinyVector<3>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R3_v = [=] {
-      CellValue<TinyVector<3>> vj{mesh->connectivity()};
-      parallel_for(
-        vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-    }();
-
-    std::shared_ptr p_other_mesh_R3_u =
-      std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-    std::shared_ptr p_R1x1_u = [=] {
-      CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R2x2_u = [=] {
-      CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<2> x = to_2d(xj[cell_id]);
-
-          uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
-                         2 * x[1], -x[0]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R3x3_u = [=] {
-      CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-
-          uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
-                         2 * x[1],        -x[0],           x[0] - x[1],   //
-                         3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_Vector3_u = [=] {
-      CellArray<double> uj_vector{mesh->connectivity(), 3};
-      parallel_for(
-        uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          uj_vector[cell_id][0] = 2 * x[0] + 1;
-          uj_vector[cell_id][1] = 1 - x[1] * x[2];
-          uj_vector[cell_id][2] = x[0] + x[2];
-        });
-
-      return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-    }();
-
-    std::shared_ptr p_Vector3_v = [=] {
-      CellArray<double> vj_vector{mesh->connectivity(), 3};
-      parallel_for(
-        vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          vj_vector[cell_id][0] = x[0] * x[1] + 1;
-          vj_vector[cell_id][1] = 2 * x[1];
-          vj_vector[cell_id][2] = x[2] * x[0];
-        });
-
-      return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-    }();
-
-    std::shared_ptr p_Vector2_w = [=] {
-      CellArray<double> wj_vector{mesh->connectivity(), 2};
-      parallel_for(
-        wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          wj_vector[cell_id][0] = x[0] + x[1] * 2;
-          wj_vector[cell_id][1] = x[0] * x[1];
-        });
-
-      return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-    }();
-
-    SECTION("sqrt Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, sqrt);
-      REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+    std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    SECTION("abs Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, abs);
-      REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh = named_mesh.mesh();
+
+        std::shared_ptr other_mesh =
+          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+        CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+        CellValue<double> values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        CellValue<double> positive_values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        CellValue<double> bounded_values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        std::shared_ptr p_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, values);
+        std::shared_ptr p_other_mesh_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, values);
+        std::shared_ptr p_positive_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, positive_values);
+        std::shared_ptr p_bounded_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, bounded_values);
+
+        std::shared_ptr p_R1_u = [=] {
+          CellValue<TinyVector<1>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R1_v = [=] {
+          CellValue<TinyVector<1>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
+        }();
+
+        std::shared_ptr p_other_mesh_R1_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
+
+        constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+          if constexpr (Dimension == 1) {
+            return {x[0], 1 + x[0] * x[0]};
+          } else if constexpr (Dimension == 2) {
+            return {x[0], x[1]};
+          } else if constexpr (Dimension == 3) {
+            return {x[0], x[1] + x[2]};
+          }
+        };
+
+        std::shared_ptr p_R2_u = [=] {
+          CellValue<TinyVector<2>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+              uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R2_v = [=] {
+          CellValue<TinyVector<2>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+              vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
+        }();
+
+        std::shared_ptr p_other_mesh_R2_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
+
+        constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+          if constexpr (Dimension == 1) {
+            return {x[0], 1 + x[0] * x[0], 2 - x[0]};
+          } else if constexpr (Dimension == 2) {
+            return {x[0], x[1], x[0] + x[1]};
+          } else if constexpr (Dimension == 3) {
+            return {x[0], x[1], x[2]};
+          }
+        };
+
+        std::shared_ptr p_R3_u = [=] {
+          CellValue<TinyVector<3>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R3_v = [=] {
+          CellValue<TinyVector<3>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
+        }();
+
+        std::shared_ptr p_other_mesh_R3_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
+
+        std::shared_ptr p_R1x1_u = [=] {
+          CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R2x2_u = [=] {
+          CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+
+              uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
+                             2 * x[1], -x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R3x3_u = [=] {
+          CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+
+              uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
+                             2 * x[1],        -x[0],           x[0] - x[1],   //
+                             3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_Vector3_u = [=] {
+          CellArray<double> uj_vector{mesh->connectivity(), 3};
+          parallel_for(
+            uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              uj_vector[cell_id][0] = 2 * x[0] + 1;
+              uj_vector[cell_id][1] = 1 - x[1] * x[2];
+              uj_vector[cell_id][2] = x[0] + x[2];
+            });
+
+          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
+        }();
+
+        std::shared_ptr p_Vector3_v = [=] {
+          CellArray<double> vj_vector{mesh->connectivity(), 3};
+          parallel_for(
+            vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              vj_vector[cell_id][0] = x[0] * x[1] + 1;
+              vj_vector[cell_id][1] = 2 * x[1];
+              vj_vector[cell_id][2] = x[2] * x[0];
+            });
+
+          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
+        }();
+
+        std::shared_ptr p_Vector2_w = [=] {
+          CellArray<double> wj_vector{mesh->connectivity(), 2};
+          parallel_for(
+            wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              wj_vector[cell_id][0] = x[0] + x[1] * 2;
+              wj_vector[cell_id][1] = x[0] * x[1];
+            });
+
+          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
+        }();
+
+        SECTION("sqrt Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, sqrt);
+          REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("sin Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sin);
-      REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("abs Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, abs);
+          REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("cos Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cos);
-      REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("sin Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sin);
+          REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("tan Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tan);
-      REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("cos Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cos);
+          REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("asin Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, asin);
-      REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("tan Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tan);
+          REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("acos Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, acos);
-      REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("asin Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, asin);
+          REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atan Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atan);
-      REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("acos Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, acos);
+          REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("sinh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sinh);
-      REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("atan Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atan);
+          REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("cosh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cosh);
-      REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("sinh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sinh);
+          REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("tanh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tanh);
-      REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("cosh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cosh);
+          REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("asinh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, asinh);
-      REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("tanh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tanh);
+          REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("acosh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, acosh);
-      REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("asinh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, asinh);
+          REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atanh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atanh);
-      REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("acosh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, acosh);
+          REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("exp Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, exp);
-      REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("atanh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atanh);
+          REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("log Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, log);
-      REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("exp Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, exp);
+          REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atan2 Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2);
-      REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-      REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-    }
+        SECTION("log Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, log);
+          REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atan2 Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2);
-      REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("atan2 Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2);
+          REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+          REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+        }
 
-    SECTION("atan2 R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2);
-      REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("atan2 Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2);
+          REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("min Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min);
-      REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-    }
+        SECTION("atan2 R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2);
+          REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-    SECTION("min Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min);
-      REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("min Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min);
+          REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
 
-    SECTION("min R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min);
-      REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("min Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min);
+          REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("min Vh -> R")
-    {
-      REQUIRE(min(std::shared_ptr<const IDiscreteFunction>{p_u}) == min(p_u->cellValues()));
-      REQUIRE_THROWS_WITH(min(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                          "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("min R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min);
+          REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-    SECTION("max Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max);
-      REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-    }
+        SECTION("min Vh -> R")
+        {
+          REQUIRE(min(std::shared_ptr<const IDiscreteFunction>{p_u}) == min(p_u->cellValues()));
+          REQUIRE_THROWS_WITH(min(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
+                              "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("max Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max);
-      REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("max Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max);
+          REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
 
-    SECTION("max Vh -> R")
-    {
-      REQUIRE(max(std::shared_ptr<const IDiscreteFunction>{p_u}) == max(p_u->cellValues()));
-      REQUIRE_THROWS_WITH(max(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                          "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("max Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max);
+          REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("max R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max);
-      REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("max Vh -> R")
+        {
+          REQUIRE(max(std::shared_ptr<const IDiscreteFunction>{p_u}) == max(p_u->cellValues()));
+          REQUIRE_THROWS_WITH(max(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
+                              "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("pow Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow);
-      REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-    }
+        SECTION("max R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max);
+          REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-    SECTION("pow Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow);
-      REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("pow Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow);
+          REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
 
-    SECTION("pow R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow);
-      REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("pow Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow);
+          REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("dot Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot);
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot);
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot);
+        SECTION("pow R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow);
+          REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-      {
-        auto p_UV = dot(p_Vector3_u, p_Vector3_v);
-        REQUIRE(p_UV.use_count() == 1);
+        SECTION("dot Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot);
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot);
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot);
 
-        auto UV        = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_UV);
-        auto direct_UV = dot(*p_Vector3_u, *p_Vector3_v);
+          {
+            auto p_UV = dot(p_Vector3_u, p_Vector3_v);
+            REQUIRE(p_UV.use_count() == 1);
 
-        bool is_same = true;
-        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-          if (UV[cell_id] != direct_UV[cell_id]) {
-            is_same = false;
-            break;
-          }
-        }
+            auto UV        = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_UV);
+            auto direct_UV = dot(*p_Vector3_u, *p_Vector3_v);
 
-        REQUIRE(is_same);
-      }
+            bool is_same = true;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              if (UV[cell_id] != direct_UV[cell_id]) {
+                is_same = false;
+                break;
+              }
+            }
 
-      REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
-      REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
-    }
+            REQUIRE(is_same);
+          }
 
-    SECTION("dot Vh*Rd -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot);
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot);
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot);
-      REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})), "error: incompatible operand types Vh(P0:R^1) and R^2");
-      REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
-                          "error: incompatible operand types Vh(P0:R^2) and R^3");
-      REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
-    }
+          REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
+        }
 
-    SECTION("dot Rd*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot);
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot);
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot);
-      REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u), "error: incompatible operand types R^2 and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
-                          "error: incompatible operand types R^3 and Vh(P0:R^2)");
-      REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
-    }
+        SECTION("dot Vh*Rd -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot);
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot);
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot);
+          REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})),
+                              "error: incompatible operand types Vh(P0:R^1) and R^2");
+          REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
+                              "error: incompatible operand types Vh(P0:R^2) and R^3");
+          REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
+        }
 
-    SECTION("sum_of_R* Vh -> R*")
-    {
-      REQUIRE(sum_of<double>(p_u) == sum(p_u->cellValues()));
-      REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->cellValues()));
-      REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->cellValues()));
-      REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->cellValues()));
-      REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->cellValues()));
-      REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->cellValues()));
-      REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->cellValues()));
-
-      REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
-    }
+        SECTION("dot Rd*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot);
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot);
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot);
+          REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u),
+                              "error: incompatible operand types R^2 and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
+                              "error: incompatible operand types R^3 and Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
+        }
 
-    SECTION("integral_of_R* Vh -> R*")
-    {
-      auto integrate_locally = [&](const auto& cell_values) {
-        const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
-        using DataType = decltype(double{} * cell_values[CellId{0}]);
-        CellValue<DataType> local_integral{mesh->connectivity()};
-        parallel_for(
-          local_integral.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
-        return local_integral;
-      };
-
-      REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->cellValues())));
-      REQUIRE(integral_of<TinyVector<1>>(p_R1_u) == sum(integrate_locally(p_R1_u->cellValues())));
-      REQUIRE(integral_of<TinyVector<2>>(p_R2_u) == sum(integrate_locally(p_R2_u->cellValues())));
-      REQUIRE(integral_of<TinyVector<3>>(p_R3_u) == sum(integrate_locally(p_R3_u->cellValues())));
-      REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) == sum(integrate_locally(p_R1x1_u->cellValues())));
-      REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) == sum(integrate_locally(p_R2x2_u->cellValues())));
-      REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) == sum(integrate_locally(p_R3x3_u->cellValues())));
-
-      REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        SECTION("sum_of_R* Vh -> R*")
+        {
+          REQUIRE(sum_of<double>(p_u) == sum(p_u->cellValues()));
+          REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->cellValues()));
+          REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->cellValues()));
+          REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->cellValues()));
+          REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->cellValues()));
+          REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->cellValues()));
+          REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->cellValues()));
+
+          REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        }
+
+        SECTION("integral_of_R* Vh -> R*")
+        {
+          auto integrate_locally = [&](const auto& cell_values) {
+            const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
+            using DataType = decltype(double{} * cell_values[CellId{0}]);
+            CellValue<DataType> local_integral{mesh->connectivity()};
+            parallel_for(
+              local_integral.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
+            return local_integral;
+          };
+
+          REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->cellValues())));
+          REQUIRE(integral_of<TinyVector<1>>(p_R1_u) == sum(integrate_locally(p_R1_u->cellValues())));
+          REQUIRE(integral_of<TinyVector<2>>(p_R2_u) == sum(integrate_locally(p_R2_u->cellValues())));
+          REQUIRE(integral_of<TinyVector<3>>(p_R3_u) == sum(integrate_locally(p_R3_u->cellValues())));
+          REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) == sum(integrate_locally(p_R1x1_u->cellValues())));
+          REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) == sum(integrate_locally(p_R2x2_u->cellValues())));
+          REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) == sum(integrate_locally(p_R3x3_u->cellValues())));
+
+          REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        }
+      }
     }
   }
 
@@ -606,494 +618,506 @@ TEST_CASE("EmbeddedIDiscreteFunctionMathFunctions", "[scheme]")
 
     using Rd = TinyVector<Dimension>;
 
-    std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
-
-    std::shared_ptr other_mesh =
-      std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-    CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-    CellValue<double> values = [=] {
-      CellValue<double> build_values{mesh->connectivity()};
-      parallel_for(
-        build_values.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-      return build_values;
-    }();
-
-    CellValue<double> positive_values = [=] {
-      CellValue<double> build_values{mesh->connectivity()};
-      parallel_for(
-        build_values.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
-      return build_values;
-    }();
-
-    CellValue<double> bounded_values = [=] {
-      CellValue<double> build_values{mesh->connectivity()};
-      parallel_for(
-        build_values.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
-      return build_values;
-    }();
-
-    std::shared_ptr p_u            = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, values);
-    std::shared_ptr p_other_mesh_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, values);
-    std::shared_ptr p_positive_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, positive_values);
-    std::shared_ptr p_bounded_u  = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, bounded_values);
-
-    std::shared_ptr p_R1_u = [=] {
-      CellValue<TinyVector<1>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R1_v = [=] {
-      CellValue<TinyVector<1>> vj{mesh->connectivity()};
-      parallel_for(
-        vj.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-    }();
-
-    std::shared_ptr p_other_mesh_R1_u =
-      std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-    constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-      if constexpr (Dimension == 1) {
-        return {x[0], 1 + x[0] * x[0]};
-      } else if constexpr (Dimension == 2) {
-        return {x[0], x[1]};
-      } else if constexpr (Dimension == 3) {
-        return {x[0], x[1] + x[2]};
-      }
-    };
-
-    std::shared_ptr p_R2_u = [=] {
-      CellValue<TinyVector<2>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<2> x = to_2d(xj[cell_id]);
-          uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R2_v = [=] {
-      CellValue<TinyVector<2>> vj{mesh->connectivity()};
-      parallel_for(
-        vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<2> x = to_2d(xj[cell_id]);
-          vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-    }();
-
-    std::shared_ptr p_other_mesh_R2_u =
-      std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-    constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-      if constexpr (Dimension == 1) {
-        return {x[0], 1 + x[0] * x[0], 2 - x[0]};
-      } else if constexpr (Dimension == 2) {
-        return {x[0], x[1], x[0] + x[1]};
-      } else if constexpr (Dimension == 3) {
-        return {x[0], x[1], x[2]};
-      }
-    };
-
-    std::shared_ptr p_R3_u = [=] {
-      CellValue<TinyVector<3>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R3_v = [=] {
-      CellValue<TinyVector<3>> vj{mesh->connectivity()};
-      parallel_for(
-        vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-    }();
-
-    std::shared_ptr p_other_mesh_R3_u =
-      std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-    std::shared_ptr p_R1x1_u = [=] {
-      CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R2x2_u = [=] {
-      CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<2> x = to_2d(xj[cell_id]);
-
-          uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
-                         2 * x[1], -x[0]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R3x3_u = [=] {
-      CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-
-          uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
-                         2 * x[1],        -x[0],           x[0] - x[1],   //
-                         3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_Vector3_u = [=] {
-      CellArray<double> uj_vector{mesh->connectivity(), 3};
-      parallel_for(
-        uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          uj_vector[cell_id][0] = 2 * x[0] + 1;
-          uj_vector[cell_id][1] = 1 - x[1] * x[2];
-          uj_vector[cell_id][2] = x[0] + x[2];
-        });
-
-      return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-    }();
-
-    std::shared_ptr p_Vector3_v = [=] {
-      CellArray<double> vj_vector{mesh->connectivity(), 3};
-      parallel_for(
-        vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          vj_vector[cell_id][0] = x[0] * x[1] + 1;
-          vj_vector[cell_id][1] = 2 * x[1];
-          vj_vector[cell_id][2] = x[2] * x[0];
-        });
-
-      return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-    }();
-
-    std::shared_ptr p_Vector2_w = [=] {
-      CellArray<double> wj_vector{mesh->connectivity(), 2};
-      parallel_for(
-        wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          wj_vector[cell_id][0] = x[0] + x[1] * 2;
-          wj_vector[cell_id][1] = x[0] * x[1];
-        });
-
-      return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-    }();
-
-    SECTION("sqrt Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, sqrt);
-      REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    SECTION("abs Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, abs);
-      REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh = named_mesh.mesh();
+
+        std::shared_ptr other_mesh =
+          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+        CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+        CellValue<double> values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        CellValue<double> positive_values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        CellValue<double> bounded_values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        std::shared_ptr p_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, values);
+        std::shared_ptr p_other_mesh_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, values);
+        std::shared_ptr p_positive_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, positive_values);
+        std::shared_ptr p_bounded_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, bounded_values);
+
+        std::shared_ptr p_R1_u = [=] {
+          CellValue<TinyVector<1>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R1_v = [=] {
+          CellValue<TinyVector<1>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
+        }();
+
+        std::shared_ptr p_other_mesh_R1_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
+
+        constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+          if constexpr (Dimension == 1) {
+            return {x[0], 1 + x[0] * x[0]};
+          } else if constexpr (Dimension == 2) {
+            return {x[0], x[1]};
+          } else if constexpr (Dimension == 3) {
+            return {x[0], x[1] + x[2]};
+          }
+        };
+
+        std::shared_ptr p_R2_u = [=] {
+          CellValue<TinyVector<2>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+              uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R2_v = [=] {
+          CellValue<TinyVector<2>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+              vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
+        }();
+
+        std::shared_ptr p_other_mesh_R2_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
+
+        constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+          if constexpr (Dimension == 1) {
+            return {x[0], 1 + x[0] * x[0], 2 - x[0]};
+          } else if constexpr (Dimension == 2) {
+            return {x[0], x[1], x[0] + x[1]};
+          } else if constexpr (Dimension == 3) {
+            return {x[0], x[1], x[2]};
+          }
+        };
+
+        std::shared_ptr p_R3_u = [=] {
+          CellValue<TinyVector<3>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R3_v = [=] {
+          CellValue<TinyVector<3>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
+        }();
+
+        std::shared_ptr p_other_mesh_R3_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
+
+        std::shared_ptr p_R1x1_u = [=] {
+          CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R2x2_u = [=] {
+          CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+
+              uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
+                             2 * x[1], -x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R3x3_u = [=] {
+          CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+
+              uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
+                             2 * x[1],        -x[0],           x[0] - x[1],   //
+                             3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_Vector3_u = [=] {
+          CellArray<double> uj_vector{mesh->connectivity(), 3};
+          parallel_for(
+            uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              uj_vector[cell_id][0] = 2 * x[0] + 1;
+              uj_vector[cell_id][1] = 1 - x[1] * x[2];
+              uj_vector[cell_id][2] = x[0] + x[2];
+            });
+
+          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
+        }();
+
+        std::shared_ptr p_Vector3_v = [=] {
+          CellArray<double> vj_vector{mesh->connectivity(), 3};
+          parallel_for(
+            vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              vj_vector[cell_id][0] = x[0] * x[1] + 1;
+              vj_vector[cell_id][1] = 2 * x[1];
+              vj_vector[cell_id][2] = x[2] * x[0];
+            });
+
+          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
+        }();
+
+        std::shared_ptr p_Vector2_w = [=] {
+          CellArray<double> wj_vector{mesh->connectivity(), 2};
+          parallel_for(
+            wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              wj_vector[cell_id][0] = x[0] + x[1] * 2;
+              wj_vector[cell_id][1] = x[0] * x[1];
+            });
+
+          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
+        }();
+
+        SECTION("sqrt Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, sqrt);
+          REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("sin Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sin);
-      REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("abs Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, abs);
+          REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("cos Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cos);
-      REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("sin Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sin);
+          REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("tan Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tan);
-      REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("cos Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cos);
+          REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("asin Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, asin);
-      REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("tan Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tan);
+          REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("acos Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, acos);
-      REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("asin Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, asin);
+          REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atan Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atan);
-      REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("acos Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, acos);
+          REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("sinh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sinh);
-      REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("atan Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atan);
+          REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("cosh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cosh);
-      REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("sinh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sinh);
+          REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("tanh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tanh);
-      REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("cosh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cosh);
+          REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("asinh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, asinh);
-      REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("tanh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tanh);
+          REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("acosh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, acosh);
-      REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("asinh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, asinh);
+          REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atanh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atanh);
-      REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("acosh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, acosh);
+          REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("exp Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, exp);
-      REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("atanh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atanh);
+          REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("log Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, log);
-      REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("exp Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, exp);
+          REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atan2 Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2);
-      REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-      REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-    }
+        SECTION("log Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, log);
+          REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atan2 Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2);
-      REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("atan2 Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2);
+          REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+          REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+        }
 
-    SECTION("atan2 R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2);
-      REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("atan2 Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2);
+          REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("min Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min);
-      REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-    }
+        SECTION("atan2 R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2);
+          REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-    SECTION("min Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min);
-      REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("min Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min);
+          REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
 
-    SECTION("min R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min);
-      REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("min Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min);
+          REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("min Vh -> R")
-    {
-      REQUIRE(min(std::shared_ptr<const IDiscreteFunction>{p_u}) == min(p_u->cellValues()));
-      REQUIRE_THROWS_WITH(min(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                          "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("min R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min);
+          REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-    SECTION("max Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max);
-      REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-    }
+        SECTION("min Vh -> R")
+        {
+          REQUIRE(min(std::shared_ptr<const IDiscreteFunction>{p_u}) == min(p_u->cellValues()));
+          REQUIRE_THROWS_WITH(min(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
+                              "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("max Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max);
-      REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("max Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max);
+          REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
 
-    SECTION("max Vh -> R")
-    {
-      REQUIRE(max(std::shared_ptr<const IDiscreteFunction>{p_u}) == max(p_u->cellValues()));
-      REQUIRE_THROWS_WITH(max(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                          "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("max Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max);
+          REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("max R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max);
-      REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("max Vh -> R")
+        {
+          REQUIRE(max(std::shared_ptr<const IDiscreteFunction>{p_u}) == max(p_u->cellValues()));
+          REQUIRE_THROWS_WITH(max(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
+                              "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("pow Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow);
-      REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-    }
+        SECTION("max R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max);
+          REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-    SECTION("pow Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow);
-      REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("pow Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow);
+          REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
 
-    SECTION("pow R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow);
-      REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("pow Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow);
+          REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("dot Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot);
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot);
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot);
+        SECTION("pow R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow);
+          REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-      {
-        auto p_UV = dot(p_Vector3_u, p_Vector3_v);
-        REQUIRE(p_UV.use_count() == 1);
+        SECTION("dot Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot);
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot);
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot);
 
-        auto UV        = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_UV);
-        auto direct_UV = dot(*p_Vector3_u, *p_Vector3_v);
+          {
+            auto p_UV = dot(p_Vector3_u, p_Vector3_v);
+            REQUIRE(p_UV.use_count() == 1);
 
-        bool is_same = true;
-        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-          if (UV[cell_id] != direct_UV[cell_id]) {
-            is_same = false;
-            break;
-          }
-        }
+            auto UV        = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_UV);
+            auto direct_UV = dot(*p_Vector3_u, *p_Vector3_v);
 
-        REQUIRE(is_same);
-      }
+            bool is_same = true;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              if (UV[cell_id] != direct_UV[cell_id]) {
+                is_same = false;
+                break;
+              }
+            }
 
-      REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
-      REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
-    }
+            REQUIRE(is_same);
+          }
 
-    SECTION("dot Vh*Rd -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot);
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot);
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot);
-      REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})), "error: incompatible operand types Vh(P0:R^1) and R^2");
-      REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
-                          "error: incompatible operand types Vh(P0:R^2) and R^3");
-      REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
-    }
+          REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
+        }
 
-    SECTION("dot Rd*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot);
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot);
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot);
-      REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u), "error: incompatible operand types R^2 and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
-                          "error: incompatible operand types R^3 and Vh(P0:R^2)");
-      REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
-    }
+        SECTION("dot Vh*Rd -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot);
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot);
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot);
+          REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})),
+                              "error: incompatible operand types Vh(P0:R^1) and R^2");
+          REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
+                              "error: incompatible operand types Vh(P0:R^2) and R^3");
+          REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
+        }
 
-    SECTION("sum_of_R* Vh -> R*")
-    {
-      REQUIRE(sum_of<double>(p_u) == sum(p_u->cellValues()));
-      REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->cellValues()));
-      REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->cellValues()));
-      REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->cellValues()));
-      REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->cellValues()));
-      REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->cellValues()));
-      REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->cellValues()));
-
-      REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
-    }
+        SECTION("dot Rd*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot);
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot);
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot);
+          REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u),
+                              "error: incompatible operand types R^2 and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
+                              "error: incompatible operand types R^3 and Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
+        }
+
+        SECTION("sum_of_R* Vh -> R*")
+        {
+          REQUIRE(sum_of<double>(p_u) == sum(p_u->cellValues()));
+          REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->cellValues()));
+          REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->cellValues()));
+          REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->cellValues()));
+          REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->cellValues()));
+          REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->cellValues()));
+          REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->cellValues()));
+
+          REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        }
 
-    SECTION("integral_of_R* Vh -> R*")
-    {
-      auto integrate_locally = [&](const auto& cell_values) {
-        const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
-        using DataType = decltype(double{} * cell_values[CellId{0}]);
-        CellValue<DataType> local_integral{mesh->connectivity()};
-        parallel_for(
-          local_integral.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
-        return local_integral;
-      };
-
-      REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->cellValues())));
-      REQUIRE(integral_of<TinyVector<1>>(p_R1_u) == sum(integrate_locally(p_R1_u->cellValues())));
-      REQUIRE(integral_of<TinyVector<2>>(p_R2_u) == sum(integrate_locally(p_R2_u->cellValues())));
-      REQUIRE(integral_of<TinyVector<3>>(p_R3_u) == sum(integrate_locally(p_R3_u->cellValues())));
-      REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) == sum(integrate_locally(p_R1x1_u->cellValues())));
-      REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) == sum(integrate_locally(p_R2x2_u->cellValues())));
-      REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) == sum(integrate_locally(p_R3x3_u->cellValues())));
-
-      REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        SECTION("integral_of_R* Vh -> R*")
+        {
+          auto integrate_locally = [&](const auto& cell_values) {
+            const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
+            using DataType = decltype(double{} * cell_values[CellId{0}]);
+            CellValue<DataType> local_integral{mesh->connectivity()};
+            parallel_for(
+              local_integral.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
+            return local_integral;
+          };
+
+          REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->cellValues())));
+          REQUIRE(integral_of<TinyVector<1>>(p_R1_u) == sum(integrate_locally(p_R1_u->cellValues())));
+          REQUIRE(integral_of<TinyVector<2>>(p_R2_u) == sum(integrate_locally(p_R2_u->cellValues())));
+          REQUIRE(integral_of<TinyVector<3>>(p_R3_u) == sum(integrate_locally(p_R3_u->cellValues())));
+          REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) == sum(integrate_locally(p_R1x1_u->cellValues())));
+          REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) == sum(integrate_locally(p_R2x2_u->cellValues())));
+          REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) == sum(integrate_locally(p_R3x3_u->cellValues())));
+
+          REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        }
+      }
     }
   }
 
@@ -1103,494 +1127,506 @@ TEST_CASE("EmbeddedIDiscreteFunctionMathFunctions", "[scheme]")
 
     using Rd = TinyVector<Dimension>;
 
-    std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
-
-    std::shared_ptr other_mesh =
-      std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-    CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-    CellValue<double> values = [=] {
-      CellValue<double> build_values{mesh->connectivity()};
-      parallel_for(
-        build_values.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-      return build_values;
-    }();
-
-    CellValue<double> positive_values = [=] {
-      CellValue<double> build_values{mesh->connectivity()};
-      parallel_for(
-        build_values.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
-      return build_values;
-    }();
-
-    CellValue<double> bounded_values = [=] {
-      CellValue<double> build_values{mesh->connectivity()};
-      parallel_for(
-        build_values.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
-      return build_values;
-    }();
-
-    std::shared_ptr p_u            = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, values);
-    std::shared_ptr p_other_mesh_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, values);
-    std::shared_ptr p_positive_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, positive_values);
-    std::shared_ptr p_bounded_u  = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, bounded_values);
-
-    std::shared_ptr p_R1_u = [=] {
-      CellValue<TinyVector<1>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R1_v = [=] {
-      CellValue<TinyVector<1>> vj{mesh->connectivity()};
-      parallel_for(
-        vj.numberOfItems(),
-        PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-    }();
-
-    std::shared_ptr p_other_mesh_R1_u =
-      std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-    constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-      if constexpr (Dimension == 1) {
-        return {x[0], 1 + x[0] * x[0]};
-      } else if constexpr (Dimension == 2) {
-        return {x[0], x[1]};
-      } else if constexpr (Dimension == 3) {
-        return {x[0], x[1] + x[2]};
-      }
-    };
-
-    std::shared_ptr p_R2_u = [=] {
-      CellValue<TinyVector<2>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<2> x = to_2d(xj[cell_id]);
-          uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R2_v = [=] {
-      CellValue<TinyVector<2>> vj{mesh->connectivity()};
-      parallel_for(
-        vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<2> x = to_2d(xj[cell_id]);
-          vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-    }();
-
-    std::shared_ptr p_other_mesh_R2_u =
-      std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-    constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-      if constexpr (Dimension == 1) {
-        return {x[0], 1 + x[0] * x[0], 2 - x[0]};
-      } else if constexpr (Dimension == 2) {
-        return {x[0], x[1], x[0] + x[1]};
-      } else if constexpr (Dimension == 3) {
-        return {x[0], x[1], x[2]};
-      }
-    };
-
-    std::shared_ptr p_R3_u = [=] {
-      CellValue<TinyVector<3>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R3_v = [=] {
-      CellValue<TinyVector<3>> vj{mesh->connectivity()};
-      parallel_for(
-        vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-    }();
-
-    std::shared_ptr p_other_mesh_R3_u =
-      std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-    std::shared_ptr p_R1x1_u = [=] {
-      CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R2x2_u = [=] {
-      CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<2> x = to_2d(xj[cell_id]);
-
-          uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
-                         2 * x[1], -x[0]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_R3x3_u = [=] {
-      CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-      parallel_for(
-        uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-
-          uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
-                         2 * x[1],        -x[0],           x[0] - x[1],   //
-                         3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-        });
-
-      return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-    }();
-
-    std::shared_ptr p_Vector3_u = [=] {
-      CellArray<double> uj_vector{mesh->connectivity(), 3};
-      parallel_for(
-        uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          uj_vector[cell_id][0] = 2 * x[0] + 1;
-          uj_vector[cell_id][1] = 1 - x[1] * x[2];
-          uj_vector[cell_id][2] = x[0] + x[2];
-        });
-
-      return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-    }();
-
-    std::shared_ptr p_Vector3_v = [=] {
-      CellArray<double> vj_vector{mesh->connectivity(), 3};
-      parallel_for(
-        vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          vj_vector[cell_id][0] = x[0] * x[1] + 1;
-          vj_vector[cell_id][1] = 2 * x[1];
-          vj_vector[cell_id][2] = x[2] * x[0];
-        });
-
-      return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-    }();
-
-    std::shared_ptr p_Vector2_w = [=] {
-      CellArray<double> wj_vector{mesh->connectivity(), 2};
-      parallel_for(
-        wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<3> x = to_3d(xj[cell_id]);
-          wj_vector[cell_id][0] = x[0] + x[1] * 2;
-          wj_vector[cell_id][1] = x[0] * x[1];
-        });
-
-      return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-    }();
-
-    SECTION("sqrt Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, sqrt);
-      REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    SECTION("abs Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, abs);
-      REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh = named_mesh.mesh();
+
+        std::shared_ptr other_mesh =
+          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+        CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+        CellValue<double> values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        CellValue<double> positive_values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        CellValue<double> bounded_values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        std::shared_ptr p_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, values);
+        std::shared_ptr p_other_mesh_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, values);
+        std::shared_ptr p_positive_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, positive_values);
+        std::shared_ptr p_bounded_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, bounded_values);
+
+        std::shared_ptr p_R1_u = [=] {
+          CellValue<TinyVector<1>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R1_v = [=] {
+          CellValue<TinyVector<1>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
+        }();
+
+        std::shared_ptr p_other_mesh_R1_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
+
+        constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+          if constexpr (Dimension == 1) {
+            return {x[0], 1 + x[0] * x[0]};
+          } else if constexpr (Dimension == 2) {
+            return {x[0], x[1]};
+          } else if constexpr (Dimension == 3) {
+            return {x[0], x[1] + x[2]};
+          }
+        };
+
+        std::shared_ptr p_R2_u = [=] {
+          CellValue<TinyVector<2>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+              uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R2_v = [=] {
+          CellValue<TinyVector<2>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+              vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
+        }();
+
+        std::shared_ptr p_other_mesh_R2_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
+
+        constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+          if constexpr (Dimension == 1) {
+            return {x[0], 1 + x[0] * x[0], 2 - x[0]};
+          } else if constexpr (Dimension == 2) {
+            return {x[0], x[1], x[0] + x[1]};
+          } else if constexpr (Dimension == 3) {
+            return {x[0], x[1], x[2]};
+          }
+        };
+
+        std::shared_ptr p_R3_u = [=] {
+          CellValue<TinyVector<3>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R3_v = [=] {
+          CellValue<TinyVector<3>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
+        }();
+
+        std::shared_ptr p_other_mesh_R3_u =
+          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
+
+        std::shared_ptr p_R1x1_u = [=] {
+          CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R2x2_u = [=] {
+          CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+
+              uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
+                             2 * x[1], -x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_R3x3_u = [=] {
+          CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+
+              uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
+                             2 * x[1],        -x[0],           x[0] - x[1],   //
+                             3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
+        }();
+
+        std::shared_ptr p_Vector3_u = [=] {
+          CellArray<double> uj_vector{mesh->connectivity(), 3};
+          parallel_for(
+            uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              uj_vector[cell_id][0] = 2 * x[0] + 1;
+              uj_vector[cell_id][1] = 1 - x[1] * x[2];
+              uj_vector[cell_id][2] = x[0] + x[2];
+            });
+
+          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
+        }();
+
+        std::shared_ptr p_Vector3_v = [=] {
+          CellArray<double> vj_vector{mesh->connectivity(), 3};
+          parallel_for(
+            vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              vj_vector[cell_id][0] = x[0] * x[1] + 1;
+              vj_vector[cell_id][1] = 2 * x[1];
+              vj_vector[cell_id][2] = x[2] * x[0];
+            });
+
+          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
+        }();
+
+        std::shared_ptr p_Vector2_w = [=] {
+          CellArray<double> wj_vector{mesh->connectivity(), 2};
+          parallel_for(
+            wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              wj_vector[cell_id][0] = x[0] + x[1] * 2;
+              wj_vector[cell_id][1] = x[0] * x[1];
+            });
+
+          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
+        }();
+
+        SECTION("sqrt Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, sqrt);
+          REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("sin Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sin);
-      REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("abs Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, abs);
+          REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("cos Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cos);
-      REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("sin Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sin);
+          REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("tan Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tan);
-      REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("cos Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cos);
+          REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("asin Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, asin);
-      REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("tan Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tan);
+          REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("acos Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, acos);
-      REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("asin Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, asin);
+          REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atan Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atan);
-      REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("acos Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, acos);
+          REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("sinh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sinh);
-      REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("atan Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atan);
+          REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("cosh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cosh);
-      REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("sinh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sinh);
+          REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("tanh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tanh);
-      REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("cosh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cosh);
+          REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("asinh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, asinh);
-      REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("tanh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tanh);
+          REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("acosh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, acosh);
-      REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("asinh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, asinh);
+          REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atanh Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atanh);
-      REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("acosh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, acosh);
+          REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("exp Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, exp);
-      REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("atanh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atanh);
+          REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("log Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, log);
-      REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("exp Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, exp);
+          REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atan2 Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2);
-      REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-      REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-    }
+        SECTION("log Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, log);
+          REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("atan2 Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2);
-      REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("atan2 Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2);
+          REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+          REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+        }
 
-    SECTION("atan2 R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2);
-      REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("atan2 Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2);
+          REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("min Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min);
-      REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-    }
+        SECTION("atan2 R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2);
+          REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-    SECTION("min Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min);
-      REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("min Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min);
+          REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
 
-    SECTION("min R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min);
-      REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("min Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min);
+          REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("min Vh -> R")
-    {
-      REQUIRE(min(std::shared_ptr<const IDiscreteFunction>{p_u}) == min(p_u->cellValues()));
-      REQUIRE_THROWS_WITH(min(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                          "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("min R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min);
+          REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-    SECTION("max Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max);
-      REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-    }
+        SECTION("min Vh -> R")
+        {
+          REQUIRE(min(std::shared_ptr<const IDiscreteFunction>{p_u}) == min(p_u->cellValues()));
+          REQUIRE_THROWS_WITH(min(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
+                              "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("max Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max);
-      REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("max Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max);
+          REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
 
-    SECTION("max Vh -> R")
-    {
-      REQUIRE(max(std::shared_ptr<const IDiscreteFunction>{p_u}) == max(p_u->cellValues()));
-      REQUIRE_THROWS_WITH(max(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                          "error: invalid operand type Vh(P0:R^1)");
-    }
+        SECTION("max Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max);
+          REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("max R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max);
-      REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("max Vh -> R")
+        {
+          REQUIRE(max(std::shared_ptr<const IDiscreteFunction>{p_u}) == max(p_u->cellValues()));
+          REQUIRE_THROWS_WITH(max(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
+                              "error: invalid operand type Vh(P0:R^1)");
+        }
 
-    SECTION("pow Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow);
-      REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-    }
+        SECTION("max R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max);
+          REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-    SECTION("pow Vh*R -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow);
-      REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
-    }
+        SECTION("pow Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow);
+          REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
 
-    SECTION("pow R*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow);
-      REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-    }
+        SECTION("pow Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow);
+          REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
 
-    SECTION("dot Vh*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot);
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot);
-      CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot);
+        SECTION("pow R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow);
+          REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
 
-      {
-        auto p_UV = dot(p_Vector3_u, p_Vector3_v);
-        REQUIRE(p_UV.use_count() == 1);
+        SECTION("dot Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot);
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot);
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot);
 
-        auto UV        = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_UV);
-        auto direct_UV = dot(*p_Vector3_u, *p_Vector3_v);
+          {
+            auto p_UV = dot(p_Vector3_u, p_Vector3_v);
+            REQUIRE(p_UV.use_count() == 1);
 
-        bool is_same = true;
-        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-          if (UV[cell_id] != direct_UV[cell_id]) {
-            is_same = false;
-            break;
-          }
-        }
+            auto UV        = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_UV);
+            auto direct_UV = dot(*p_Vector3_u, *p_Vector3_v);
 
-        REQUIRE(is_same);
-      }
+            bool is_same = true;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              if (UV[cell_id] != direct_UV[cell_id]) {
+                is_same = false;
+                break;
+              }
+            }
 
-      REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
-      REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
-      REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
-    }
+            REQUIRE(is_same);
+          }
 
-    SECTION("dot Vh*Rd -> Vh")
-    {
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot);
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot);
-      CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot);
-      REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})), "error: incompatible operand types Vh(P0:R^1) and R^2");
-      REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
-                          "error: incompatible operand types Vh(P0:R^2) and R^3");
-      REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
-    }
+          REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
+        }
 
-    SECTION("dot Rd*Vh -> Vh")
-    {
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot);
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot);
-      CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot);
-      REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u), "error: incompatible operand types R^2 and Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
-                          "error: incompatible operand types R^3 and Vh(P0:R^2)");
-      REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
-    }
+        SECTION("dot Vh*Rd -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot);
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot);
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot);
+          REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})),
+                              "error: incompatible operand types Vh(P0:R^1) and R^2");
+          REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
+                              "error: incompatible operand types Vh(P0:R^2) and R^3");
+          REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
+        }
 
-    SECTION("sum_of_R* Vh -> R*")
-    {
-      REQUIRE(sum_of<double>(p_u) == sum(p_u->cellValues()));
-      REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->cellValues()));
-      REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->cellValues()));
-      REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->cellValues()));
-      REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->cellValues()));
-      REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->cellValues()));
-      REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->cellValues()));
-
-      REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-      REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
-    }
+        SECTION("dot Rd*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot);
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot);
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot);
+          REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u),
+                              "error: incompatible operand types R^2 and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
+                              "error: incompatible operand types R^3 and Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
+        }
 
-    SECTION("integral_of_R* Vh -> R*")
-    {
-      auto integrate_locally = [&](const auto& cell_values) {
-        const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
-        using DataType = decltype(double{} * cell_values[CellId{0}]);
-        CellValue<DataType> local_integral{mesh->connectivity()};
-        parallel_for(
-          local_integral.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
-        return local_integral;
-      };
-
-      REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->cellValues())));
-      REQUIRE(integral_of<TinyVector<1>>(p_R1_u) == sum(integrate_locally(p_R1_u->cellValues())));
-      REQUIRE(integral_of<TinyVector<2>>(p_R2_u) == sum(integrate_locally(p_R2_u->cellValues())));
-      REQUIRE(integral_of<TinyVector<3>>(p_R3_u) == sum(integrate_locally(p_R3_u->cellValues())));
-      REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) == sum(integrate_locally(p_R1x1_u->cellValues())));
-      REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) == sum(integrate_locally(p_R2x2_u->cellValues())));
-      REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) == sum(integrate_locally(p_R3x3_u->cellValues())));
-
-      REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-      REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        SECTION("sum_of_R* Vh -> R*")
+        {
+          REQUIRE(sum_of<double>(p_u) == sum(p_u->cellValues()));
+          REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->cellValues()));
+          REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->cellValues()));
+          REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->cellValues()));
+          REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->cellValues()));
+          REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->cellValues()));
+          REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->cellValues()));
+
+          REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        }
+
+        SECTION("integral_of_R* Vh -> R*")
+        {
+          auto integrate_locally = [&](const auto& cell_values) {
+            const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
+            using DataType = decltype(double{} * cell_values[CellId{0}]);
+            CellValue<DataType> local_integral{mesh->connectivity()};
+            parallel_for(
+              local_integral.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
+            return local_integral;
+          };
+
+          REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->cellValues())));
+          REQUIRE(integral_of<TinyVector<1>>(p_R1_u) == sum(integrate_locally(p_R1_u->cellValues())));
+          REQUIRE(integral_of<TinyVector<2>>(p_R2_u) == sum(integrate_locally(p_R2_u->cellValues())));
+          REQUIRE(integral_of<TinyVector<3>>(p_R3_u) == sum(integrate_locally(p_R3_u->cellValues())));
+          REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) == sum(integrate_locally(p_R1x1_u->cellValues())));
+          REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) == sum(integrate_locally(p_R2x2_u->cellValues())));
+          REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) == sum(integrate_locally(p_R3x3_u->cellValues())));
+
+          REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        }
+      }
     }
   }
 }
diff --git a/tests/test_EmbeddedIDiscreteFunctionOperators.cpp b/tests/test_EmbeddedIDiscreteFunctionOperators.cpp
index 5232e8fe8d05aca29d5d56440071c0718a206627..5d91447d90147537c3428addfea8c2710defed84 100644
--- a/tests/test_EmbeddedIDiscreteFunctionOperators.cpp
+++ b/tests/test_EmbeddedIDiscreteFunctionOperators.cpp
@@ -185,6 +185,10 @@
     REQUIRE(is_same);                                                            \
   }
 
+#ifdef __clang__
+#pragma clang optimize off
+#endif   // __clang__
+
 TEST_CASE("EmbeddedIDiscreteFunctionOperators", "[scheme]")
 {
   SECTION("binary operators")
@@ -195,668 +199,693 @@ TEST_CASE("EmbeddedIDiscreteFunctionOperators", "[scheme]")
 
       using Rd = TinyVector<Dimension>;
 
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
-      std::shared_ptr other_mesh =
-        std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-      CellValue<double> u_R_values = [=] {
-        CellValue<double> build_values{mesh->connectivity()};
-        parallel_for(
-          build_values.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-        return build_values;
-      }();
-
-      CellValue<double> v_R_values = [=] {
-        CellValue<double> build_values{mesh->connectivity()};
-        parallel_for(
-          build_values.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
-        return build_values;
-      }();
-
-      std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-      std::shared_ptr p_other_mesh_R_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, u_R_values);
-      std::shared_ptr p_R_v = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, v_R_values);
-
-      std::shared_ptr p_R1_u = [=] {
-        CellValue<TinyVector<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R1_v = [=] {
-        CellValue<TinyVector<1>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_other_mesh_R1_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1] + x[2]};
-        }
-      };
-
-      std::shared_ptr p_R2_u = [=] {
-        CellValue<TinyVector<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R2_v = [=] {
-        CellValue<TinyVector<2>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-            vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_other_mesh_R2_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0], 2 - x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1], x[0] + x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1], x[2]};
-        }
-      };
-
-      std::shared_ptr p_R3_u = [=] {
-        CellValue<TinyVector<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R3_v = [=] {
-        CellValue<TinyVector<3>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_other_mesh_R3_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-      std::shared_ptr p_R1x1_u = [=] {
-        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_other_mesh_R1x1_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(other_mesh, p_R1x1_u->cellValues());
-
-      std::shared_ptr p_R1x1_v = [=] {
-        CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = {0.3 - xj[cell_id][0]}; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_R2x2_u = [=] {
-        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
-                           2 * x[1], -x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_other_mesh_R2x2_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(other_mesh, p_R2x2_u->cellValues());
-
-      std::shared_ptr p_R2x2_v = [=] {
-        CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-
-            vj[cell_id] = {x[0] + 0.3, 1 - x[1] - x[0],   //
-                           2 * x[1] + x[0], x[1] - x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_R3x3_u = [=] {
-        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
-                           2 * x[1],        -x[0],           x[0] - x[1],   //
-                           3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_other_mesh_R3x3_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(other_mesh, p_R3x3_u->cellValues());
-
-      std::shared_ptr p_R3x3_v = [=] {
-        CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-
-            vj[cell_id] = {0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
-                           2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
-                           2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_Vector3_u = [=] {
-        CellArray<double> uj_vector{mesh->connectivity(), 3};
-        parallel_for(
-          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj_vector[cell_id][0] = 2 * x[0] + 1;
-            uj_vector[cell_id][1] = 1 - x[1] * x[2];
-            uj_vector[cell_id][2] = x[0] + x[2];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-      }();
-
-      std::shared_ptr p_other_mesh_Vector3_u =
-        std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(other_mesh, p_Vector3_u->cellArrays());
-
-      std::shared_ptr p_Vector3_v = [=] {
-        CellArray<double> vj_vector{mesh->connectivity(), 3};
-        parallel_for(
-          vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            vj_vector[cell_id][0] = x[0] * x[1] + 1;
-            vj_vector[cell_id][1] = 2 * x[1];
-            vj_vector[cell_id][2] = x[2] * x[0];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-      }();
-
-      std::shared_ptr p_Vector2_w = [=] {
-        CellArray<double> wj_vector{mesh->connectivity(), 2};
-        parallel_for(
-          wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            wj_vector[cell_id][0] = x[0] + x[1] * 2;
-            wj_vector[cell_id][1] = x[0] * x[1];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-      }();
-
-      SECTION("sum")
-      {
-        SECTION("Vh + Vh -> Vh")
-        {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, +, p_R_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1_u, +, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2_u, +, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3_u, +, p_R3_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v);
-
-          CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v);
-
-          REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-          REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-        }
-
-        SECTION("Vh + X -> Vh")
-        {
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, bool{true});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, uint64_t{1});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, int64_t{2});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, double{1.3});
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, +,
-                                  (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}));
-
-          REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-          REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-          REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^1");
-          REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^2");
-        }
-
-        SECTION("X + Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, +, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, +, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, +, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, +, p_R_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}),
-                                  +, p_R3x3_u);
-
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-          REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
-                              "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
-                              "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-        }
-      }
-
-      SECTION("difference")
-      {
-        SECTION("Vh - Vh -> Vh")
-        {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, -, p_R_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1_u, -, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2_u, -, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3_u, -, p_R3_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v);
-
-          CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v);
-
-          REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-          REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-        }
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-        SECTION("Vh - X -> Vh")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, bool{true});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, uint64_t{1});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, int64_t{2});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, double{1.3});
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, -,
-                                  (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}));
-
-          REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-          REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-          REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^1");
-          REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^2");
-        }
-
-        SECTION("X - Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, -, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, -, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, -, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, -, p_R_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}),
-                                  -, p_R3x3_u);
-
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-          REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
-                              "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
-                              "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-        }
-      }
-
-      SECTION("product")
-      {
-        SECTION("Vh * Vh -> Vh")
-        {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3_v);
+          auto mesh = named_mesh.mesh();
+
+          std::shared_ptr other_mesh =
+            std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          CellValue<double> u_R_values = [=] {
+            CellValue<double> build_values{mesh->connectivity()};
+            parallel_for(
+              build_values.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+            return build_values;
+          }();
+
+          CellValue<double> v_R_values = [=] {
+            CellValue<double> build_values{mesh->connectivity()};
+            parallel_for(
+              build_values.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
+            return build_values;
+          }();
+
+          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
+          std::shared_ptr p_other_mesh_R_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, u_R_values);
+          std::shared_ptr p_R_v = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, v_R_values);
+
+          std::shared_ptr p_R1_u = [=] {
+            CellValue<TinyVector<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R1_v = [=] {
+            CellValue<TinyVector<1>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_other_mesh_R1_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
+
+          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1] + x[2]};
+            }
+          };
+
+          std::shared_ptr p_R2_u = [=] {
+            CellValue<TinyVector<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R2_v = [=] {
+            CellValue<TinyVector<2>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+                vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_other_mesh_R2_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
+
+          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0], 2 - x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1], x[0] + x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1], x[2]};
+            }
+          };
+
+          std::shared_ptr p_R3_u = [=] {
+            CellValue<TinyVector<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R3_v = [=] {
+            CellValue<TinyVector<3>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_other_mesh_R3_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
+
+          std::shared_ptr p_R1x1_u = [=] {
+            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_other_mesh_R1x1_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(other_mesh, p_R1x1_u->cellValues());
+
+          std::shared_ptr p_R1x1_v = [=] {
+            CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = {0.3 - xj[cell_id][0]}; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_R2x2_u = [=] {
+            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
+                               2 * x[1], -x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_other_mesh_R2x2_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(other_mesh, p_R2x2_u->cellValues());
+
+          std::shared_ptr p_R2x2_v = [=] {
+            CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+
+                vj[cell_id] = {x[0] + 0.3, 1 - x[1] - x[0],   //
+                               2 * x[1] + x[0], x[1] - x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_R3x3_u = [=] {
+            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
+                               2 * x[1],        -x[0],           x[0] - x[1],   //
+                               3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_other_mesh_R3x3_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(other_mesh, p_R3x3_u->cellValues());
+
+          std::shared_ptr p_R3x3_v = [=] {
+            CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+
+                vj[cell_id] = {0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
+                               2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
+                               2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_Vector3_u = [=] {
+            CellArray<double> uj_vector{mesh->connectivity(), 3};
+            parallel_for(
+              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj_vector[cell_id][0] = 2 * x[0] + 1;
+                uj_vector[cell_id][1] = 1 - x[1] * x[2];
+                uj_vector[cell_id][2] = x[0] + x[2];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
+          }();
+
+          std::shared_ptr p_other_mesh_Vector3_u =
+            std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(other_mesh, p_Vector3_u->cellArrays());
+
+          std::shared_ptr p_Vector3_v = [=] {
+            CellArray<double> vj_vector{mesh->connectivity(), 3};
+            parallel_for(
+              vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                vj_vector[cell_id][0] = x[0] * x[1] + 1;
+                vj_vector[cell_id][1] = 2 * x[1];
+                vj_vector[cell_id][2] = x[2] * x[0];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
+          }();
+
+          std::shared_ptr p_Vector2_w = [=] {
+            CellArray<double> wj_vector{mesh->connectivity(), 2};
+            parallel_for(
+              wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                wj_vector[cell_id][0] = x[0] + x[1] * 2;
+                wj_vector[cell_id][1] = x[0] * x[1];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
+          }();
+
+          SECTION("sum")
+          {
+            SECTION("Vh + Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, +, p_R_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1_u, +, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2_u, +, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3_u, +, p_R3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v);
+
+              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v);
+
+              REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+              REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u,
+                                  "error: operands are defined on different meshes");
+            }
 
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3x3_v);
+            SECTION("Vh + X -> Vh")
+            {
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, bool{true});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, uint64_t{1});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, int64_t{2});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, double{1.3});
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}));
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, +,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}));
+
+              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+              REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
+              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
+            }
 
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3_v);
+            SECTION("X + Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, +, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, +, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, +, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, +, p_R_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      +, p_R3x3_u);
+
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u,
+                                  "error: incompatible operand types R^3 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+              REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
+                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
+                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+            }
+          }
 
+          SECTION("difference")
           {
-            std::shared_ptr p_fuv = p_R_u * p_Vector3_v;
+            SECTION("Vh - Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, -, p_R_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1_u, -, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2_u, -, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3_u, -, p_R3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v);
+
+              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v);
+
+              REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+              REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u,
+                                  "error: operands are defined on different meshes");
+            }
 
-            REQUIRE(p_fuv.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv));
+            SECTION("Vh - X -> Vh")
+            {
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, bool{true});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, uint64_t{1});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, int64_t{2});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, double{1.3});
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}));
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, -,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}));
+
+              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+              REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
+              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
+            }
 
-            const auto& fuv = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv);
+            SECTION("X - Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, -, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, -, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, -, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, -, p_R_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      -, p_R3x3_u);
+
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u,
+                                  "error: incompatible operand types R^3 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+              REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
+                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
+                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+            }
+          }
 
-            auto lhs_values = p_R_u->cellValues();
-            auto rhs_arrays = p_Vector3_v->cellArrays();
-            bool is_same    = true;
-            for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {
-              for (size_t i = 0; i < fuv.size(); ++i) {
-                if (fuv[cell_id][i] != (lhs_values[cell_id] * rhs_arrays[cell_id][i])) {
-                  is_same = false;
-                  break;
+          SECTION("product")
+          {
+            SECTION("Vh * Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3x3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3_v);
+
+              {
+                std::shared_ptr p_fuv = p_R_u * p_Vector3_v;
+
+                REQUIRE(p_fuv.use_count() > 0);
+                REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv));
+
+                const auto& fuv = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv);
+
+                auto lhs_values = p_R_u->cellValues();
+                auto rhs_arrays = p_Vector3_v->cellArrays();
+                bool is_same    = true;
+                for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {
+                  for (size_t i = 0; i < fuv.size(); ++i) {
+                    if (fuv[cell_id][i] != (lhs_values[cell_id] * rhs_arrays[cell_id][i])) {
+                      is_same = false;
+                      break;
+                    }
+                  }
                 }
-              }
-            }
 
-            REQUIRE(is_same);
-          }
+                REQUIRE(is_same);
+              }
 
-          REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
-
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
-
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
-
-          REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
-
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
-
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-        }
+              REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
+
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v,
+                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v,
+                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v,
+                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
+
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
+
+              REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
+
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
+
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
+            }
 
-        SECTION("Vh * X -> Vh")
-        {
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, bool{true});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, uint64_t{1});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, int64_t{2});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, double{1.3});
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, *,
-                                  (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}));
-
-          REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
-          REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R^2) and R^2");
-          REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
-                              "error: incompatible operand types Vh(P0:R^3) and R^3");
-          REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R^1) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R^2) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R^3) and R^3x3");
-          REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
-                              "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
-
-          REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
-          REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
-        }
+            SECTION("Vh * X -> Vh")
+            {
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, bool{true});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, uint64_t{1});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, int64_t{2});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, double{1.3});
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, *,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}));
+
+              REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
+              REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}),
+                                  "error: incompatible operand types Vh(P0:R^2) and R^2");
+              REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
+                                  "error: incompatible operand types Vh(P0:R^3) and R^3");
+              REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}),
+                                  "error: incompatible operand types Vh(P0:R^1) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R^2) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R^3) and R^3x3");
+              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
+                                  "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
+
+              REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
+              REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
+            }
 
-        SECTION("X * Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R_u);
-
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R1x1_u);
-
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R2x2_u);
-
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R3x3_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R3x3_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+            SECTION("X * Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R_u);
+
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R1x1_u);
+
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R2x2_u);
+
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R3x3_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R3x3_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
                                                                      4.7, 2.3, 7.1,   //
                                                                      9.7, 3.2, 6.8}),
                                                       *, p_R3_u);
 
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
                                                                      4.7, 2.3, 7.1,   //
                                                                      9.7, 3.2, 6.8}),
                                                       *, p_R3x3_u);
 
-          CHECK_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u);
-          CHECK_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u);
-          CHECK_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u);
-          CHECK_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u);
-
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u, "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
-
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
-                              "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
-
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
-                              "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
-        }
-      }
+              CHECK_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u);
+              CHECK_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u);
+              CHECK_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u);
+              CHECK_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u);
+
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u,
+                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
+
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
+                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
+
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
+                                  "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
+            }
+          }
 
-      SECTION("ratio")
-      {
-        SECTION("Vh / Vh -> Vh")
-        {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, /, p_R_v);
+          SECTION("ratio")
+          {
+            SECTION("Vh / Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, /, p_R_v);
 
-          REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
 
-          REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
-        }
+              REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
+            }
 
-        SECTION("X / Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, /, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, /, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, /, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, /, p_R_u);
+            SECTION("X / Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, /, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, /, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, /, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, /, p_R_u);
+            }
+          }
         }
       }
     }
@@ -867,668 +896,693 @@ TEST_CASE("EmbeddedIDiscreteFunctionOperators", "[scheme]")
 
       using Rd = TinyVector<Dimension>;
 
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
-
-      std::shared_ptr other_mesh =
-        std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-      CellValue<double> u_R_values = [=] {
-        CellValue<double> build_values{mesh->connectivity()};
-        parallel_for(
-          build_values.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-        return build_values;
-      }();
-
-      CellValue<double> v_R_values = [=] {
-        CellValue<double> build_values{mesh->connectivity()};
-        parallel_for(
-          build_values.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
-        return build_values;
-      }();
-
-      std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-      std::shared_ptr p_other_mesh_R_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, u_R_values);
-      std::shared_ptr p_R_v = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, v_R_values);
-
-      std::shared_ptr p_R1_u = [=] {
-        CellValue<TinyVector<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R1_v = [=] {
-        CellValue<TinyVector<1>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_other_mesh_R1_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1] + x[2]};
-        }
-      };
-
-      std::shared_ptr p_R2_u = [=] {
-        CellValue<TinyVector<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R2_v = [=] {
-        CellValue<TinyVector<2>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-            vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_other_mesh_R2_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0], 2 - x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1], x[0] + x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1], x[2]};
-        }
-      };
-
-      std::shared_ptr p_R3_u = [=] {
-        CellValue<TinyVector<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R3_v = [=] {
-        CellValue<TinyVector<3>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_other_mesh_R3_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-      std::shared_ptr p_R1x1_u = [=] {
-        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_other_mesh_R1x1_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(other_mesh, p_R1x1_u->cellValues());
-
-      std::shared_ptr p_R1x1_v = [=] {
-        CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = {0.3 - xj[cell_id][0]}; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_R2x2_u = [=] {
-        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
-                           2 * x[1], -x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_other_mesh_R2x2_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(other_mesh, p_R2x2_u->cellValues());
-
-      std::shared_ptr p_R2x2_v = [=] {
-        CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-
-            vj[cell_id] = {x[0] + 0.3, 1 - x[1] - x[0],   //
-                           2 * x[1] + x[0], x[1] - x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_R3x3_u = [=] {
-        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
-                           2 * x[1],        -x[0],           x[0] - x[1],   //
-                           3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_other_mesh_R3x3_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(other_mesh, p_R3x3_u->cellValues());
-
-      std::shared_ptr p_R3x3_v = [=] {
-        CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-
-            vj[cell_id] = {0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
-                           2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
-                           2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_Vector3_u = [=] {
-        CellArray<double> uj_vector{mesh->connectivity(), 3};
-        parallel_for(
-          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj_vector[cell_id][0] = 2 * x[0] + 1;
-            uj_vector[cell_id][1] = 1 - x[1] * x[2];
-            uj_vector[cell_id][2] = x[0] + x[2];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-      }();
-
-      std::shared_ptr p_other_mesh_Vector3_u =
-        std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(other_mesh, p_Vector3_u->cellArrays());
-
-      std::shared_ptr p_Vector3_v = [=] {
-        CellArray<double> vj_vector{mesh->connectivity(), 3};
-        parallel_for(
-          vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            vj_vector[cell_id][0] = x[0] * x[1] + 1;
-            vj_vector[cell_id][1] = 2 * x[1];
-            vj_vector[cell_id][2] = x[2] * x[0];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-      }();
-
-      std::shared_ptr p_Vector2_w = [=] {
-        CellArray<double> wj_vector{mesh->connectivity(), 2};
-        parallel_for(
-          wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            wj_vector[cell_id][0] = x[0] + x[1] * 2;
-            wj_vector[cell_id][1] = x[0] * x[1];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-      }();
-
-      SECTION("sum")
-      {
-        SECTION("Vh + Vh -> Vh")
-        {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, +, p_R_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1_u, +, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2_u, +, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3_u, +, p_R3_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v);
-
-          CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v);
-
-          REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-          REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-        }
-
-        SECTION("Vh + X -> Vh")
-        {
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, bool{true});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, uint64_t{1});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, int64_t{2});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, double{1.3});
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, +,
-                                  (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}));
-
-          REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-          REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-          REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^1");
-          REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^2");
-        }
-
-        SECTION("X + Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, +, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, +, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, +, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, +, p_R_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}),
-                                  +, p_R3x3_u);
-
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-          REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
-                              "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
-                              "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-        }
-      }
-
-      SECTION("difference")
-      {
-        SECTION("Vh - Vh -> Vh")
-        {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, -, p_R_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1_u, -, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2_u, -, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3_u, -, p_R3_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v);
-
-          CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v);
-
-          REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-          REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-        }
-
-        SECTION("Vh - X -> Vh")
-        {
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, bool{true});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, uint64_t{1});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, int64_t{2});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, double{1.3});
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, -,
-                                  (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}));
-
-          REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-          REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-          REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^1");
-          REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^2");
-        }
-
-        SECTION("X - Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, -, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, -, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, -, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, -, p_R_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}),
-                                  -, p_R3x3_u);
-
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-          REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
-                              "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
-                              "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-        }
-      }
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      SECTION("product")
-      {
-        SECTION("Vh * Vh -> Vh")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3_v);
+          auto mesh = named_mesh.mesh();
+
+          std::shared_ptr other_mesh =
+            std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          CellValue<double> u_R_values = [=] {
+            CellValue<double> build_values{mesh->connectivity()};
+            parallel_for(
+              build_values.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+            return build_values;
+          }();
+
+          CellValue<double> v_R_values = [=] {
+            CellValue<double> build_values{mesh->connectivity()};
+            parallel_for(
+              build_values.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
+            return build_values;
+          }();
+
+          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
+          std::shared_ptr p_other_mesh_R_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, u_R_values);
+          std::shared_ptr p_R_v = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, v_R_values);
+
+          std::shared_ptr p_R1_u = [=] {
+            CellValue<TinyVector<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R1_v = [=] {
+            CellValue<TinyVector<1>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_other_mesh_R1_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
+
+          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1] + x[2]};
+            }
+          };
+
+          std::shared_ptr p_R2_u = [=] {
+            CellValue<TinyVector<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R2_v = [=] {
+            CellValue<TinyVector<2>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+                vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_other_mesh_R2_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
+
+          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0], 2 - x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1], x[0] + x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1], x[2]};
+            }
+          };
+
+          std::shared_ptr p_R3_u = [=] {
+            CellValue<TinyVector<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R3_v = [=] {
+            CellValue<TinyVector<3>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_other_mesh_R3_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
+
+          std::shared_ptr p_R1x1_u = [=] {
+            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_other_mesh_R1x1_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(other_mesh, p_R1x1_u->cellValues());
+
+          std::shared_ptr p_R1x1_v = [=] {
+            CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = {0.3 - xj[cell_id][0]}; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_R2x2_u = [=] {
+            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
+                               2 * x[1], -x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_other_mesh_R2x2_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(other_mesh, p_R2x2_u->cellValues());
+
+          std::shared_ptr p_R2x2_v = [=] {
+            CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+
+                vj[cell_id] = {x[0] + 0.3, 1 - x[1] - x[0],   //
+                               2 * x[1] + x[0], x[1] - x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_R3x3_u = [=] {
+            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
+                               2 * x[1],        -x[0],           x[0] - x[1],   //
+                               3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_other_mesh_R3x3_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(other_mesh, p_R3x3_u->cellValues());
+
+          std::shared_ptr p_R3x3_v = [=] {
+            CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+
+                vj[cell_id] = {0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
+                               2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
+                               2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_Vector3_u = [=] {
+            CellArray<double> uj_vector{mesh->connectivity(), 3};
+            parallel_for(
+              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj_vector[cell_id][0] = 2 * x[0] + 1;
+                uj_vector[cell_id][1] = 1 - x[1] * x[2];
+                uj_vector[cell_id][2] = x[0] + x[2];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
+          }();
+
+          std::shared_ptr p_other_mesh_Vector3_u =
+            std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(other_mesh, p_Vector3_u->cellArrays());
+
+          std::shared_ptr p_Vector3_v = [=] {
+            CellArray<double> vj_vector{mesh->connectivity(), 3};
+            parallel_for(
+              vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                vj_vector[cell_id][0] = x[0] * x[1] + 1;
+                vj_vector[cell_id][1] = 2 * x[1];
+                vj_vector[cell_id][2] = x[2] * x[0];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
+          }();
+
+          std::shared_ptr p_Vector2_w = [=] {
+            CellArray<double> wj_vector{mesh->connectivity(), 2};
+            parallel_for(
+              wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                wj_vector[cell_id][0] = x[0] + x[1] * 2;
+                wj_vector[cell_id][1] = x[0] * x[1];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
+          }();
+
+          SECTION("sum")
+          {
+            SECTION("Vh + Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, +, p_R_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1_u, +, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2_u, +, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3_u, +, p_R3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v);
+
+              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v);
+
+              REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+              REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u,
+                                  "error: operands are defined on different meshes");
+            }
 
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3x3_v);
+            SECTION("Vh + X -> Vh")
+            {
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, bool{true});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, uint64_t{1});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, int64_t{2});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, double{1.3});
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}));
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, +,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}));
+
+              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+              REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
+              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
+            }
 
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3_v);
+            SECTION("X + Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, +, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, +, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, +, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, +, p_R_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      +, p_R3x3_u);
+
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u,
+                                  "error: incompatible operand types R^3 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+              REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
+                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
+                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+            }
+          }
 
+          SECTION("difference")
           {
-            std::shared_ptr p_fuv = p_R_u * p_Vector3_v;
+            SECTION("Vh - Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, -, p_R_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1_u, -, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2_u, -, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3_u, -, p_R3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v);
+
+              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v);
+
+              REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+              REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u,
+                                  "error: operands are defined on different meshes");
+            }
 
-            REQUIRE(p_fuv.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv));
+            SECTION("Vh - X -> Vh")
+            {
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, bool{true});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, uint64_t{1});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, int64_t{2});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, double{1.3});
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}));
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, -,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}));
+
+              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+              REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
+              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
+            }
 
-            const auto& fuv = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv);
+            SECTION("X - Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, -, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, -, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, -, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, -, p_R_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      -, p_R3x3_u);
+
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u,
+                                  "error: incompatible operand types R^3 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+              REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
+                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
+                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+            }
+          }
 
-            auto lhs_values = p_R_u->cellValues();
-            auto rhs_arrays = p_Vector3_v->cellArrays();
-            bool is_same    = true;
-            for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {
-              for (size_t i = 0; i < fuv.size(); ++i) {
-                if (fuv[cell_id][i] != (lhs_values[cell_id] * rhs_arrays[cell_id][i])) {
-                  is_same = false;
-                  break;
+          SECTION("product")
+          {
+            SECTION("Vh * Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3x3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3_v);
+
+              {
+                std::shared_ptr p_fuv = p_R_u * p_Vector3_v;
+
+                REQUIRE(p_fuv.use_count() > 0);
+                REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv));
+
+                const auto& fuv = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv);
+
+                auto lhs_values = p_R_u->cellValues();
+                auto rhs_arrays = p_Vector3_v->cellArrays();
+                bool is_same    = true;
+                for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {
+                  for (size_t i = 0; i < fuv.size(); ++i) {
+                    if (fuv[cell_id][i] != (lhs_values[cell_id] * rhs_arrays[cell_id][i])) {
+                      is_same = false;
+                      break;
+                    }
+                  }
                 }
-              }
-            }
 
-            REQUIRE(is_same);
-          }
+                REQUIRE(is_same);
+              }
 
-          REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
-
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
-
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
-
-          REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
-
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
-
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-        }
+              REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
+
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v,
+                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v,
+                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v,
+                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
+
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
+
+              REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
+
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
+
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
+            }
 
-        SECTION("Vh * X -> Vh")
-        {
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, bool{true});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, uint64_t{1});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, int64_t{2});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, double{1.3});
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, *,
-                                  (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}));
-
-          REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
-          REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R^2) and R^2");
-          REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
-                              "error: incompatible operand types Vh(P0:R^3) and R^3");
-          REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R^1) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R^2) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R^3) and R^3x3");
-          REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
-                              "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
-
-          REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
-          REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
-        }
+            SECTION("Vh * X -> Vh")
+            {
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, bool{true});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, uint64_t{1});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, int64_t{2});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, double{1.3});
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, *,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}));
+
+              REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
+              REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}),
+                                  "error: incompatible operand types Vh(P0:R^2) and R^2");
+              REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
+                                  "error: incompatible operand types Vh(P0:R^3) and R^3");
+              REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}),
+                                  "error: incompatible operand types Vh(P0:R^1) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R^2) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R^3) and R^3x3");
+              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
+                                  "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
+
+              REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
+              REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
+            }
 
-        SECTION("X * Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R_u);
-
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R1x1_u);
-
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R2x2_u);
-
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R3x3_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R3x3_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+            SECTION("X * Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R_u);
+
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R1x1_u);
+
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R2x2_u);
+
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R3x3_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R3x3_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
                                                                      4.7, 2.3, 7.1,   //
                                                                      9.7, 3.2, 6.8}),
                                                       *, p_R3_u);
 
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
                                                                      4.7, 2.3, 7.1,   //
                                                                      9.7, 3.2, 6.8}),
                                                       *, p_R3x3_u);
 
-          CHECK_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u);
-          CHECK_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u);
-          CHECK_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u);
-          CHECK_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u);
-
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u, "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
-
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
-                              "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
-
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
-                              "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
-        }
-      }
+              CHECK_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u);
+              CHECK_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u);
+              CHECK_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u);
+              CHECK_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u);
+
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u,
+                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
+
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
+                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
+
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
+                                  "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
+            }
+          }
 
-      SECTION("ratio")
-      {
-        SECTION("Vh / Vh -> Vh")
-        {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, /, p_R_v);
+          SECTION("ratio")
+          {
+            SECTION("Vh / Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, /, p_R_v);
 
-          REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
 
-          REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
-        }
+              REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
+            }
 
-        SECTION("X / Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, /, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, /, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, /, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, /, p_R_u);
+            SECTION("X / Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, /, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, /, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, /, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, /, p_R_u);
+            }
+          }
         }
       }
     }
@@ -1539,668 +1593,693 @@ TEST_CASE("EmbeddedIDiscreteFunctionOperators", "[scheme]")
 
       using Rd = TinyVector<Dimension>;
 
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
-
-      std::shared_ptr other_mesh =
-        std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-      CellValue<double> u_R_values = [=] {
-        CellValue<double> build_values{mesh->connectivity()};
-        parallel_for(
-          build_values.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-        return build_values;
-      }();
-
-      CellValue<double> v_R_values = [=] {
-        CellValue<double> build_values{mesh->connectivity()};
-        parallel_for(
-          build_values.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
-        return build_values;
-      }();
-
-      std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-      std::shared_ptr p_other_mesh_R_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, u_R_values);
-      std::shared_ptr p_R_v = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, v_R_values);
-
-      std::shared_ptr p_R1_u = [=] {
-        CellValue<TinyVector<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R1_v = [=] {
-        CellValue<TinyVector<1>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_other_mesh_R1_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1] + x[2]};
-        }
-      };
-
-      std::shared_ptr p_R2_u = [=] {
-        CellValue<TinyVector<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R2_v = [=] {
-        CellValue<TinyVector<2>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-            vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_other_mesh_R2_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0], 2 - x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1], x[0] + x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1], x[2]};
-        }
-      };
-
-      std::shared_ptr p_R3_u = [=] {
-        CellValue<TinyVector<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R3_v = [=] {
-        CellValue<TinyVector<3>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_other_mesh_R3_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-      std::shared_ptr p_R1x1_u = [=] {
-        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_other_mesh_R1x1_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(other_mesh, p_R1x1_u->cellValues());
-
-      std::shared_ptr p_R1x1_v = [=] {
-        CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = {0.3 - xj[cell_id][0]}; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_R2x2_u = [=] {
-        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
-                           2 * x[1], -x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_other_mesh_R2x2_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(other_mesh, p_R2x2_u->cellValues());
-
-      std::shared_ptr p_R2x2_v = [=] {
-        CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-
-            vj[cell_id] = {x[0] + 0.3, 1 - x[1] - x[0],   //
-                           2 * x[1] + x[0], x[1] - x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_R3x3_u = [=] {
-        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
-                           2 * x[1],        -x[0],           x[0] - x[1],   //
-                           3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_other_mesh_R3x3_u =
-        std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(other_mesh, p_R3x3_u->cellValues());
-
-      std::shared_ptr p_R3x3_v = [=] {
-        CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
-        parallel_for(
-          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-
-            vj[cell_id] = {0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
-                           2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
-                           2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, vj);
-      }();
-
-      std::shared_ptr p_Vector3_u = [=] {
-        CellArray<double> uj_vector{mesh->connectivity(), 3};
-        parallel_for(
-          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj_vector[cell_id][0] = 2 * x[0] + 1;
-            uj_vector[cell_id][1] = 1 - x[1] * x[2];
-            uj_vector[cell_id][2] = x[0] + x[2];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-      }();
-
-      std::shared_ptr p_other_mesh_Vector3_u =
-        std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(other_mesh, p_Vector3_u->cellArrays());
-
-      std::shared_ptr p_Vector3_v = [=] {
-        CellArray<double> vj_vector{mesh->connectivity(), 3};
-        parallel_for(
-          vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            vj_vector[cell_id][0] = x[0] * x[1] + 1;
-            vj_vector[cell_id][1] = 2 * x[1];
-            vj_vector[cell_id][2] = x[2] * x[0];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-      }();
-
-      std::shared_ptr p_Vector2_w = [=] {
-        CellArray<double> wj_vector{mesh->connectivity(), 2};
-        parallel_for(
-          wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            wj_vector[cell_id][0] = x[0] + x[1] * 2;
-            wj_vector[cell_id][1] = x[0] * x[1];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-      }();
-
-      SECTION("sum")
-      {
-        SECTION("Vh + Vh -> Vh")
-        {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, +, p_R_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1_u, +, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2_u, +, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3_u, +, p_R3_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v);
-
-          CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v);
-
-          REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-          REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-        }
-
-        SECTION("Vh + X -> Vh")
-        {
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, bool{true});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, uint64_t{1});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, int64_t{2});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, double{1.3});
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, +,
-                                  (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}));
-
-          REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-          REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-          REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^1");
-          REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^2");
-        }
-
-        SECTION("X + Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, +, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, +, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, +, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, +, p_R_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}),
-                                  +, p_R3x3_u);
-
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-          REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
-                              "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
-                              "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-        }
-      }
-
-      SECTION("difference")
-      {
-        SECTION("Vh - Vh -> Vh")
-        {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, -, p_R_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1_u, -, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2_u, -, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3_u, -, p_R3_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v);
-
-          CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v);
-
-          REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-          REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-        }
-
-        SECTION("Vh - X -> Vh")
-        {
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, bool{true});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, uint64_t{1});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, int64_t{2});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, double{1.3});
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, -,
-                                  (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}));
-
-          REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-          REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-          REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^1");
-          REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^2");
-        }
-
-        SECTION("X - Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, -, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, -, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, -, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, -, p_R_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}),
-                                  -, p_R3x3_u);
-
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-          REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
-                              "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
-                              "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-        }
-      }
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      SECTION("product")
-      {
-        SECTION("Vh * Vh -> Vh")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v);
-
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3_v);
+          auto mesh = named_mesh.mesh();
+
+          std::shared_ptr other_mesh =
+            std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          CellValue<double> u_R_values = [=] {
+            CellValue<double> build_values{mesh->connectivity()};
+            parallel_for(
+              build_values.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+            return build_values;
+          }();
+
+          CellValue<double> v_R_values = [=] {
+            CellValue<double> build_values{mesh->connectivity()};
+            parallel_for(
+              build_values.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
+            return build_values;
+          }();
+
+          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
+          std::shared_ptr p_other_mesh_R_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, u_R_values);
+          std::shared_ptr p_R_v = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, v_R_values);
+
+          std::shared_ptr p_R1_u = [=] {
+            CellValue<TinyVector<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R1_v = [=] {
+            CellValue<TinyVector<1>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_other_mesh_R1_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
+
+          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1] + x[2]};
+            }
+          };
+
+          std::shared_ptr p_R2_u = [=] {
+            CellValue<TinyVector<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R2_v = [=] {
+            CellValue<TinyVector<2>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+                vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_other_mesh_R2_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
+
+          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0], 2 - x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1], x[0] + x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1], x[2]};
+            }
+          };
+
+          std::shared_ptr p_R3_u = [=] {
+            CellValue<TinyVector<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R3_v = [=] {
+            CellValue<TinyVector<3>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                vj[cell_id]           = {x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_other_mesh_R3_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
+
+          std::shared_ptr p_R1x1_u = [=] {
+            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_other_mesh_R1x1_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(other_mesh, p_R1x1_u->cellValues());
+
+          std::shared_ptr p_R1x1_v = [=] {
+            CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = {0.3 - xj[cell_id][0]}; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_R2x2_u = [=] {
+            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
+                               2 * x[1], -x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_other_mesh_R2x2_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(other_mesh, p_R2x2_u->cellValues());
+
+          std::shared_ptr p_R2x2_v = [=] {
+            CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+
+                vj[cell_id] = {x[0] + 0.3, 1 - x[1] - x[0],   //
+                               2 * x[1] + x[0], x[1] - x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_R3x3_u = [=] {
+            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
+                               2 * x[1],        -x[0],           x[0] - x[1],   //
+                               3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_other_mesh_R3x3_u =
+            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(other_mesh, p_R3x3_u->cellValues());
+
+          std::shared_ptr p_R3x3_v = [=] {
+            CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
+            parallel_for(
+              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+
+                vj[cell_id] = {0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
+                               2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
+                               2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, vj);
+          }();
+
+          std::shared_ptr p_Vector3_u = [=] {
+            CellArray<double> uj_vector{mesh->connectivity(), 3};
+            parallel_for(
+              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj_vector[cell_id][0] = 2 * x[0] + 1;
+                uj_vector[cell_id][1] = 1 - x[1] * x[2];
+                uj_vector[cell_id][2] = x[0] + x[2];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
+          }();
+
+          std::shared_ptr p_other_mesh_Vector3_u =
+            std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(other_mesh, p_Vector3_u->cellArrays());
+
+          std::shared_ptr p_Vector3_v = [=] {
+            CellArray<double> vj_vector{mesh->connectivity(), 3};
+            parallel_for(
+              vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                vj_vector[cell_id][0] = x[0] * x[1] + 1;
+                vj_vector[cell_id][1] = 2 * x[1];
+                vj_vector[cell_id][2] = x[2] * x[0];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
+          }();
+
+          std::shared_ptr p_Vector2_w = [=] {
+            CellArray<double> wj_vector{mesh->connectivity(), 2};
+            parallel_for(
+              wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                wj_vector[cell_id][0] = x[0] + x[1] * 2;
+                wj_vector[cell_id][1] = x[0] * x[1];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
+          }();
+
+          SECTION("sum")
+          {
+            SECTION("Vh + Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, +, p_R_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1_u, +, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2_u, +, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3_u, +, p_R3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v);
+
+              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v);
+
+              REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+              REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u,
+                                  "error: operands are defined on different meshes");
+            }
 
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1x1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2x2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3x3_v);
+            SECTION("Vh + X -> Vh")
+            {
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, bool{true});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, uint64_t{1});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, int64_t{2});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, double{1.3});
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}));
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, +,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}));
+
+              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+              REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
+              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
+            }
 
-          CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2_v);
-          CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3_v);
+            SECTION("X + Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, +, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, +, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, +, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, +, p_R_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      +, p_R3x3_u);
+
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u,
+                                  "error: incompatible operand types R^3 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+              REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
+                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
+                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+            }
+          }
 
+          SECTION("difference")
           {
-            std::shared_ptr p_fuv = p_R_u * p_Vector3_v;
+            SECTION("Vh - Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, -, p_R_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1_u, -, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2_u, -, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3_u, -, p_R3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v);
+
+              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v);
+
+              REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+              REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u,
+                                  "error: operands are defined on different meshes");
+            }
 
-            REQUIRE(p_fuv.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv));
+            SECTION("Vh - X -> Vh")
+            {
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, bool{true});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, uint64_t{1});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, int64_t{2});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, double{1.3});
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}));
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, -,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}));
+
+              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+              REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
+              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
+            }
 
-            const auto& fuv = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv);
+            SECTION("X - Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, -, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, -, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, -, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, -, p_R_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      -, p_R3x3_u);
+
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u,
+                                  "error: incompatible operand types R^3 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+              REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
+                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
+                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+            }
+          }
 
-            auto lhs_values = p_R_u->cellValues();
-            auto rhs_arrays = p_Vector3_v->cellArrays();
-            bool is_same    = true;
-            for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {
-              for (size_t i = 0; i < fuv.size(); ++i) {
-                if (fuv[cell_id][i] != (lhs_values[cell_id] * rhs_arrays[cell_id][i])) {
-                  is_same = false;
-                  break;
+          SECTION("product")
+          {
+            SECTION("Vh * Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1x1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2x2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3x3_v);
+
+              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2_v);
+              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3_v);
+
+              {
+                std::shared_ptr p_fuv = p_R_u * p_Vector3_v;
+
+                REQUIRE(p_fuv.use_count() > 0);
+                REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv));
+
+                const auto& fuv = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv);
+
+                auto lhs_values = p_R_u->cellValues();
+                auto rhs_arrays = p_Vector3_v->cellArrays();
+                bool is_same    = true;
+                for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {
+                  for (size_t i = 0; i < fuv.size(); ++i) {
+                    if (fuv[cell_id][i] != (lhs_values[cell_id] * rhs_arrays[cell_id][i])) {
+                      is_same = false;
+                      break;
+                    }
+                  }
                 }
-              }
-            }
 
-            REQUIRE(is_same);
-          }
+                REQUIRE(is_same);
+              }
 
-          REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
-
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
-
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
-
-          REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
-
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
-                              "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
-
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-        }
+              REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
+
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v,
+                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v,
+                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v,
+                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
+
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
+
+              REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
+
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
+                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
+
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
+            }
 
-        SECTION("Vh * X -> Vh")
-        {
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, bool{true});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, uint64_t{1});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, int64_t{2});
-          CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, double{1.3});
-
-          CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-          CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, *,
-                                  (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                 4.7, 2.3, 7.1,   //
-                                                 9.7, 3.2, 6.8}));
-
-          REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
-          REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R^2) and R^2");
-          REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
-                              "error: incompatible operand types Vh(P0:R^3) and R^3");
-          REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R^1) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R^2) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R^3) and R^3x3");
-          REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
-                              "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
-          REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                              "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
-          REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
-
-          REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                              "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
-          REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
-        }
+            SECTION("Vh * X -> Vh")
+            {
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, bool{true});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, uint64_t{1});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, int64_t{2});
+              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, double{1.3});
+
+              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
+              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, *,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}));
+
+              REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
+              REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}),
+                                  "error: incompatible operand types Vh(P0:R^2) and R^2");
+              REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
+                                  "error: incompatible operand types Vh(P0:R^3) and R^3");
+              REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}),
+                                  "error: incompatible operand types Vh(P0:R^1) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R^2) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R^3) and R^3x3");
+              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
+                                  "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
+              REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                  "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
+              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
+
+              REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                  "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
+              REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
+            }
 
-        SECTION("X * Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R_u);
-
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R1x1_u);
-
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R2x2_u);
-
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R3x3_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R3x3_u);
-
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+            SECTION("X * Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R_u);
+
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R1x1_u);
+
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R2x2_u);
+
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R3x3_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R3x3_u);
+
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
                                                                      4.7, 2.3, 7.1,   //
                                                                      9.7, 3.2, 6.8}),
                                                       *, p_R3_u);
 
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u);
-          CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u);
+              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
                                                                      4.7, 2.3, 7.1,   //
                                                                      9.7, 3.2, 6.8}),
                                                       *, p_R3x3_u);
 
-          CHECK_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u);
-          CHECK_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u);
-          CHECK_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u);
-          CHECK_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u);
-
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u, "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
-
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
-                              "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
-                              "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
-
-          REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
-                              "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
-          REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
-                              "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
-        }
-      }
+              CHECK_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u);
+              CHECK_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u);
+              CHECK_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u);
+              CHECK_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u);
+
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u,
+                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
+
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
+                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
+                                  "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
+
+              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
+                                  "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
+              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
+                                  "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
+            }
+          }
 
-      SECTION("ratio")
-      {
-        SECTION("Vh / Vh -> Vh")
-        {
-          CHECK_SCALAR_VH2_TO_VH(p_R_u, /, p_R_v);
+          SECTION("ratio")
+          {
+            SECTION("Vh / Vh -> Vh")
+            {
+              CHECK_SCALAR_VH2_TO_VH(p_R_u, /, p_R_v);
 
-          REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+              REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+              REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+              REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
 
-          REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
-        }
+              REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
+            }
 
-        SECTION("X / Vh -> Vh")
-        {
-          CHECK_SCALAR_XxVH_TO_VH(bool{true}, /, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, /, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, /, p_R_u);
-          CHECK_SCALAR_XxVH_TO_VH(double{1.3}, /, p_R_u);
+            SECTION("X / Vh -> Vh")
+            {
+              CHECK_SCALAR_XxVH_TO_VH(bool{true}, /, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, /, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, /, p_R_u);
+              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, /, p_R_u);
+            }
+          }
         }
       }
     }
@@ -2214,133 +2293,140 @@ TEST_CASE("EmbeddedIDiscreteFunctionOperators", "[scheme]")
 
       using Rd = TinyVector<Dimension>;
 
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
-
-      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-      CellValue<double> u_R_values = [=] {
-        CellValue<double> build_values{mesh->connectivity()};
-        parallel_for(
-          build_values.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-        return build_values;
-      }();
-
-      std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      std::shared_ptr p_R1_u = [=] {
-        CellValue<TinyVector<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-      }();
-
-      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1] + x[2]};
-        }
-      };
-
-      std::shared_ptr p_R2_u = [=] {
-        CellValue<TinyVector<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-      }();
-
-      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0], 2 - x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1], x[0] + x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1], x[2]};
-        }
-      };
-
-      std::shared_ptr p_R3_u = [=] {
-        CellValue<TinyVector<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R1x1_u = [=] {
-        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R2x2_u = [=] {
-        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
-                           2 * x[1], -x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R3x3_u = [=] {
-        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
-                           2 * x[1],        -x[0],           x[0] - x[1],   //
-                           3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_Vector3_u = [=] {
-        CellArray<double> uj_vector{mesh->connectivity(), 3};
-        parallel_for(
-          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj_vector[cell_id][0] = 2 * x[0] + 1;
-            uj_vector[cell_id][1] = 1 - x[1] * x[2];
-            uj_vector[cell_id][2] = x[0] + x[2];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-      }();
-
-      SECTION("unary minus")
-      {
-        SECTION("- Vh -> Vh")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          CHECK_SCALAR_VH_TO_VH(-, p_R_u);
+          auto mesh = named_mesh.mesh();
+
+          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          CellValue<double> u_R_values = [=] {
+            CellValue<double> build_values{mesh->connectivity()};
+            parallel_for(
+              build_values.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+            return build_values;
+          }();
+
+          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
+
+          std::shared_ptr p_R1_u = [=] {
+            CellValue<TinyVector<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
+          }();
+
+          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1] + x[2]};
+            }
+          };
+
+          std::shared_ptr p_R2_u = [=] {
+            CellValue<TinyVector<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
+          }();
+
+          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0], 2 - x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1], x[0] + x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1], x[2]};
+            }
+          };
+
+          std::shared_ptr p_R3_u = [=] {
+            CellValue<TinyVector<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R1x1_u = [=] {
+            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R2x2_u = [=] {
+            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
+                               2 * x[1], -x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R3x3_u = [=] {
+            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
+                               2 * x[1],        -x[0],           x[0] - x[1],   //
+                               3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_Vector3_u = [=] {
+            CellArray<double> uj_vector{mesh->connectivity(), 3};
+            parallel_for(
+              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj_vector[cell_id][0] = 2 * x[0] + 1;
+                uj_vector[cell_id][1] = 1 - x[1] * x[2];
+                uj_vector[cell_id][2] = x[0] + x[2];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
+          }();
+
+          SECTION("unary minus")
+          {
+            SECTION("- Vh -> Vh")
+            {
+              CHECK_SCALAR_VH_TO_VH(-, p_R_u);
 
-          CHECK_SCALAR_VH_TO_VH(-, p_R1_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R2_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R3_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R1_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R2_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R3_u);
 
-          CHECK_SCALAR_VH_TO_VH(-, p_R1x1_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R2x2_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R3x3_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R1x1_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R2x2_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R3x3_u);
 
-          CHECK_VECTOR_VH_TO_VH(-, p_Vector3_u);
+              CHECK_VECTOR_VH_TO_VH(-, p_Vector3_u);
+            }
+          }
         }
       }
     }
@@ -2351,133 +2437,140 @@ TEST_CASE("EmbeddedIDiscreteFunctionOperators", "[scheme]")
 
       using Rd = TinyVector<Dimension>;
 
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-      CellValue<double> u_R_values = [=] {
-        CellValue<double> build_values{mesh->connectivity()};
-        parallel_for(
-          build_values.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-        return build_values;
-      }();
-
-      std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-
-      std::shared_ptr p_R1_u = [=] {
-        CellValue<TinyVector<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-      }();
-
-      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1] + x[2]};
-        }
-      };
-
-      std::shared_ptr p_R2_u = [=] {
-        CellValue<TinyVector<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-      }();
-
-      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0], 2 - x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1], x[0] + x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1], x[2]};
-        }
-      };
-
-      std::shared_ptr p_R3_u = [=] {
-        CellValue<TinyVector<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R1x1_u = [=] {
-        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R2x2_u = [=] {
-        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
-                           2 * x[1], -x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R3x3_u = [=] {
-        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
-                           2 * x[1],        -x[0],           x[0] - x[1],   //
-                           3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_Vector3_u = [=] {
-        CellArray<double> uj_vector{mesh->connectivity(), 3};
-        parallel_for(
-          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj_vector[cell_id][0] = 2 * x[0] + 1;
-            uj_vector[cell_id][1] = 1 - x[1] * x[2];
-            uj_vector[cell_id][2] = x[0] + x[2];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-      }();
-
-      SECTION("unary minus")
-      {
-        SECTION("- Vh -> Vh")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          CHECK_SCALAR_VH_TO_VH(-, p_R_u);
+          auto mesh = named_mesh.mesh();
+
+          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          CellValue<double> u_R_values = [=] {
+            CellValue<double> build_values{mesh->connectivity()};
+            parallel_for(
+              build_values.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+            return build_values;
+          }();
+
+          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
+
+          std::shared_ptr p_R1_u = [=] {
+            CellValue<TinyVector<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
+          }();
+
+          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1] + x[2]};
+            }
+          };
+
+          std::shared_ptr p_R2_u = [=] {
+            CellValue<TinyVector<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
+          }();
+
+          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0], 2 - x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1], x[0] + x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1], x[2]};
+            }
+          };
+
+          std::shared_ptr p_R3_u = [=] {
+            CellValue<TinyVector<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R1x1_u = [=] {
+            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R2x2_u = [=] {
+            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
+                               2 * x[1], -x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R3x3_u = [=] {
+            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
+                               2 * x[1],        -x[0],           x[0] - x[1],   //
+                               3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_Vector3_u = [=] {
+            CellArray<double> uj_vector{mesh->connectivity(), 3};
+            parallel_for(
+              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj_vector[cell_id][0] = 2 * x[0] + 1;
+                uj_vector[cell_id][1] = 1 - x[1] * x[2];
+                uj_vector[cell_id][2] = x[0] + x[2];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
+          }();
+
+          SECTION("unary minus")
+          {
+            SECTION("- Vh -> Vh")
+            {
+              CHECK_SCALAR_VH_TO_VH(-, p_R_u);
 
-          CHECK_SCALAR_VH_TO_VH(-, p_R1_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R2_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R3_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R1_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R2_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R3_u);
 
-          CHECK_SCALAR_VH_TO_VH(-, p_R1x1_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R2x2_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R3x3_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R1x1_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R2x2_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R3x3_u);
 
-          CHECK_VECTOR_VH_TO_VH(-, p_Vector3_u);
+              CHECK_VECTOR_VH_TO_VH(-, p_Vector3_u);
+            }
+          }
         }
       }
     }
@@ -2488,135 +2581,146 @@ TEST_CASE("EmbeddedIDiscreteFunctionOperators", "[scheme]")
 
       using Rd = TinyVector<Dimension>;
 
-      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
-
-      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      CellValue<double> u_R_values = [=] {
-        CellValue<double> build_values{mesh->connectivity()};
-        parallel_for(
-          build_values.numberOfItems(),
-          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-        return build_values;
-      }();
-
-      std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-
-      std::shared_ptr p_R1_u = [=] {
-        CellValue<TinyVector<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-      }();
-
-      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1] + x[2]};
-        }
-      };
-
-      std::shared_ptr p_R2_u = [=] {
-        CellValue<TinyVector<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-      }();
-
-      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-        if constexpr (Dimension == 1) {
-          return {x[0], 1 + x[0] * x[0], 2 - x[0]};
-        } else if constexpr (Dimension == 2) {
-          return {x[0], x[1], x[0] + x[1]};
-        } else if constexpr (Dimension == 3) {
-          return {x[0], x[1], x[2]};
-        }
-      };
-
-      std::shared_ptr p_R3_u = [=] {
-        CellValue<TinyVector<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R1x1_u = [=] {
-        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R2x2_u = [=] {
-        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<2> x = to_2d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
-                           2 * x[1], -x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_R3x3_u = [=] {
-        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-        parallel_for(
-          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-
-            uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
-                           2 * x[1],        -x[0],           x[0] - x[1],   //
-                           3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-          });
-
-        return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-      }();
-
-      std::shared_ptr p_Vector3_u = [=] {
-        CellArray<double> uj_vector{mesh->connectivity(), 3};
-        parallel_for(
-          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<3> x = to_3d(xj[cell_id]);
-            uj_vector[cell_id][0] = 2 * x[0] + 1;
-            uj_vector[cell_id][1] = 1 - x[1] * x[2];
-            uj_vector[cell_id][2] = x[0] + x[2];
-          });
-
-        return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-      }();
-
-      SECTION("unary minus")
-      {
-        SECTION("- Vh -> Vh")
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          CHECK_SCALAR_VH_TO_VH(-, p_R_u);
+          auto mesh = named_mesh.mesh();
+
+          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          CellValue<double> u_R_values = [=] {
+            CellValue<double> build_values{mesh->connectivity()};
+            parallel_for(
+              build_values.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+            return build_values;
+          }();
+
+          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
+
+          std::shared_ptr p_R1_u = [=] {
+            CellValue<TinyVector<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
+          }();
+
+          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1] + x[2]};
+            }
+          };
+
+          std::shared_ptr p_R2_u = [=] {
+            CellValue<TinyVector<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
+          }();
+
+          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+            if constexpr (Dimension == 1) {
+              return {x[0], 1 + x[0] * x[0], 2 - x[0]};
+            } else if constexpr (Dimension == 2) {
+              return {x[0], x[1], x[0] + x[1]};
+            } else if constexpr (Dimension == 3) {
+              return {x[0], x[1], x[2]};
+            }
+          };
+
+          std::shared_ptr p_R3_u = [=] {
+            CellValue<TinyVector<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj[cell_id]           = {2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R1x1_u = [=] {
+            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = {2 * xj[cell_id][0] + 1}; });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R2x2_u = [=] {
+            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<2> x = to_2d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1, 1 - x[1],   //
+                               2 * x[1], -x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_R3x3_u = [=] {
+            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+            parallel_for(
+              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+
+                uj[cell_id] = {2 * x[0] + 1,    1 - x[1],        3,             //
+                               2 * x[1],        -x[0],           x[0] - x[1],   //
+                               3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+              });
+
+            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
+          }();
+
+          std::shared_ptr p_Vector3_u = [=] {
+            CellArray<double> uj_vector{mesh->connectivity(), 3};
+            parallel_for(
+              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<3> x = to_3d(xj[cell_id]);
+                uj_vector[cell_id][0] = 2 * x[0] + 1;
+                uj_vector[cell_id][1] = 1 - x[1] * x[2];
+                uj_vector[cell_id][2] = x[0] + x[2];
+              });
+
+            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
+          }();
+
+          SECTION("unary minus")
+          {
+            SECTION("- Vh -> Vh")
+            {
+              CHECK_SCALAR_VH_TO_VH(-, p_R_u);
 
-          CHECK_SCALAR_VH_TO_VH(-, p_R1_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R2_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R3_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R1_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R2_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R3_u);
 
-          CHECK_SCALAR_VH_TO_VH(-, p_R1x1_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R2x2_u);
-          CHECK_SCALAR_VH_TO_VH(-, p_R3x3_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R1x1_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R2x2_u);
+              CHECK_SCALAR_VH_TO_VH(-, p_R3x3_u);
 
-          CHECK_VECTOR_VH_TO_VH(-, p_Vector3_u);
+              CHECK_VECTOR_VH_TO_VH(-, p_Vector3_u);
+            }
+          }
         }
       }
     }
   }
 }
+
+#ifdef __clang__
+#pragma clang optimize on
+#endif   // __clang__
diff --git a/tests/test_EmbeddedIDiscreteFunctionUtils.cpp b/tests/test_EmbeddedIDiscreteFunctionUtils.cpp
index 0ded74fd6b5764bfb67917a48a1a24ab8b1db873..e7d0e8eea200cb115be60a2ba789999155cf5fab 100644
--- a/tests/test_EmbeddedIDiscreteFunctionUtils.cpp
+++ b/tests/test_EmbeddedIDiscreteFunctionUtils.cpp
@@ -29,28 +29,46 @@ TEST_CASE("EmbeddedIDiscreteFunctionUtils", "[language]")
 
     SECTION("discrete P0 function")
     {
-      std::shared_ptr mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
-
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, double>{mesh_1d}) == "Vh(P0:R)");
-
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R1>{mesh_1d}) == "Vh(P0:R^1)");
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R2>{mesh_1d}) == "Vh(P0:R^2)");
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R3>{mesh_1d}) == "Vh(P0:R^3)");
-
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R1x1>{mesh_1d}) ==
-              "Vh(P0:R^1x1)");
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R2x2>{mesh_1d}) ==
-              "Vh(P0:R^2x2)");
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R3x3>{mesh_1d}) ==
-              "Vh(P0:R^3x3)");
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, double>{mesh_1d}) ==
+                  "Vh(P0:R)");
+
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R1>{mesh_1d}) ==
+                  "Vh(P0:R^1)");
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R2>{mesh_1d}) ==
+                  "Vh(P0:R^2)");
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R3>{mesh_1d}) ==
+                  "Vh(P0:R^3)");
+
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R1x1>{mesh_1d}) ==
+                  "Vh(P0:R^1x1)");
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R2x2>{mesh_1d}) ==
+                  "Vh(P0:R^2x2)");
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R3x3>{mesh_1d}) ==
+                  "Vh(P0:R^3x3)");
+        }
+      }
     }
 
     SECTION("discrete P0Vector function")
     {
-      std::shared_ptr mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
 
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0Vector<1, double>{mesh_1d, 2}) ==
-              "Vh(P0Vector:R)");
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0Vector<1, double>{mesh_1d, 2}) ==
+                  "Vh(P0Vector:R)");
+        }
+      }
     }
   }
 
@@ -58,59 +76,83 @@ TEST_CASE("EmbeddedIDiscreteFunctionUtils", "[language]")
   {
     SECTION("from shared_ptr")
     {
-      std::shared_ptr mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
-
-      REQUIRE(
-        EmbeddedIDiscreteFunctionUtils::isSameDiscretization(std::make_shared<DiscreteFunctionP0<1, double>>(mesh_1d),
-                                                             std::make_shared<DiscreteFunctionP0<1, double>>(mesh_1d)));
-
-      REQUIRE(not EmbeddedIDiscreteFunctionUtils::
-                isSameDiscretization(std::make_shared<DiscreteFunctionP0<1, double>>(mesh_1d),
-                                     std::make_shared<DiscreteFunctionP0Vector<1, double>>(mesh_1d, 1)));
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(std::make_shared<DiscreteFunctionP0<1, double>>(
+                                                                         mesh_1d),
+                                                                       std::make_shared<DiscreteFunctionP0<1, double>>(
+                                                                         mesh_1d)));
+
+          REQUIRE(not EmbeddedIDiscreteFunctionUtils::
+                    isSameDiscretization(std::make_shared<DiscreteFunctionP0<1, double>>(mesh_1d),
+                                         std::make_shared<DiscreteFunctionP0Vector<1, double>>(mesh_1d, 1)));
+        }
+      }
     }
 
     SECTION("from value")
     {
-      std::shared_ptr mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, double>{mesh_1d},
-                                                                   DiscreteFunctionP0<1, double>{mesh_1d}));
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
 
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R1>{mesh_1d},
-                                                                   DiscreteFunctionP0<1, R1>{mesh_1d}));
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, double>{mesh_1d},
+                                                                       DiscreteFunctionP0<1, double>{mesh_1d}));
 
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R2>{mesh_1d},
-                                                                   DiscreteFunctionP0<1, R2>{mesh_1d}));
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R1>{mesh_1d},
+                                                                       DiscreteFunctionP0<1, R1>{mesh_1d}));
 
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R3>{mesh_1d},
-                                                                   DiscreteFunctionP0<1, R3>{mesh_1d}));
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R2>{mesh_1d},
+                                                                       DiscreteFunctionP0<1, R2>{mesh_1d}));
 
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R1x1>{mesh_1d},
-                                                                   DiscreteFunctionP0<1, R1x1>{mesh_1d}));
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R3>{mesh_1d},
+                                                                       DiscreteFunctionP0<1, R3>{mesh_1d}));
 
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R2x2>{mesh_1d},
-                                                                   DiscreteFunctionP0<1, R2x2>{mesh_1d}));
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R1x1>{mesh_1d},
+                                                                       DiscreteFunctionP0<1, R1x1>{mesh_1d}));
 
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R3x3>{mesh_1d},
-                                                                   DiscreteFunctionP0<1, R3x3>{mesh_1d}));
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R2x2>{mesh_1d},
+                                                                       DiscreteFunctionP0<1, R2x2>{mesh_1d}));
 
-      REQUIRE(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, double>{mesh_1d},
-                                                                       DiscreteFunctionP0<1, R1>{mesh_1d}));
+          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R3x3>{mesh_1d},
+                                                                       DiscreteFunctionP0<1, R3x3>{mesh_1d}));
 
-      REQUIRE(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R2>{mesh_1d},
-                                                                       DiscreteFunctionP0<1, R2x2>{mesh_1d}));
+          REQUIRE(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, double>{mesh_1d},
+                                                                           DiscreteFunctionP0<1, R1>{mesh_1d}));
 
-      REQUIRE(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R3x3>{mesh_1d},
-                                                                       DiscreteFunctionP0<1, R2x2>{mesh_1d}));
+          REQUIRE(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R2>{mesh_1d},
+                                                                           DiscreteFunctionP0<1, R2x2>{mesh_1d}));
+
+          REQUIRE(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R3x3>{mesh_1d},
+                                                                           DiscreteFunctionP0<1, R2x2>{mesh_1d}));
+        }
+      }
     }
 
     SECTION("invalid data type")
     {
-      std::shared_ptr mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
-
-      REQUIRE_THROWS_WITH(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, int64_t>{mesh_1d},
-                                                                               DiscreteFunctionP0<1, int64_t>{mesh_1d}),
-                          "unexpected error: invalid data type Vh(P0:Z)");
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          REQUIRE_THROWS_WITH(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1,
+                                                                                                      int64_t>{mesh_1d},
+                                                                                   DiscreteFunctionP0<1, int64_t>{
+                                                                                     mesh_1d}),
+                              "unexpected error: invalid data type Vh(P0:Z)");
+        }
+      }
     }
   }
 
diff --git a/tests/test_FaceIntegrator.cpp b/tests/test_FaceIntegrator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9dee9c032b48db4ad51c362b5f47681613039fb5
--- /dev/null
+++ b/tests/test_FaceIntegrator.cpp
@@ -0,0 +1,506 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussLobattoQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <mesh/DiamondDualMeshManager.hpp>
+#include <mesh/ItemValue.hpp>
+#include <mesh/Mesh.hpp>
+#include <scheme/FaceIntegrator.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("FaceIntegrator", "[scheme]")
+{
+  SECTION("2D")
+  {
+    using R2 = TinyVector<2>;
+
+    const auto mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+
+    auto f = [](const R2& X) -> double {
+      const double x = X[0];
+      const double y = X[1];
+      return x * x + 2 * x * y + 3 * y * y + 2;
+    };
+
+    Array<const double> int_f_per_face = [=] {
+      Array<double> int_f(mesh->numberOfFaces());
+      auto face_to_node_matrix = mesh->connectivity().faceToNodeMatrix();
+
+      parallel_for(
+        mesh->numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+          auto face_node_list = face_to_node_matrix[face_id];
+          auto xr             = mesh->xr();
+          double integral     = 0;
+          LineTransformation<2> T(xr[face_node_list[0]], xr[face_node_list[1]]);
+          auto qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor(2));
+
+          for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+            const auto& xi = qf.point(i);
+            integral += qf.weight(i) * T.velocityNorm() * f(T(xi));
+          }
+
+          int_f[face_id] = integral;
+        });
+
+      return int_f;
+    }();
+
+    SECTION("direct formula")
+    {
+      SECTION("all faces")
+      {
+        SECTION("FaceValue")
+        {
+          FaceValue<double> values(mesh->connectivity());
+          FaceIntegrator::integrateTo([=](const R2 x) { return f(x); }, GaussQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+            error += std::abs(int_f_per_face[face_id] - values[face_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("Array")
+        {
+          Array<double> values(mesh->numberOfFaces());
+
+          FaceIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+            error += std::abs(int_f_per_face[face_id] - values[face_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<double> values(mesh->numberOfFaces());
+          FaceIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+            error += std::abs(int_f_per_face[face_id] - values[face_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+
+      SECTION("face list")
+      {
+        SECTION("Array")
+        {
+          Array<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+
+          {
+            size_t k = 0;
+            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
+              face_list[k] = face_id;
+            }
+
+            REQUIRE(k == face_list.size());
+          }
+
+          Array<double> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, face_list);
+
+          double error = 0;
+          for (size_t i = 0; i < face_list.size(); ++i) {
+            error += std::abs(int_f_per_face[face_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+
+          {
+            size_t k = 0;
+            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
+              face_list[k] = face_id;
+            }
+
+            REQUIRE(k == face_list.size());
+          }
+
+          SmallArray<double> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, face_list);
+
+          double error = 0;
+          for (size_t i = 0; i < face_list.size(); ++i) {
+            error += std::abs(int_f_per_face[face_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+    }
+
+    SECTION("tensorial formula")
+    {
+      SECTION("all faces")
+      {
+        SECTION("FaceValue")
+        {
+          FaceValue<double> values(mesh->connectivity());
+          FaceIntegrator::integrateTo([=](const R2 x) { return f(x); }, GaussLobattoQuadratureDescriptor(2), *mesh,
+                                      values);
+
+          double error = 0;
+          for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+            error += std::abs(int_f_per_face[face_id] - values[face_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("Array")
+        {
+          Array<double> values(mesh->numberOfFaces());
+
+          FaceIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+            error += std::abs(int_f_per_face[face_id] - values[face_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<double> values(mesh->numberOfFaces());
+          FaceIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values);
+
+          double error = 0;
+          for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+            error += std::abs(int_f_per_face[face_id] - values[face_id]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+
+      SECTION("face list")
+      {
+        SECTION("Array")
+        {
+          Array<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+
+          {
+            size_t k = 0;
+            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
+              face_list[k] = face_id;
+            }
+
+            REQUIRE(k == face_list.size());
+          }
+
+          Array<double> values = FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, face_list);
+
+          double error = 0;
+          for (size_t i = 0; i < face_list.size(); ++i) {
+            error += std::abs(int_f_per_face[face_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+
+        SECTION("SmallArray")
+        {
+          SmallArray<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+
+          {
+            size_t k = 0;
+            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
+              face_list[k] = face_id;
+            }
+
+            REQUIRE(k == face_list.size());
+          }
+
+          SmallArray<double> values =
+            FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, face_list);
+
+          double error = 0;
+          for (size_t i = 0; i < face_list.size(); ++i) {
+            error += std::abs(int_f_per_face[face_list[i]] - values[i]);
+          }
+
+          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+        }
+      }
+    }
+  }
+
+  SECTION("3D")
+  {
+    using R3 = TinyVector<3>;
+
+    auto hybrid_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+
+    auto f = [](const R3& X) -> double {
+      const double x = X[0];
+      const double y = X[1];
+      const double z = X[2];
+      return x * x + 2 * x * y + 3 * y * y + 2 * z * z - z + 1;
+    };
+
+    std::vector<std::pair<std::string, decltype(hybrid_mesh)>> mesh_list;
+    mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
+    mesh_list.push_back(
+      std::make_pair("diamond mesh", DiamondDualMeshManager::instance().getDiamondDualMesh(hybrid_mesh)));
+
+    for (auto mesh_info : mesh_list) {
+      auto mesh_name = mesh_info.first;
+      auto mesh      = mesh_info.second;
+
+      Array<const double> int_f_per_face = [=] {
+        Array<double> int_f(mesh->numberOfFaces());
+        auto face_to_node_matrix = mesh->connectivity().faceToNodeMatrix();
+
+        parallel_for(
+          mesh->numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            auto face_node_list = face_to_node_matrix[face_id];
+            auto xr             = mesh->xr();
+            double integral     = 0;
+
+            switch (face_node_list.size()) {
+            case 3: {
+              TriangleTransformation<3> T(xr[face_node_list[0]], xr[face_node_list[1]], xr[face_node_list[2]]);
+              auto qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(2));
+              for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                const auto& xi = qf.point(i);
+                integral += qf.weight(i) * T.areaVariationNorm() * f(T(xi));
+              }
+              break;
+            }
+            case 4: {
+              SquareTransformation<3> T(xr[face_node_list[0]], xr[face_node_list[1]], xr[face_node_list[2]],
+                                        xr[face_node_list[3]]);
+              auto qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(2));
+              for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+                const auto& xi = qf.point(i);
+                integral += qf.weight(i) * T.areaVariationNorm(xi) * f(T(xi));
+              }
+              break;
+            }
+            default: {
+              throw UnexpectedError("invalid face (node number must be 3 or 4)");
+            }
+            }
+            int_f[face_id] = integral;
+          });
+
+        return int_f;
+      }();
+
+      SECTION(mesh_name)
+      {
+        SECTION("direct formula")
+        {
+          SECTION("all faces")
+          {
+            SECTION("FaceValue")
+            {
+              FaceValue<double> values(mesh->connectivity());
+              FaceIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussQuadratureDescriptor(4), *mesh,
+                                          values);
+
+              double error = 0;
+              for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+                error += std::abs(int_f_per_face[face_id] - values[face_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("Array")
+            {
+              Array<double> values(mesh->numberOfFaces());
+
+              FaceIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+                error += std::abs(int_f_per_face[face_id] - values[face_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<double> values(mesh->numberOfFaces());
+              FaceIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+                error += std::abs(int_f_per_face[face_id] - values[face_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+          }
+
+          SECTION("face list")
+          {
+            SECTION("Array")
+            {
+              Array<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+
+              {
+                size_t k = 0;
+                for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
+                  face_list[k] = face_id;
+                }
+
+                REQUIRE(k == face_list.size());
+              }
+
+              Array<double> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, face_list);
+
+              double error = 0;
+              for (size_t i = 0; i < face_list.size(); ++i) {
+                error += std::abs(int_f_per_face[face_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+
+              {
+                size_t k = 0;
+                for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
+                  face_list[k] = face_id;
+                }
+
+                REQUIRE(k == face_list.size());
+              }
+
+              SmallArray<double> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, face_list);
+
+              double error = 0;
+              for (size_t i = 0; i < face_list.size(); ++i) {
+                error += std::abs(int_f_per_face[face_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+          }
+        }
+
+        SECTION("tensorial formula")
+        {
+          SECTION("all faces")
+          {
+            SECTION("FaceValue")
+            {
+              FaceValue<double> values(mesh->connectivity());
+              FaceIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussLegendreQuadratureDescriptor(10),
+                                          *mesh, values);
+
+              double error = 0;
+              for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+                error += std::abs(int_f_per_face[face_id] - values[face_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("Array")
+            {
+              Array<double> values(mesh->numberOfFaces());
+
+              FaceIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+                error += std::abs(int_f_per_face[face_id] - values[face_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<double> values(mesh->numberOfFaces());
+              FaceIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values);
+
+              double error = 0;
+              for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+                error += std::abs(int_f_per_face[face_id] - values[face_id]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+          }
+
+          SECTION("face list")
+          {
+            SECTION("Array")
+            {
+              Array<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+
+              {
+                size_t k = 0;
+                for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
+                  face_list[k] = face_id;
+                }
+
+                REQUIRE(k == face_list.size());
+              }
+
+              Array<double> values =
+                FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, face_list);
+
+              double error = 0;
+              for (size_t i = 0; i < face_list.size(); ++i) {
+                error += std::abs(int_f_per_face[face_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+
+            SECTION("SmallArray")
+            {
+              SmallArray<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+
+              {
+                size_t k = 0;
+                for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
+                  face_list[k] = face_id;
+                }
+
+                REQUIRE(k == face_list.size());
+              }
+
+              SmallArray<double> values =
+                FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, face_list);
+
+              double error = 0;
+              for (size_t i = 0; i < face_list.size(); ++i) {
+                error += std::abs(int_f_per_face[face_list[i]] - values[i]);
+              }
+
+              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_InterpolateItemArray.cpp b/tests/test_InterpolateItemArray.cpp
index 5e0bbd962bd9c24165956b9e17a223f80a187055..2974e01d8732d35702b32493f0cb810932de2df2 100644
--- a/tests/test_InterpolateItemArray.cpp
+++ b/tests/test_InterpolateItemArray.cpp
@@ -46,195 +46,219 @@ TEST_CASE("InterpolateItemArray", "[language]")
     {
       constexpr size_t Dimension = 1;
 
-      const auto& mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      std::string_view data = R"(
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+
+          std::string_view data = R"(
 import math;
 let scalar_affine_1d: R^1 -> R, x -> 2*x[0] + 2;
 let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
 
-      auto ast = ASTBuilder::build(input);
+          auto ast = ASTBuilder::build(input);
 
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
 
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
 
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
 
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
 
-      std::vector<FunctionSymbolId> function_symbol_id_list;
+          std::vector<FunctionSymbolId> function_symbol_id_list;
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      CellArray<double> cell_array{mesh_1d->connectivity(), 2};
-      parallel_for(
-        cell_array.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_array[cell_id][0]         = 2 * x[0] + 2;
-          cell_array[cell_id][1]         = 2 * exp(x[0]) + 3;
-        });
+          CellArray<double> cell_array{mesh_1d->connectivity(), 2};
+          parallel_for(
+            cell_array.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_array[cell_id][0]         = 2 * x[0] + 2;
+              cell_array[cell_id][1]         = 2 * exp(x[0]) + 3;
+            });
 
-      CellArray<const double> interpolate_array =
-        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
+          CellArray<const double> interpolate_array =
+            InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
 
-      REQUIRE(same_cell_array(cell_array, interpolate_array));
+          REQUIRE(same_cell_array(cell_array, interpolate_array));
+        }
+      }
     }
 
     SECTION("2D")
     {
       constexpr size_t Dimension = 2;
 
-      const auto& mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
 
-      std::string_view data = R"(
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+
+          std::string_view data = R"(
 import math;
 let scalar_affine_2d: R^2 -> R, x -> 2*x[0] + 3*x[1] + 2;
 let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
 
-      auto ast = ASTBuilder::build(input);
+          auto ast = ASTBuilder::build(input);
 
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
 
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
 
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
 
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
 
-      std::vector<FunctionSymbolId> function_symbol_id_list;
+          std::vector<FunctionSymbolId> function_symbol_id_list;
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      CellArray<double> cell_array{mesh_2d->connectivity(), 2};
-      parallel_for(
-        cell_array.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_array[cell_id][0]         = 2 * x[0] + 3 * x[1] + 2;
-          cell_array[cell_id][1]         = 2 * exp(x[0]) * sin(x[1]) + 3;
-        });
+          CellArray<double> cell_array{mesh_2d->connectivity(), 2};
+          parallel_for(
+            cell_array.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_array[cell_id][0]         = 2 * x[0] + 3 * x[1] + 2;
+              cell_array[cell_id][1]         = 2 * exp(x[0]) * sin(x[1]) + 3;
+            });
 
-      CellArray<const double> interpolate_array =
-        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
+          CellArray<const double> interpolate_array =
+            InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
 
-      REQUIRE(same_cell_array(cell_array, interpolate_array));
+          REQUIRE(same_cell_array(cell_array, interpolate_array));
+        }
+      }
     }
 
     SECTION("3D")
     {
       constexpr size_t Dimension = 3;
 
-      const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
 
-      std::string_view data = R"(
+          std::string_view data = R"(
 import math;
 let scalar_affine_3d: R^3 -> R, x -> 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
 let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
 
-      auto ast = ASTBuilder::build(input);
+          auto ast = ASTBuilder::build(input);
 
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
 
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
 
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
 
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
 
-      std::vector<FunctionSymbolId> function_symbol_id_list;
+          std::vector<FunctionSymbolId> function_symbol_id_list;
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      CellArray<double> cell_array{mesh_3d->connectivity(), 2};
-      parallel_for(
-        cell_array.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-          const TinyVector<Dimension>& x = xj[cell_id];
-          cell_array[cell_id][0]         = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
-          cell_array[cell_id][1]         = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
-        });
+          CellArray<double> cell_array{mesh_3d->connectivity(), 2};
+          parallel_for(
+            cell_array.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_array[cell_id][0]         = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+              cell_array[cell_id][1]         = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+            });
 
-      CellArray<const double> interpolate_array =
-        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
+          CellArray<const double> interpolate_array =
+            InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
 
-      REQUIRE(same_cell_array(cell_array, interpolate_array));
+          REQUIRE(same_cell_array(cell_array, interpolate_array));
+        }
+      }
     }
   }
 
@@ -255,213 +279,237 @@ let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
     {
       constexpr size_t Dimension = 1;
 
-      const auto& mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      Array<const CellId> cell_id_list = [&] {
-        Array<CellId> cell_ids{mesh_1d->numberOfCells() / 2};
-        for (size_t i_cell = 0; i_cell < cell_ids.size(); ++i_cell) {
-          cell_ids[i_cell] = static_cast<CellId>(2 * i_cell);
-        }
-        return cell_ids;
-      }();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+
+          Array<const CellId> cell_id_list = [&] {
+            Array<CellId> cell_ids{mesh_1d->numberOfCells() / 2};
+            for (size_t i_cell = 0; i_cell < cell_ids.size(); ++i_cell) {
+              cell_ids[i_cell] = static_cast<CellId>(2 * i_cell);
+            }
+            return cell_ids;
+          }();
 
-      std::string_view data = R"(
+          std::string_view data = R"(
   import math;
   let scalar_affine_1d: R^1 -> R, x -> 2*x[0] + 2;
   let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
   )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
 
-      auto ast = ASTBuilder::build(input);
+          auto ast = ASTBuilder::build(input);
 
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
 
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
 
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
 
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
 
-      std::vector<FunctionSymbolId> function_symbol_id_list;
+          std::vector<FunctionSymbolId> function_symbol_id_list;
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      Table<double> cell_array{cell_id_list.size(), 2};
-      parallel_for(
-        cell_id_list.size(), PUGS_LAMBDA(const size_t i) {
-          const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-          cell_array[i][0]               = 2 * x[0] + 2;
-          cell_array[i][1]               = 2 * exp(x[0]) + 3;
-        });
+          Table<double> cell_array{cell_id_list.size(), 2};
+          parallel_for(
+            cell_id_list.size(), PUGS_LAMBDA(const size_t i) {
+              const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+              cell_array[i][0]               = 2 * x[0] + 2;
+              cell_array[i][1]               = 2 * exp(x[0]) + 3;
+            });
 
-      Table<const double> interpolate_array =
-        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
+          Table<const double> interpolate_array =
+            InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
 
-      REQUIRE(same_cell_value(cell_array, interpolate_array));
+          REQUIRE(same_cell_value(cell_array, interpolate_array));
+        }
+      }
     }
 
     SECTION("2D")
     {
       constexpr size_t Dimension = 2;
 
-      const auto& mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      Array<CellId> cell_id_list{mesh_2d->numberOfCells() / 2};
-      for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
-        cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
-      }
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
 
-      std::string_view data = R"(
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+
+          Array<CellId> cell_id_list{mesh_2d->numberOfCells() / 2};
+          for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
+            cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
+          }
+
+          std::string_view data = R"(
 import math;
 let scalar_affine_2d: R^2 -> R, x -> 2*x[0] + 3*x[1] + 2;
 let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
 
-      auto ast = ASTBuilder::build(input);
+          auto ast = ASTBuilder::build(input);
 
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
 
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
 
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
 
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
 
-      std::vector<FunctionSymbolId> function_symbol_id_list;
+          std::vector<FunctionSymbolId> function_symbol_id_list;
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      Table<double> cell_array{cell_id_list.size(), 2};
-      parallel_for(
-        cell_id_list.size(), PUGS_LAMBDA(const size_t i) {
-          const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-          cell_array[i][0]               = 2 * x[0] + 3 * x[1] + 2;
-          cell_array[i][1]               = 2 * exp(x[0]) * sin(x[1]) + 3;
-        });
+          Table<double> cell_array{cell_id_list.size(), 2};
+          parallel_for(
+            cell_id_list.size(), PUGS_LAMBDA(const size_t i) {
+              const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+              cell_array[i][0]               = 2 * x[0] + 3 * x[1] + 2;
+              cell_array[i][1]               = 2 * exp(x[0]) * sin(x[1]) + 3;
+            });
 
-      Table<const double> interpolate_array =
-        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
+          Table<const double> interpolate_array =
+            InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
 
-      REQUIRE(same_cell_value(cell_array, interpolate_array));
+          REQUIRE(same_cell_value(cell_array, interpolate_array));
+        }
+      }
     }
 
     SECTION("3D")
     {
       constexpr size_t Dimension = 3;
 
-      const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      Array<CellId> cell_id_list{mesh_3d->numberOfCells() / 2};
-      for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
-        cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
-      }
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+
+          Array<CellId> cell_id_list{mesh_3d->numberOfCells() / 2};
+          for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
+            cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
+          }
 
-      std::string_view data = R"(
+          std::string_view data = R"(
 import math;
 let scalar_affine_3d: R^3 -> R, x -> 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
 let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
 
-      auto ast = ASTBuilder::build(input);
+          auto ast = ASTBuilder::build(input);
 
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
 
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
 
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
 
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
 
-      std::vector<FunctionSymbolId> function_symbol_id_list;
+          std::vector<FunctionSymbolId> function_symbol_id_list;
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        function_symbol_id_list.push_back(
-          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
-      }
+            function_symbol_id_list.push_back(
+              FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+          }
 
-      Table<double> cell_array{cell_id_list.size(), 2};
-      parallel_for(
-        cell_id_list.size(), PUGS_LAMBDA(const size_t i) {
-          const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-          cell_array[i][0]               = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
-          cell_array[i][1]               = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
-        });
+          Table<double> cell_array{cell_id_list.size(), 2};
+          parallel_for(
+            cell_id_list.size(), PUGS_LAMBDA(const size_t i) {
+              const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+              cell_array[i][0]               = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+              cell_array[i][1]               = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+            });
 
-      Table<const double> interpolate_array =
-        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
+          Table<const double> interpolate_array =
+            InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
 
-      REQUIRE(same_cell_value(cell_array, interpolate_array));
+          REQUIRE(same_cell_value(cell_array, interpolate_array));
+        }
+      }
     }
   }
 }
diff --git a/tests/test_InterpolateItemValue.cpp b/tests/test_InterpolateItemValue.cpp
index fa786632c10660c9197842196440ae383a489edd..fabbda2560ac969e6305586041f531d0b348a0f8 100644
--- a/tests/test_InterpolateItemValue.cpp
+++ b/tests/test_InterpolateItemValue.cpp
@@ -43,10 +43,16 @@ TEST_CASE("InterpolateItemValue", "[language]")
     {
       constexpr size_t Dimension = 1;
 
-      const auto& mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      std::string_view data = R"(
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+
+          std::string_view data = R"(
 import math;
 let scalar_affine_1d: R^1 -> R, x -> 2*x[0] + 2;
 let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
@@ -55,149 +61,152 @@ let R3_non_linear_1d: R^1 -> R^3, x -> (2 * exp(x[0]) + 3, x[0] - 2, 3);
 let R2x2_affine_1d: R^1 -> R^2x2, x -> (2 * x[0] + 3 + 2, 3 * x[0], 2 * x[0], 2);
 let R2x2_non_linear_1d: R^1 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]);
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-      auto ast = ASTBuilder::build(input);
-
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
-
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
 
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+          auto ast = ASTBuilder::build(input);
 
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
-
-      SECTION("scalar_affine_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<double> cell_value{mesh_1d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = 2 * x[0] + 2;
-          });
-
-        CellValue<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-      SECTION("scalar_non_linear_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
 
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
 
-        CellValue<double> cell_value{mesh_1d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = 2 * exp(x[0]) + 3;
-          });
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
 
-        CellValue<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
 
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
+          SECTION("scalar_affine_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-      SECTION("R3_affine_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_affine_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+            CellValue<double> cell_value{mesh_1d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = 2 * x[0] + 2;
+              });
 
-        CellValue<TinyVector<3>> cell_value{mesh_1d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = TinyVector<3>{2 * x[0] + 2, 3 * x[0], 2};
-          });
+            CellValue<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
 
-        CellValue<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
 
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
+          SECTION("scalar_non_linear_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-      SECTION("R3_non_linear_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_non_linear_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+            CellValue<double> cell_value{mesh_1d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = 2 * exp(x[0]) + 3;
+              });
 
-        CellValue<TinyVector<3>> cell_value{mesh_1d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = TinyVector<3>{2 * exp(x[0]) + 3, x[0] - 2, 3};
-          });
+            CellValue<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
 
-        CellValue<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
 
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
+          SECTION("R3_affine_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_affine_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-      SECTION("R2x2_affine_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_affine_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+            CellValue<TinyVector<3>> cell_value{mesh_1d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = TinyVector<3>{2 * x[0] + 2, 3 * x[0], 2};
+              });
 
-        CellValue<TinyMatrix<2>> cell_value{mesh_1d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = TinyMatrix<2>{2 * x[0] + 3 + 2, 3 * x[0], 2 * x[0], 2};
-          });
+            CellValue<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
 
-        CellValue<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
 
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
+          SECTION("R3_non_linear_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_non_linear_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-      SECTION("R2x2_non_linear_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+            CellValue<TinyVector<3>> cell_value{mesh_1d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = TinyVector<3>{2 * exp(x[0]) + 3, x[0] - 2, 3};
+              });
 
-        CellValue<TinyMatrix<2>> cell_value{mesh_1d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]};
-          });
+            CellValue<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
 
-        CellValue<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
 
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
+          SECTION("R2x2_affine_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_affine_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<TinyMatrix<2>> cell_value{mesh_1d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = TinyMatrix<2>{2 * x[0] + 3 + 2, 3 * x[0], 2 * x[0], 2};
+              });
+
+            CellValue<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_non_linear_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<TinyMatrix<2>> cell_value{mesh_1d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id] =
+                  TinyMatrix<2>{2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]};
+              });
+
+            CellValue<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+        }
       }
     }
 
@@ -205,10 +214,16 @@ let R2x2_non_linear_1d: R^1 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[0]) + 3, sin(x
     {
       constexpr size_t Dimension = 2;
 
-      const auto& mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
 
-      std::string_view data = R"(
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+
+          std::string_view data = R"(
 import math;
 let scalar_affine_2d: R^2 -> R, x -> 2*x[0] + 3*x[1] + 2;
 let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
@@ -217,143 +232,146 @@ let R3_non_linear_2d: R^2 -> R^3, x -> (2*exp(x[0])*sin(x[1])+3, x[0]-2*x[1], 3)
 let R2x2_affine_2d: R^2 -> R^2x2, x -> (2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[0] + x[1], 2);
 let R2x2_non_linear_2d: R^2 -> R^2x2, x -> (2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1]), 3, x[0]*x[1]);
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-      auto ast = ASTBuilder::build(input);
-
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
-
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
-
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
-
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
-
-      SECTION("scalar_affine_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<double> cell_value{mesh_2d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = 2 * x[0] + 3 * x[1] + 2;
-          });
-        CellValue<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("scalar_non_linear_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<double> cell_value{mesh_2d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = 2 * exp(x[0]) * sin(x[1]) + 3;
-          });
-        CellValue<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R3_affine_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_affine_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<TinyVector<3>> cell_value{mesh_2d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[1]};
-          });
-        CellValue<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R3_non_linear_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_non_linear_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<TinyVector<3>> cell_value{mesh_2d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + 3, x[0] - 2 * x[1], 3};
-          });
-        CellValue<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R2x2_affine_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_affine_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<TinyMatrix<2>> cell_value{mesh_2d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id] = TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[0] + x[1], 2};
-          });
-        CellValue<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R2x2_non_linear_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<TinyMatrix<2>> cell_value{mesh_2d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3, sin(x[0] - 2 * x[1]), 3, x[0] * x[1]};
-          });
-        CellValue<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+          auto ast = ASTBuilder::build(input);
+
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
+
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
+
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
+
+          SECTION("scalar_affine_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<double> cell_value{mesh_2d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = 2 * x[0] + 3 * x[1] + 2;
+              });
+            CellValue<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("scalar_non_linear_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<double> cell_value{mesh_2d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = 2 * exp(x[0]) * sin(x[1]) + 3;
+              });
+            CellValue<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R3_affine_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_affine_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<TinyVector<3>> cell_value{mesh_2d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[1]};
+              });
+            CellValue<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R3_non_linear_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_non_linear_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<TinyVector<3>> cell_value{mesh_2d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + 3, x[0] - 2 * x[1], 3};
+              });
+            CellValue<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_affine_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_affine_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<TinyMatrix<2>> cell_value{mesh_2d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id] = TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[0] + x[1], 2};
+              });
+            CellValue<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_non_linear_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<TinyMatrix<2>> cell_value{mesh_2d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id] =
+                  TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3, sin(x[0] - 2 * x[1]), 3, x[0] * x[1]};
+              });
+            CellValue<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+        }
       }
     }
 
@@ -361,10 +379,16 @@ let R2x2_non_linear_2d: R^2 -> R^2x2, x -> (2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*
     {
       constexpr size_t Dimension = 3;
 
-      const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
 
-      std::string_view data = R"(
+          std::string_view data = R"(
 import math;
 let scalar_affine_3d: R^3 -> R, x -> 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
 let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
@@ -373,145 +397,147 @@ let R3_non_linear_3d: R^3 -> R^3, x -> (2 * exp(x[0]) * sin(x[1]) + x[2] + 3, x[
 let R2x2_affine_3d: R^3 -> R^2x2, x -> (2 * x[0] + 3 * x[1] + 2 * x[2] + 1, 3 * x[0] + x[1] + 2 * x[2], 2 * x[0] + x[1] + x[2], 2);
 let R2x2_non_linear_3d: R^3 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin(x[0] - 2 * x[1] * x[2]), 3, x[0] * x[1] * x[2]);
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-      auto ast = ASTBuilder::build(input);
-
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
-
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
-
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
-
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
-
-      SECTION("scalar_affine_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<double> cell_value{mesh_3d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
-          });
-        CellValue<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("scalar_non_linear_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<double> cell_value{mesh_3d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id]            = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
-          });
-        CellValue<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R3_affine_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_affine_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<TinyVector<3>> cell_value{mesh_3d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id] = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1] + 2 * x[2], 2 * x[2]};
-          });
-        CellValue<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R3_non_linear_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_non_linear_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<TinyVector<3>> cell_value{mesh_3d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id] = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + x[2] + 3, x[0] * x[2] - 2 * x[1], 3};
-          });
-        CellValue<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R2x2_affine_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_affine_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<TinyMatrix<2>> cell_value{mesh_3d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id] =
-              TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2 * x[2] + 1, 3 * x[0] + x[1] + 2 * x[2], 2 * x[0] + x[1] + x[2], 2};
-          });
-        CellValue<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R2x2_non_linear_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        CellValue<TinyMatrix<2>> cell_value{mesh_3d->connectivity()};
-        parallel_for(
-          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-            const TinyVector<Dimension>& x = xj[cell_id];
-            cell_value[cell_id] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin(x[0] - 2 * x[1] * x[2]),
-                                                3, x[0] * x[1] * x[2]};
-          });
-        CellValue<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+          auto ast = ASTBuilder::build(input);
+
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
+
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
+
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
+
+          SECTION("scalar_affine_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<double> cell_value{mesh_3d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+              });
+            CellValue<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("scalar_non_linear_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<double> cell_value{mesh_3d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+              });
+            CellValue<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R3_affine_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_affine_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<TinyVector<3>> cell_value{mesh_3d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id] = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1] + 2 * x[2], 2 * x[2]};
+              });
+            CellValue<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R3_non_linear_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_non_linear_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<TinyVector<3>> cell_value{mesh_3d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id] = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + x[2] + 3, x[0] * x[2] - 2 * x[1], 3};
+              });
+            CellValue<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_affine_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_affine_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<TinyMatrix<2>> cell_value{mesh_3d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id] = TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2 * x[2] + 1, 3 * x[0] + x[1] + 2 * x[2],
+                                                    2 * x[0] + x[1] + x[2], 2};
+              });
+            CellValue<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_non_linear_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            CellValue<TinyMatrix<2>> cell_value{mesh_3d->connectivity()};
+            parallel_for(
+              cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+                const TinyVector<Dimension>& x = xj[cell_id];
+                cell_value[cell_id]            = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]),
+                                                    sin(x[0] - 2 * x[1] * x[2]), 3, x[0] * x[1] * x[2]};
+              });
+            CellValue<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+        }
       }
     }
   }
@@ -532,18 +558,24 @@ let R2x2_non_linear_3d: R^3 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[1]) + 3 * cos(
     {
       constexpr size_t Dimension = 1;
 
-      const auto& mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      Array<const CellId> cell_id_list = [&] {
-        Array<CellId> cell_ids{mesh_1d->numberOfCells() / 2};
-        for (size_t i_cell = 0; i_cell < cell_ids.size(); ++i_cell) {
-          cell_ids[i_cell] = static_cast<CellId>(2 * i_cell);
-        }
-        return cell_ids;
-      }();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+
+          Array<const CellId> cell_id_list = [&] {
+            Array<CellId> cell_ids{mesh_1d->numberOfCells() / 2};
+            for (size_t i_cell = 0; i_cell < cell_ids.size(); ++i_cell) {
+              cell_ids[i_cell] = static_cast<CellId>(2 * i_cell);
+            }
+            return cell_ids;
+          }();
 
-      std::string_view data = R"(
+          std::string_view data = R"(
 import math;
 let scalar_affine_1d: R^1 -> R, x -> 2*x[0] + 2;
 let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
@@ -552,149 +584,155 @@ let R3_non_linear_1d: R^1 -> R^3, x -> (2 * exp(x[0]) + 3, x[0] - 2, 3);
 let R2x2_affine_1d: R^1 -> R^2x2, x -> (2 * x[0] + 3 + 2, 3 * x[0], 2 * x[0], 2);
 let R2x2_non_linear_1d: R^1 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]);
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-      auto ast = ASTBuilder::build(input);
-
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
-
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
-
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
-
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
 
-      SECTION("scalar_affine_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+          auto ast = ASTBuilder::build(input);
 
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
-        Array<double> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = 2 * x[0] + 2;
-          });
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
 
-        Array<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
 
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("scalar_non_linear_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<double> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = 2 * exp(x[0]) + 3;
-          });
-
-        Array<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R3_affine_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_affine_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<TinyVector<3>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = TinyVector<3>{2 * x[0] + 2, 3 * x[0], 2};
-          });
-
-        Array<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R3_non_linear_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_non_linear_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<TinyVector<3>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = TinyVector<3>{2 * exp(x[0]) + 3, x[0] - 2, 3};
-          });
-
-        Array<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R2x2_affine_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_affine_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
 
-        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = TinyMatrix<2>{2 * x[0] + 3 + 2, 3 * x[0], 2 * x[0], 2};
-          });
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
 
-        Array<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+          SECTION("scalar_affine_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
-      SECTION("R2x2_non_linear_1d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_1d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+            Array<double> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = 2 * x[0] + 2;
+              });
 
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+            Array<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
 
-        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]};
-          });
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("scalar_non_linear_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
 
-        Array<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<double> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = 2 * exp(x[0]) + 3;
+              });
+
+            Array<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R3_affine_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_affine_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyVector<3>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = TinyVector<3>{2 * x[0] + 2, 3 * x[0], 2};
+              });
+
+            Array<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R3_non_linear_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_non_linear_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyVector<3>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = TinyVector<3>{2 * exp(x[0]) + 3, x[0] - 2, 3};
+              });
+
+            Array<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
 
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_affine_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_affine_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = TinyMatrix<2>{2 * x[0] + 3 + 2, 3 * x[0], 2 * x[0], 2};
+              });
+
+            Array<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_non_linear_1d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_1d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]};
+              });
+
+            Array<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+        }
       }
     }
 
@@ -702,15 +740,21 @@ let R2x2_non_linear_1d: R^1 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[0]) + 3, sin(x
     {
       constexpr size_t Dimension = 2;
 
-      const auto& mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      Array<CellId> cell_id_list{mesh_2d->numberOfCells() / 2};
-      for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
-        cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
-      }
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
 
-      std::string_view data = R"(
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+
+          Array<CellId> cell_id_list{mesh_2d->numberOfCells() / 2};
+          for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
+            cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
+          }
+
+          std::string_view data = R"(
 import math;
 let scalar_affine_2d: R^2 -> R, x -> 2*x[0] + 3*x[1] + 2;
 let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
@@ -719,144 +763,150 @@ let R3_non_linear_2d: R^2 -> R^3, x -> (2*exp(x[0])*sin(x[1])+3, x[0]-2*x[1], 3)
 let R2x2_affine_2d: R^2 -> R^2x2, x -> (2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[0] + x[1], 2);
 let R2x2_non_linear_2d: R^2 -> R^2x2, x -> (2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1]), 3, x[0]*x[1]);
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-      auto ast = ASTBuilder::build(input);
-
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
-
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
-
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
-
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
-
-      SECTION("scalar_affine_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<double> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = 2 * x[0] + 3 * x[1] + 2;
-          });
-
-        Array<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("scalar_non_linear_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<double> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = 2 * exp(x[0]) * sin(x[1]) + 3;
-          });
-        Array<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R3_affine_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_affine_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<TinyVector<3>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[1]};
-          });
-        Array<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R3_non_linear_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_non_linear_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<TinyVector<3>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + 3, x[0] - 2 * x[1], 3};
-          });
-        Array<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R2x2_affine_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_affine_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i] = TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[0] + x[1], 2};
-          });
-        Array<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R2x2_non_linear_2d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_2d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3, sin(x[0] - 2 * x[1]), 3, x[0] * x[1]};
-          });
-        Array<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+          auto ast = ASTBuilder::build(input);
+
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
+
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
+
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
+
+          SECTION("scalar_affine_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<double> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = 2 * x[0] + 3 * x[1] + 2;
+              });
+
+            Array<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("scalar_non_linear_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<double> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = 2 * exp(x[0]) * sin(x[1]) + 3;
+              });
+            Array<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R3_affine_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_affine_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyVector<3>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[1]};
+              });
+            Array<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R3_non_linear_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_non_linear_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyVector<3>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + 3, x[0] - 2 * x[1], 3};
+              });
+            Array<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_affine_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_affine_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i] = TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[0] + x[1], 2};
+              });
+            Array<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_non_linear_2d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_2d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3, sin(x[0] - 2 * x[1]), 3, x[0] * x[1]};
+              });
+            Array<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+        }
       }
     }
 
@@ -864,15 +914,21 @@ let R2x2_non_linear_2d: R^2 -> R^2x2, x -> (2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*
     {
       constexpr size_t Dimension = 3;
 
-      const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
-      auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      Array<CellId> cell_id_list{mesh_3d->numberOfCells() / 2};
-      for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
-        cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
-      }
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+
+          Array<CellId> cell_id_list{mesh_3d->numberOfCells() / 2};
+          for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
+            cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
+          }
 
-      std::string_view data = R"(
+          std::string_view data = R"(
 import math;
 let scalar_affine_3d: R^3 -> R, x -> 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
 let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
@@ -881,145 +937,151 @@ let R3_non_linear_3d: R^3 -> R^3, x -> (2 * exp(x[0]) * sin(x[1]) + x[2] + 3, x[
 let R2x2_affine_3d: R^3 -> R^2x2, x -> (2 * x[0] + 3 * x[1] + 2 * x[2] + 1, 3 * x[0] + x[1] + 2 * x[2], 2 * x[0] + x[1] + x[2], 2);
 let R2x2_non_linear_3d: R^3 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin(x[0] - 2 * x[1] * x[2]), 3, x[0] * x[1] * x[2]);
 )";
-      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-
-      auto ast = ASTBuilder::build(input);
-
-      ASTModulesImporter{*ast};
-      ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-      ASTSymbolTableBuilder{*ast};
-      ASTNodeDataTypeBuilder{*ast};
-
-      ASTNodeTypeCleaner<language::var_declaration>{*ast};
-      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
-      ASTNodeExpressionBuilder{*ast};
-
-      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
-
-      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
-      position.byte = data.size();   // ensure that variables are declared at this point
-
-      SECTION("scalar_affine_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<double> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
-          });
-        Array<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("scalar_non_linear_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<double> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i]                  = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
-          });
-        Array<const double> interpolate_value =
-          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R3_affine_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_affine_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<TinyVector<3>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i] = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1] + 2 * x[2], 2 * x[2]};
-          });
-        Array<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R3_non_linear_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R3_non_linear_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<TinyVector<3>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i] = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + x[2] + 3, x[0] * x[2] - 2 * x[1], 3};
-          });
-        Array<const TinyVector<3>> interpolate_value =
-          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R2x2_affine_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_affine_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i] =
-              TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2 * x[2] + 1, 3 * x[0] + x[1] + 2 * x[2], 2 * x[0] + x[1] + x[2], 2};
-          });
-        Array<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
-      }
-
-      SECTION("R2x2_non_linear_3d")
-      {
-        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_3d", position);
-        REQUIRE(found);
-        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
-
-        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
-
-        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
-        parallel_for(
-          cell_value.size(), PUGS_LAMBDA(const size_t i) {
-            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
-            cell_value[i] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin(x[0] - 2 * x[1] * x[2]), 3,
-                                          x[0] * x[1] * x[2]};
-          });
-        Array<const TinyMatrix<2>> interpolate_value =
-          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
-
-        REQUIRE(same_cell_value(cell_value, interpolate_value));
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+          auto ast = ASTBuilder::build(input);
+
+          ASTModulesImporter{*ast};
+          ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+          ASTSymbolTableBuilder{*ast};
+          ASTNodeDataTypeBuilder{*ast};
+
+          ASTNodeTypeCleaner<language::var_declaration>{*ast};
+          ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+          ASTNodeExpressionBuilder{*ast};
+
+          std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+          TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+          position.byte = data.size();   // ensure that variables are declared at this point
+
+          SECTION("scalar_affine_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<double> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+              });
+            Array<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("scalar_non_linear_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<double> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i]                  = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+              });
+            Array<const double> interpolate_value =
+              InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R3_affine_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_affine_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyVector<3>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i] = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1] + 2 * x[2], 2 * x[2]};
+              });
+            Array<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R3_non_linear_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R3_non_linear_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyVector<3>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i] = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + x[2] + 3, x[0] * x[2] - 2 * x[1], 3};
+              });
+            Array<const TinyVector<3>> interpolate_value =
+              InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_affine_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_affine_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i] = TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2 * x[2] + 1, 3 * x[0] + x[1] + 2 * x[2],
+                                              2 * x[0] + x[1] + x[2], 2};
+              });
+            Array<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+
+          SECTION("R2x2_non_linear_3d")
+          {
+            auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_3d", position);
+            REQUIRE(found);
+            REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+            FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+            Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+            parallel_for(
+              cell_value.size(), PUGS_LAMBDA(const size_t i) {
+                const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+                cell_value[i] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin(x[0] - 2 * x[1] * x[2]), 3,
+                                              x[0] * x[1] * x[2]};
+              });
+            Array<const TinyMatrix<2>> interpolate_value =
+              InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj,
+                                                                                      cell_id_list);
+
+            REQUIRE(same_cell_value(cell_value, interpolate_value));
+          }
+        }
       }
     }
   }
diff --git a/tests/test_ItemArray.cpp b/tests/test_ItemArray.cpp
index 17337200786737dcc7b98c75eb18021cfd62a82d..3422d1da9c8e9627abf75fae743a826809ca5505 100644
--- a/tests/test_ItemArray.cpp
+++ b/tests/test_ItemArray.cpp
@@ -28,118 +28,150 @@ TEST_CASE("ItemArray", "[mesh]")
 
   SECTION("1D")
   {
-    const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
-    const Connectivity<1>& connectivity  = mesh_1d.connectivity();
-
-    REQUIRE_NOTHROW(NodeArray<int>{connectivity, 3});
-    REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 3});
-    REQUIRE_NOTHROW(FaceArray<int>{connectivity, 3});
-    REQUIRE_NOTHROW(CellArray<int>{connectivity, 3});
-
-    REQUIRE(NodeArray<int>{connectivity, 3}.isBuilt());
-    REQUIRE(EdgeArray<int>{connectivity, 3}.isBuilt());
-    REQUIRE(FaceArray<int>{connectivity, 3}.isBuilt());
-    REQUIRE(CellArray<int>{connectivity, 3}.isBuilt());
-
-    NodeArray<int> node_value{connectivity, 3};
-    EdgeArray<int> edge_value{connectivity, 3};
-    FaceArray<int> face_value{connectivity, 3};
-    CellArray<int> cell_value{connectivity, 3};
-
-    REQUIRE(edge_value.numberOfItems() == node_value.numberOfItems());
-    REQUIRE(face_value.numberOfItems() == node_value.numberOfItems());
-    REQUIRE(cell_value.numberOfItems() + 1 == node_value.numberOfItems());
-
-    REQUIRE(node_value.sizeOfArrays() == 3);
-    REQUIRE(edge_value.sizeOfArrays() == 3);
-    REQUIRE(face_value.sizeOfArrays() == 3);
-    REQUIRE(cell_value.sizeOfArrays() == 3);
+    std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_1d = named_mesh.mesh();
+
+        const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+        REQUIRE_NOTHROW(NodeArray<int>{connectivity, 3});
+        REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 3});
+        REQUIRE_NOTHROW(FaceArray<int>{connectivity, 3});
+        REQUIRE_NOTHROW(CellArray<int>{connectivity, 3});
+
+        REQUIRE(NodeArray<int>{connectivity, 3}.isBuilt());
+        REQUIRE(EdgeArray<int>{connectivity, 3}.isBuilt());
+        REQUIRE(FaceArray<int>{connectivity, 3}.isBuilt());
+        REQUIRE(CellArray<int>{connectivity, 3}.isBuilt());
+
+        NodeArray<int> node_value{connectivity, 3};
+        EdgeArray<int> edge_value{connectivity, 3};
+        FaceArray<int> face_value{connectivity, 3};
+        CellArray<int> cell_value{connectivity, 3};
+
+        REQUIRE(edge_value.numberOfItems() == node_value.numberOfItems());
+        REQUIRE(face_value.numberOfItems() == node_value.numberOfItems());
+        REQUIRE(cell_value.numberOfItems() + 1 == node_value.numberOfItems());
+
+        REQUIRE(node_value.sizeOfArrays() == 3);
+        REQUIRE(edge_value.sizeOfArrays() == 3);
+        REQUIRE(face_value.sizeOfArrays() == 3);
+        REQUIRE(cell_value.sizeOfArrays() == 3);
+      }
+    }
   }
 
   SECTION("2D")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
-
-    REQUIRE_NOTHROW(NodeArray<int>{connectivity, 2});
-    REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 2});
-    REQUIRE_NOTHROW(FaceArray<int>{connectivity, 2});
-    REQUIRE_NOTHROW(CellArray<int>{connectivity, 2});
-
-    REQUIRE(NodeArray<int>{connectivity, 2}.isBuilt());
-    REQUIRE(EdgeArray<int>{connectivity, 2}.isBuilt());
-    REQUIRE(FaceArray<int>{connectivity, 2}.isBuilt());
-    REQUIRE(CellArray<int>{connectivity, 2}.isBuilt());
-
-    NodeArray<int> node_value{connectivity, 2};
-    EdgeArray<int> edge_value{connectivity, 2};
-    FaceArray<int> face_value{connectivity, 2};
-    CellArray<int> cell_value{connectivity, 2};
-
-    REQUIRE(edge_value.numberOfItems() == face_value.numberOfItems());
-
-    REQUIRE(node_value.sizeOfArrays() == 2);
-    REQUIRE(edge_value.sizeOfArrays() == 2);
-    REQUIRE(face_value.sizeOfArrays() == 2);
-    REQUIRE(cell_value.sizeOfArrays() == 2);
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
+
+        const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+        REQUIRE_NOTHROW(NodeArray<int>{connectivity, 2});
+        REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 2});
+        REQUIRE_NOTHROW(FaceArray<int>{connectivity, 2});
+        REQUIRE_NOTHROW(CellArray<int>{connectivity, 2});
+
+        REQUIRE(NodeArray<int>{connectivity, 2}.isBuilt());
+        REQUIRE(EdgeArray<int>{connectivity, 2}.isBuilt());
+        REQUIRE(FaceArray<int>{connectivity, 2}.isBuilt());
+        REQUIRE(CellArray<int>{connectivity, 2}.isBuilt());
+
+        NodeArray<int> node_value{connectivity, 2};
+        EdgeArray<int> edge_value{connectivity, 2};
+        FaceArray<int> face_value{connectivity, 2};
+        CellArray<int> cell_value{connectivity, 2};
+
+        REQUIRE(edge_value.numberOfItems() == face_value.numberOfItems());
+
+        REQUIRE(node_value.sizeOfArrays() == 2);
+        REQUIRE(edge_value.sizeOfArrays() == 2);
+        REQUIRE(face_value.sizeOfArrays() == 2);
+        REQUIRE(cell_value.sizeOfArrays() == 2);
+      }
+    }
   }
 
   SECTION("3D")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
-
-    REQUIRE_NOTHROW(NodeArray<int>{connectivity, 3});
-    REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 3});
-    REQUIRE_NOTHROW(FaceArray<int>{connectivity, 3});
-    REQUIRE_NOTHROW(CellArray<int>{connectivity, 3});
-
-    REQUIRE(NodeArray<int>{connectivity, 3}.isBuilt());
-    REQUIRE(EdgeArray<int>{connectivity, 3}.isBuilt());
-    REQUIRE(FaceArray<int>{connectivity, 3}.isBuilt());
-    REQUIRE(CellArray<int>{connectivity, 3}.isBuilt());
-
-    NodeArray<int> node_value{connectivity, 3};
-    EdgeArray<int> edge_value{connectivity, 3};
-    FaceArray<int> face_value{connectivity, 3};
-    CellArray<int> cell_value{connectivity, 3};
-
-    REQUIRE(node_value.sizeOfArrays() == 3);
-    REQUIRE(edge_value.sizeOfArrays() == 3);
-    REQUIRE(face_value.sizeOfArrays() == 3);
-    REQUIRE(cell_value.sizeOfArrays() == 3);
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
+
+        const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+        REQUIRE_NOTHROW(NodeArray<int>{connectivity, 3});
+        REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 3});
+        REQUIRE_NOTHROW(FaceArray<int>{connectivity, 3});
+        REQUIRE_NOTHROW(CellArray<int>{connectivity, 3});
+
+        REQUIRE(NodeArray<int>{connectivity, 3}.isBuilt());
+        REQUIRE(EdgeArray<int>{connectivity, 3}.isBuilt());
+        REQUIRE(FaceArray<int>{connectivity, 3}.isBuilt());
+        REQUIRE(CellArray<int>{connectivity, 3}.isBuilt());
+
+        NodeArray<int> node_value{connectivity, 3};
+        EdgeArray<int> edge_value{connectivity, 3};
+        FaceArray<int> face_value{connectivity, 3};
+        CellArray<int> cell_value{connectivity, 3};
+
+        REQUIRE(node_value.sizeOfArrays() == 3);
+        REQUIRE(edge_value.sizeOfArrays() == 3);
+        REQUIRE(face_value.sizeOfArrays() == 3);
+        REQUIRE(cell_value.sizeOfArrays() == 3);
+      }
+    }
   }
 
   SECTION("set values from array")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    CellArray<size_t> cell_array{connectivity, 3};
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
 
-    Table<size_t> table{cell_array.numberOfItems(), cell_array.sizeOfArrays()};
-    {
-      size_t k = 0;
-      for (size_t i = 0; i < table.numberOfRows(); ++i) {
-        for (size_t j = 0; j < table.numberOfColumns(); ++j) {
-          table(i, j) = k++;
-        }
-      }
-    }
-    cell_array = table;
+        const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-    auto is_same = [](const CellArray<size_t>& cell_array, const Table<size_t>& table) {
-      bool is_same = true;
-      for (CellId cell_id = 0; cell_id < cell_array.numberOfItems(); ++cell_id) {
-        Array sub_array = cell_array[cell_id];
-        for (size_t i = 0; i < sub_array.size(); ++i) {
-          is_same &= (sub_array[i] == table(cell_id, i));
+        CellArray<size_t> cell_array{connectivity, 3};
+
+        Table<size_t> table{cell_array.numberOfItems(), cell_array.sizeOfArrays()};
+        {
+          size_t k = 0;
+          for (size_t i = 0; i < table.numberOfRows(); ++i) {
+            for (size_t j = 0; j < table.numberOfColumns(); ++j) {
+              table(i, j) = k++;
+            }
+          }
         }
+        cell_array = table;
+
+        auto is_same = [](const CellArray<size_t>& cell_array, const Table<size_t>& table) {
+          bool is_same = true;
+          for (CellId cell_id = 0; cell_id < cell_array.numberOfItems(); ++cell_id) {
+            Array sub_array = cell_array[cell_id];
+            for (size_t i = 0; i < sub_array.size(); ++i) {
+              is_same &= (sub_array[i] == table(cell_id, i));
+            }
+          }
+          return is_same;
+        };
+
+        REQUIRE(is_same(cell_array, table));
       }
-      return is_same;
-    };
-
-    REQUIRE(is_same(cell_array, table));
+    }
   }
 
   SECTION("copy")
@@ -155,43 +187,59 @@ TEST_CASE("ItemArray", "[mesh]")
       return is_same;
     };
 
-    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
 
-    CellArray<int> cell_array{connectivity, 4};
-    cell_array.fill(parallel::rank());
+        const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-    CellArray<const int> cell_array_const_view{cell_array};
-    REQUIRE(cell_array.numberOfItems() == cell_array_const_view.numberOfItems());
-    REQUIRE(cell_array.sizeOfArrays() == cell_array_const_view.sizeOfArrays());
-    REQUIRE(is_same(cell_array_const_view, static_cast<std::int64_t>(parallel::rank())));
+        CellArray<int> cell_array{connectivity, 4};
+        cell_array.fill(parallel::rank());
 
-    CellArray<const int> const_cell_array;
-    const_cell_array = copy(cell_array);
+        CellArray<const int> cell_array_const_view{cell_array};
+        REQUIRE(cell_array.numberOfItems() == cell_array_const_view.numberOfItems());
+        REQUIRE(cell_array.sizeOfArrays() == cell_array_const_view.sizeOfArrays());
+        REQUIRE(is_same(cell_array_const_view, static_cast<std::int64_t>(parallel::rank())));
 
-    CellArray<int> duplicated_cell_array{connectivity, cell_array.sizeOfArrays()};
-    copy_to(const_cell_array, duplicated_cell_array);
+        CellArray<const int> const_cell_array;
+        const_cell_array = copy(cell_array);
 
-    cell_array.fill(0);
+        CellArray<int> duplicated_cell_array{connectivity, cell_array.sizeOfArrays()};
+        copy_to(const_cell_array, duplicated_cell_array);
 
-    REQUIRE(is_same(cell_array, 0));
-    REQUIRE(is_same(cell_array_const_view, 0));
-    REQUIRE(is_same(const_cell_array, static_cast<std::int64_t>(parallel::rank())));
-    REQUIRE(is_same(duplicated_cell_array, static_cast<std::int64_t>(parallel::rank())));
+        cell_array.fill(0);
+
+        REQUIRE(is_same(cell_array, 0));
+        REQUIRE(is_same(cell_array_const_view, 0));
+        REQUIRE(is_same(const_cell_array, static_cast<std::int64_t>(parallel::rank())));
+        REQUIRE(is_same(duplicated_cell_array, static_cast<std::int64_t>(parallel::rank())));
+      }
+    }
   }
 
   SECTION("WeakItemArray")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
+
+        const Connectivity<2>& connectivity = mesh_2d->connectivity();
 
-    WeakFaceArray<int> weak_face_array{connectivity, 5};
+        WeakFaceArray<int> weak_face_array{connectivity, 5};
 
-    weak_face_array.fill(parallel::rank());
+        weak_face_array.fill(parallel::rank());
 
-    FaceArray<const int> face_array{weak_face_array};
+        FaceArray<const int> face_array{weak_face_array};
 
-    REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+        REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+      }
+    }
   }
 
 #ifndef NDEBUG
@@ -214,35 +262,51 @@ TEST_CASE("ItemArray", "[mesh]")
 
     SECTION("checking for bounds violation")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
 
-      CellArray<int> cell_array{connectivity, 1};
-      CellId invalid_cell_id = connectivity.numberOfCells();
-      REQUIRE_THROWS_AS(cell_array[invalid_cell_id], AssertError);
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-      FaceArray<int> face_array{connectivity, 2};
-      FaceId invalid_face_id = connectivity.numberOfFaces();
-      REQUIRE_THROWS_AS(face_array[invalid_face_id], AssertError);
+          CellArray<int> cell_array{connectivity, 1};
+          CellId invalid_cell_id = connectivity.numberOfCells();
+          REQUIRE_THROWS_AS(cell_array[invalid_cell_id], AssertError);
 
-      EdgeArray<int> edge_array{connectivity, 1};
-      EdgeId invalid_edge_id = connectivity.numberOfEdges();
-      REQUIRE_THROWS_AS(edge_array[invalid_edge_id], AssertError);
+          FaceArray<int> face_array{connectivity, 2};
+          FaceId invalid_face_id = connectivity.numberOfFaces();
+          REQUIRE_THROWS_AS(face_array[invalid_face_id], AssertError);
 
-      NodeArray<int> node_array{connectivity, 0};
-      NodeId invalid_node_id = connectivity.numberOfNodes();
-      REQUIRE_THROWS_AS(node_array[invalid_node_id], AssertError);
+          EdgeArray<int> edge_array{connectivity, 1};
+          EdgeId invalid_edge_id = connectivity.numberOfEdges();
+          REQUIRE_THROWS_AS(edge_array[invalid_edge_id], AssertError);
+
+          NodeArray<int> node_array{connectivity, 0};
+          NodeId invalid_node_id = connectivity.numberOfNodes();
+          REQUIRE_THROWS_AS(node_array[invalid_node_id], AssertError);
+        }
+      }
     }
 
     SECTION("set values from invalid array size")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-      CellArray<size_t> cell_array{connectivity, 2};
+          CellArray<size_t> cell_array{connectivity, 2};
 
-      Table<size_t> values{3, connectivity.numberOfCells() + 3};
-      REQUIRE_THROWS_AS(cell_array = values, AssertError);
+          Table<size_t> values{3, connectivity.numberOfCells() + 3};
+          REQUIRE_THROWS_AS(cell_array = values, AssertError);
+        }
+      }
     }
   }
 #endif   // NDEBUG
diff --git a/tests/test_ItemArrayUtils.cpp b/tests/test_ItemArrayUtils.cpp
index 6eb6894d41aa3700fdc86e6c0d7c9d83acda328e..af91ecb70897073a4069f20457190b6e20318e72 100644
--- a/tests/test_ItemArrayUtils.cpp
+++ b/tests/test_ItemArrayUtils.cpp
@@ -19,750 +19,774 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
-      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
-
-      SECTION("node")
-      {
-        WeakNodeArray<size_t> weak_node_array{connectivity, 4};
-        auto node_number = connectivity.nodeNumber();
-
-        for (NodeId i_node = 0; i_node < mesh_1d.numberOfNodes(); ++i_node) {
-          Array array         = weak_node_array[i_node];
-          const size_t number = node_number[i_node];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-        NodeArray<const size_t> node_array{weak_node_array};
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
 
-        REQUIRE(node_array.connectivity_ptr() == weak_node_array.connectivity_ptr());
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
 
-        {   // before synchronization
-          auto node_owner    = connectivity.nodeOwner();
-          auto node_is_owned = connectivity.nodeIsOwned();
+          SECTION("node")
+          {
+            WeakNodeArray<size_t> weak_node_array{connectivity, 4};
+            auto node_number = connectivity.nodeNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (NodeId i_node = 0; i_node < mesh_1d.numberOfNodes(); ++i_node) {
-            Array array         = node_array[i_node];
-            const size_t number = node_number[i_node];
-            const size_t owner  = node_owner[i_node];
-            if (node_is_owned[i_node]) {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
-              }
-            } else {
+            for (NodeId i_node = 0; i_node < mesh_1d->numberOfNodes(); ++i_node) {
+              Array array         = weak_node_array[i_node];
+              const size_t number = node_number[i_node];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
             }
-          }
 
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
-
-        synchronize(weak_node_array);
-
-        {   // after synchronization
-          auto node_owner = connectivity.nodeOwner();
+            NodeArray<const size_t> node_array{weak_node_array};
+
+            REQUIRE(node_array.connectivity_ptr() == weak_node_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto node_owner    = connectivity.nodeOwner();
+              auto node_is_owned = connectivity.nodeIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (NodeId i_node = 0; i_node < mesh_1d->numberOfNodes(); ++i_node) {
+                Array array         = node_array[i_node];
+                const size_t number = node_number[i_node];
+                const size_t owner  = node_owner[i_node];
+                if (node_is_owned[i_node]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
+              }
 
-          bool is_synchronized = true;
-          for (NodeId i_node = 0; i_node < mesh_1d.numberOfNodes(); ++i_node) {
-            Array array         = node_array[i_node];
-            const size_t number = node_number[i_node];
-            const size_t owner  = node_owner[i_node];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
-
-          REQUIRE(is_synchronized);
-        }
-      }
 
-      SECTION("edge")
-      {
-        WeakEdgeArray<size_t> weak_edge_array{connectivity, 4};
-        auto edge_number = connectivity.edgeNumber();
+            synchronize(weak_node_array);
 
-        for (EdgeId i_edge = 0; i_edge < mesh_1d.numberOfEdges(); ++i_edge) {
-          Array array         = weak_edge_array[i_edge];
-          const size_t number = edge_number[i_edge];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
+            {   // after synchronization
+              auto node_owner = connectivity.nodeOwner();
 
-        EdgeArray<const size_t> edge_array{weak_edge_array};
+              bool is_synchronized = true;
+              for (NodeId i_node = 0; i_node < mesh_1d->numberOfNodes(); ++i_node) {
+                Array array         = node_array[i_node];
+                const size_t number = node_number[i_node];
+                const size_t owner  = node_owner[i_node];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-        REQUIRE(edge_array.connectivity_ptr() == weak_edge_array.connectivity_ptr());
+              REQUIRE(is_synchronized);
+            }
+          }
 
-        {   // before synchronization
-          auto edge_owner    = connectivity.edgeOwner();
-          auto edge_is_owned = connectivity.edgeIsOwned();
+          SECTION("edge")
+          {
+            WeakEdgeArray<size_t> weak_edge_array{connectivity, 4};
+            auto edge_number = connectivity.edgeNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (EdgeId i_edge = 0; i_edge < mesh_1d.numberOfEdges(); ++i_edge) {
-            Array array         = edge_array[i_edge];
-            const size_t number = edge_number[i_edge];
-            const size_t owner  = edge_owner[i_edge];
-            if (edge_is_owned[i_edge]) {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
-              }
-            } else {
+            for (EdgeId i_edge = 0; i_edge < mesh_1d->numberOfEdges(); ++i_edge) {
+              Array array         = weak_edge_array[i_edge];
+              const size_t number = edge_number[i_edge];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
             }
-          }
-
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
-
-        synchronize(weak_edge_array);
 
-        {   // after synchronization
-          auto edge_owner = connectivity.edgeOwner();
+            EdgeArray<const size_t> edge_array{weak_edge_array};
+
+            REQUIRE(edge_array.connectivity_ptr() == weak_edge_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto edge_owner    = connectivity.edgeOwner();
+              auto edge_is_owned = connectivity.edgeIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (EdgeId i_edge = 0; i_edge < mesh_1d->numberOfEdges(); ++i_edge) {
+                Array array         = edge_array[i_edge];
+                const size_t number = edge_number[i_edge];
+                const size_t owner  = edge_owner[i_edge];
+                if (edge_is_owned[i_edge]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
+              }
 
-          bool is_synchronized = true;
-          for (EdgeId i_edge = 0; i_edge < mesh_1d.numberOfEdges(); ++i_edge) {
-            Array array         = edge_array[i_edge];
-            const size_t number = edge_number[i_edge];
-            const size_t owner  = edge_owner[i_edge];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
 
-          REQUIRE(is_synchronized);
-        }
-      }
+            synchronize(weak_edge_array);
 
-      SECTION("face")
-      {
-        WeakFaceArray<size_t> weak_face_array{connectivity, 4};
-        auto face_number = connectivity.faceNumber();
+            {   // after synchronization
+              auto edge_owner = connectivity.edgeOwner();
 
-        for (FaceId i_face = 0; i_face < mesh_1d.numberOfFaces(); ++i_face) {
-          Array array         = weak_face_array[i_face];
-          const size_t number = face_number[i_face];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
-
-        FaceArray<const size_t> face_array{weak_face_array};
+              bool is_synchronized = true;
+              for (EdgeId i_edge = 0; i_edge < mesh_1d->numberOfEdges(); ++i_edge) {
+                Array array         = edge_array[i_edge];
+                const size_t number = edge_number[i_edge];
+                const size_t owner  = edge_owner[i_edge];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-        REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+              REQUIRE(is_synchronized);
+            }
+          }
 
-        {   // before synchronization
-          auto face_owner    = connectivity.faceOwner();
-          auto face_is_owned = connectivity.faceIsOwned();
+          SECTION("face")
+          {
+            WeakFaceArray<size_t> weak_face_array{connectivity, 4};
+            auto face_number = connectivity.faceNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (FaceId i_face = 0; i_face < mesh_1d.numberOfFaces(); ++i_face) {
-            Array array         = face_array[i_face];
-            const size_t number = face_number[i_face];
-            const size_t owner  = face_owner[i_face];
-            if (face_is_owned[i_face]) {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
-              }
-            } else {
+            for (FaceId i_face = 0; i_face < mesh_1d->numberOfFaces(); ++i_face) {
+              Array array         = weak_face_array[i_face];
+              const size_t number = face_number[i_face];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
             }
-          }
-
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
 
-        synchronize(weak_face_array);
-
-        {   // after synchronization
-          auto face_owner = connectivity.faceOwner();
+            FaceArray<const size_t> face_array{weak_face_array};
+
+            REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto face_owner    = connectivity.faceOwner();
+              auto face_is_owned = connectivity.faceIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (FaceId i_face = 0; i_face < mesh_1d->numberOfFaces(); ++i_face) {
+                Array array         = face_array[i_face];
+                const size_t number = face_number[i_face];
+                const size_t owner  = face_owner[i_face];
+                if (face_is_owned[i_face]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
+              }
 
-          bool is_synchronized = true;
-          for (FaceId i_face = 0; i_face < mesh_1d.numberOfFaces(); ++i_face) {
-            Array array         = face_array[i_face];
-            const size_t number = face_number[i_face];
-            const size_t owner  = face_owner[i_face];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
 
-          REQUIRE(is_synchronized);
-        }
-      }
+            synchronize(weak_face_array);
 
-      SECTION("cell")
-      {
-        WeakCellArray<size_t> weak_cell_array{connectivity, 4};
-        auto cell_number = connectivity.cellNumber();
+            {   // after synchronization
+              auto face_owner = connectivity.faceOwner();
 
-        for (CellId i_cell = 0; i_cell < mesh_1d.numberOfCells(); ++i_cell) {
-          Array array         = weak_cell_array[i_cell];
-          const size_t number = cell_number[i_cell];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
-
-        CellArray<const size_t> cell_array{weak_cell_array};
+              bool is_synchronized = true;
+              for (FaceId i_face = 0; i_face < mesh_1d->numberOfFaces(); ++i_face) {
+                Array array         = face_array[i_face];
+                const size_t number = face_number[i_face];
+                const size_t owner  = face_owner[i_face];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-        REQUIRE(cell_array.connectivity_ptr() == weak_cell_array.connectivity_ptr());
+              REQUIRE(is_synchronized);
+            }
+          }
 
-        {   // before synchronization
-          auto cell_owner    = connectivity.cellOwner();
-          auto cell_is_owned = connectivity.cellIsOwned();
+          SECTION("cell")
+          {
+            WeakCellArray<size_t> weak_cell_array{connectivity, 4};
+            auto cell_number = connectivity.cellNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (CellId i_cell = 0; i_cell < mesh_1d.numberOfCells(); ++i_cell) {
-            Array array         = cell_array[i_cell];
-            const size_t number = cell_number[i_cell];
-            const size_t owner  = cell_owner[i_cell];
-            if (cell_is_owned[i_cell]) {
+            for (CellId i_cell = 0; i_cell < mesh_1d->numberOfCells(); ++i_cell) {
+              Array array         = weak_cell_array[i_cell];
+              const size_t number = cell_number[i_cell];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
-            } else {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+
+            CellArray<const size_t> cell_array{weak_cell_array};
+
+            REQUIRE(cell_array.connectivity_ptr() == weak_cell_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto cell_owner    = connectivity.cellOwner();
+              auto cell_is_owned = connectivity.cellIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (CellId i_cell = 0; i_cell < mesh_1d->numberOfCells(); ++i_cell) {
+                Array array         = cell_array[i_cell];
+                const size_t number = cell_number[i_cell];
+                const size_t owner  = cell_owner[i_cell];
+                if (cell_is_owned[i_cell]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
               }
+
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
 
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
+            synchronize(weak_cell_array);
 
-        synchronize(weak_cell_array);
+            {   // after synchronization
+              auto cell_owner = connectivity.cellOwner();
 
-        {   // after synchronization
-          auto cell_owner = connectivity.cellOwner();
+              bool is_synchronized = true;
+              for (CellId i_cell = 0; i_cell < mesh_1d->numberOfCells(); ++i_cell) {
+                Array array         = cell_array[i_cell];
+                const size_t number = cell_number[i_cell];
+                const size_t owner  = cell_owner[i_cell];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-          bool is_synchronized = true;
-          for (CellId i_cell = 0; i_cell < mesh_1d.numberOfCells(); ++i_cell) {
-            Array array         = cell_array[i_cell];
-            const size_t number = cell_number[i_cell];
-            const size_t owner  = cell_owner[i_cell];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_synchronized);
             }
           }
-
-          REQUIRE(is_synchronized);
         }
       }
     }
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
-
-      SECTION("node")
-      {
-        WeakNodeArray<size_t> weak_node_array{connectivity, 3};
-        auto node_number = connectivity.nodeNumber();
-
-        for (NodeId i_node = 0; i_node < mesh_2d.numberOfNodes(); ++i_node) {
-          Array array         = weak_node_array[i_node];
-          const size_t number = node_number[i_node];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        NodeArray<const size_t> node_array{weak_node_array};
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
 
-        REQUIRE(node_array.connectivity_ptr() == weak_node_array.connectivity_ptr());
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
 
-        {   // before synchronization
-          auto node_owner    = connectivity.nodeOwner();
-          auto node_is_owned = connectivity.nodeIsOwned();
+          SECTION("node")
+          {
+            WeakNodeArray<size_t> weak_node_array{connectivity, 3};
+            auto node_number = connectivity.nodeNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (NodeId i_node = 0; i_node < mesh_2d.numberOfNodes(); ++i_node) {
-            Array array         = node_array[i_node];
-            const size_t number = node_number[i_node];
-            const size_t owner  = node_owner[i_node];
-            if (node_is_owned[i_node]) {
+            for (NodeId i_node = 0; i_node < mesh_2d->numberOfNodes(); ++i_node) {
+              Array array         = weak_node_array[i_node];
+              const size_t number = node_number[i_node];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
-              }
-            } else {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
             }
-          }
-
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
-
-        synchronize(weak_node_array);
 
-        {   // after synchronization
-          auto node_owner = connectivity.nodeOwner();
+            NodeArray<const size_t> node_array{weak_node_array};
+
+            REQUIRE(node_array.connectivity_ptr() == weak_node_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto node_owner    = connectivity.nodeOwner();
+              auto node_is_owned = connectivity.nodeIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (NodeId i_node = 0; i_node < mesh_2d->numberOfNodes(); ++i_node) {
+                Array array         = node_array[i_node];
+                const size_t number = node_number[i_node];
+                const size_t owner  = node_owner[i_node];
+                if (node_is_owned[i_node]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
+              }
 
-          bool is_synchronized = true;
-          for (NodeId i_node = 0; i_node < mesh_2d.numberOfNodes(); ++i_node) {
-            Array array         = node_array[i_node];
-            const size_t number = node_number[i_node];
-            const size_t owner  = node_owner[i_node];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
 
-          REQUIRE(is_synchronized);
-        }
-      }
+            synchronize(weak_node_array);
 
-      SECTION("edge")
-      {
-        WeakEdgeArray<size_t> weak_edge_array{connectivity, 3};
-        auto edge_number = connectivity.edgeNumber();
+            {   // after synchronization
+              auto node_owner = connectivity.nodeOwner();
 
-        for (EdgeId i_edge = 0; i_edge < mesh_2d.numberOfEdges(); ++i_edge) {
-          Array array         = weak_edge_array[i_edge];
-          const size_t number = edge_number[i_edge];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
-
-        EdgeArray<const size_t> edge_array{weak_edge_array};
+              bool is_synchronized = true;
+              for (NodeId i_node = 0; i_node < mesh_2d->numberOfNodes(); ++i_node) {
+                Array array         = node_array[i_node];
+                const size_t number = node_number[i_node];
+                const size_t owner  = node_owner[i_node];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-        REQUIRE(edge_array.connectivity_ptr() == weak_edge_array.connectivity_ptr());
+              REQUIRE(is_synchronized);
+            }
+          }
 
-        {   // before synchronization
-          auto edge_owner    = connectivity.edgeOwner();
-          auto edge_is_owned = connectivity.edgeIsOwned();
+          SECTION("edge")
+          {
+            WeakEdgeArray<size_t> weak_edge_array{connectivity, 3};
+            auto edge_number = connectivity.edgeNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (EdgeId i_edge = 0; i_edge < mesh_2d.numberOfEdges(); ++i_edge) {
-            Array array         = edge_array[i_edge];
-            const size_t number = edge_number[i_edge];
-            const size_t owner  = edge_owner[i_edge];
-            if (edge_is_owned[i_edge]) {
+            for (EdgeId i_edge = 0; i_edge < mesh_2d->numberOfEdges(); ++i_edge) {
+              Array array         = weak_edge_array[i_edge];
+              const size_t number = edge_number[i_edge];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
-              }
-            } else {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
             }
-          }
-
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
-
-        synchronize(weak_edge_array);
 
-        {   // after synchronization
-          auto edge_owner = connectivity.edgeOwner();
+            EdgeArray<const size_t> edge_array{weak_edge_array};
+
+            REQUIRE(edge_array.connectivity_ptr() == weak_edge_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto edge_owner    = connectivity.edgeOwner();
+              auto edge_is_owned = connectivity.edgeIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (EdgeId i_edge = 0; i_edge < mesh_2d->numberOfEdges(); ++i_edge) {
+                Array array         = edge_array[i_edge];
+                const size_t number = edge_number[i_edge];
+                const size_t owner  = edge_owner[i_edge];
+                if (edge_is_owned[i_edge]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
+              }
 
-          bool is_synchronized = true;
-          for (EdgeId i_edge = 0; i_edge < mesh_2d.numberOfEdges(); ++i_edge) {
-            Array array         = edge_array[i_edge];
-            const size_t number = edge_number[i_edge];
-            const size_t owner  = edge_owner[i_edge];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
-
-          REQUIRE(is_synchronized);
-        }
-      }
 
-      SECTION("face")
-      {
-        WeakFaceArray<size_t> weak_face_array{connectivity, 3};
-        auto face_number = connectivity.faceNumber();
+            synchronize(weak_edge_array);
 
-        for (FaceId i_face = 0; i_face < mesh_2d.numberOfFaces(); ++i_face) {
-          Array array         = weak_face_array[i_face];
-          const size_t number = face_number[i_face];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
+            {   // after synchronization
+              auto edge_owner = connectivity.edgeOwner();
 
-        FaceArray<const size_t> face_array{weak_face_array};
+              bool is_synchronized = true;
+              for (EdgeId i_edge = 0; i_edge < mesh_2d->numberOfEdges(); ++i_edge) {
+                Array array         = edge_array[i_edge];
+                const size_t number = edge_number[i_edge];
+                const size_t owner  = edge_owner[i_edge];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-        REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+              REQUIRE(is_synchronized);
+            }
+          }
 
-        {   // before synchronization
-          auto face_owner    = connectivity.faceOwner();
-          auto face_is_owned = connectivity.faceIsOwned();
+          SECTION("face")
+          {
+            WeakFaceArray<size_t> weak_face_array{connectivity, 3};
+            auto face_number = connectivity.faceNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (FaceId i_face = 0; i_face < mesh_2d.numberOfFaces(); ++i_face) {
-            Array array         = face_array[i_face];
-            const size_t number = face_number[i_face];
-            const size_t owner  = face_owner[i_face];
-            if (face_is_owned[i_face]) {
+            for (FaceId i_face = 0; i_face < mesh_2d->numberOfFaces(); ++i_face) {
+              Array array         = weak_face_array[i_face];
+              const size_t number = face_number[i_face];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
-              }
-            } else {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
             }
-          }
-
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
-
-        synchronize(weak_face_array);
 
-        {   // after synchronization
-          auto face_owner = connectivity.faceOwner();
+            FaceArray<const size_t> face_array{weak_face_array};
+
+            REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto face_owner    = connectivity.faceOwner();
+              auto face_is_owned = connectivity.faceIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (FaceId i_face = 0; i_face < mesh_2d->numberOfFaces(); ++i_face) {
+                Array array         = face_array[i_face];
+                const size_t number = face_number[i_face];
+                const size_t owner  = face_owner[i_face];
+                if (face_is_owned[i_face]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
+              }
 
-          bool is_synchronized = true;
-          for (FaceId i_face = 0; i_face < mesh_2d.numberOfFaces(); ++i_face) {
-            Array array         = face_array[i_face];
-            const size_t number = face_number[i_face];
-            const size_t owner  = face_owner[i_face];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
 
-          REQUIRE(is_synchronized);
-        }
-      }
+            synchronize(weak_face_array);
 
-      SECTION("cell")
-      {
-        WeakCellArray<size_t> weak_cell_array{connectivity, 3};
-        auto cell_number = connectivity.cellNumber();
+            {   // after synchronization
+              auto face_owner = connectivity.faceOwner();
 
-        for (CellId i_cell = 0; i_cell < mesh_2d.numberOfCells(); ++i_cell) {
-          Array array         = weak_cell_array[i_cell];
-          const size_t number = cell_number[i_cell];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
-
-        CellArray<const size_t> cell_array{weak_cell_array};
+              bool is_synchronized = true;
+              for (FaceId i_face = 0; i_face < mesh_2d->numberOfFaces(); ++i_face) {
+                Array array         = face_array[i_face];
+                const size_t number = face_number[i_face];
+                const size_t owner  = face_owner[i_face];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-        REQUIRE(cell_array.connectivity_ptr() == weak_cell_array.connectivity_ptr());
+              REQUIRE(is_synchronized);
+            }
+          }
 
-        {   // before synchronization
-          auto cell_owner    = connectivity.cellOwner();
-          auto cell_is_owned = connectivity.cellIsOwned();
+          SECTION("cell")
+          {
+            WeakCellArray<size_t> weak_cell_array{connectivity, 3};
+            auto cell_number = connectivity.cellNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (CellId i_cell = 0; i_cell < mesh_2d.numberOfCells(); ++i_cell) {
-            Array array         = cell_array[i_cell];
-            const size_t number = cell_number[i_cell];
-            const size_t owner  = cell_owner[i_cell];
-            if (cell_is_owned[i_cell]) {
+            for (CellId i_cell = 0; i_cell < mesh_2d->numberOfCells(); ++i_cell) {
+              Array array         = weak_cell_array[i_cell];
+              const size_t number = cell_number[i_cell];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
-            } else {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+
+            CellArray<const size_t> cell_array{weak_cell_array};
+
+            REQUIRE(cell_array.connectivity_ptr() == weak_cell_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto cell_owner    = connectivity.cellOwner();
+              auto cell_is_owned = connectivity.cellIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (CellId i_cell = 0; i_cell < mesh_2d->numberOfCells(); ++i_cell) {
+                Array array         = cell_array[i_cell];
+                const size_t number = cell_number[i_cell];
+                const size_t owner  = cell_owner[i_cell];
+                if (cell_is_owned[i_cell]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
               }
+
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
 
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
+            synchronize(weak_cell_array);
 
-        synchronize(weak_cell_array);
+            {   // after synchronization
+              auto cell_owner = connectivity.cellOwner();
 
-        {   // after synchronization
-          auto cell_owner = connectivity.cellOwner();
+              bool is_synchronized = true;
+              for (CellId i_cell = 0; i_cell < mesh_2d->numberOfCells(); ++i_cell) {
+                Array array         = cell_array[i_cell];
+                const size_t number = cell_number[i_cell];
+                const size_t owner  = cell_owner[i_cell];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-          bool is_synchronized = true;
-          for (CellId i_cell = 0; i_cell < mesh_2d.numberOfCells(); ++i_cell) {
-            Array array         = cell_array[i_cell];
-            const size_t number = cell_number[i_cell];
-            const size_t owner  = cell_owner[i_cell];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_synchronized);
             }
           }
-
-          REQUIRE(is_synchronized);
         }
       }
     }
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
-
-      SECTION("node")
-      {
-        WeakNodeArray<size_t> weak_node_array{connectivity, 4};
-        auto node_number = connectivity.nodeNumber();
-
-        for (NodeId i_node = 0; i_node < mesh_3d.numberOfNodes(); ++i_node) {
-          Array array         = weak_node_array[i_node];
-          const size_t number = node_number[i_node];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-        NodeArray<const size_t> node_array{weak_node_array};
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
 
-        REQUIRE(node_array.connectivity_ptr() == weak_node_array.connectivity_ptr());
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-        {   // before synchronization
-          auto node_owner    = connectivity.nodeOwner();
-          auto node_is_owned = connectivity.nodeIsOwned();
+          SECTION("node")
+          {
+            WeakNodeArray<size_t> weak_node_array{connectivity, 4};
+            auto node_number = connectivity.nodeNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (NodeId i_node = 0; i_node < mesh_3d.numberOfNodes(); ++i_node) {
-            Array array         = node_array[i_node];
-            const size_t number = node_number[i_node];
-            const size_t owner  = node_owner[i_node];
-            if (node_is_owned[i_node]) {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
-              }
-            } else {
+            for (NodeId i_node = 0; i_node < mesh_3d->numberOfNodes(); ++i_node) {
+              Array array         = weak_node_array[i_node];
+              const size_t number = node_number[i_node];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
             }
-          }
-
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
 
-        synchronize(weak_node_array);
-
-        {   // after synchronization
-          auto node_owner = connectivity.nodeOwner();
+            NodeArray<const size_t> node_array{weak_node_array};
+
+            REQUIRE(node_array.connectivity_ptr() == weak_node_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto node_owner    = connectivity.nodeOwner();
+              auto node_is_owned = connectivity.nodeIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (NodeId i_node = 0; i_node < mesh_3d->numberOfNodes(); ++i_node) {
+                Array array         = node_array[i_node];
+                const size_t number = node_number[i_node];
+                const size_t owner  = node_owner[i_node];
+                if (node_is_owned[i_node]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
+              }
 
-          bool is_synchronized = true;
-          for (NodeId i_node = 0; i_node < mesh_3d.numberOfNodes(); ++i_node) {
-            Array array         = node_array[i_node];
-            const size_t number = node_number[i_node];
-            const size_t owner  = node_owner[i_node];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
 
-          REQUIRE(is_synchronized);
-        }
-      }
-
-      SECTION("edge")
-      {
-        WeakEdgeArray<size_t> weak_edge_array{connectivity, 4};
-        auto edge_number = connectivity.edgeNumber();
+            synchronize(weak_node_array);
 
-        for (EdgeId i_edge = 0; i_edge < mesh_3d.numberOfEdges(); ++i_edge) {
-          Array array         = weak_edge_array[i_edge];
-          const size_t number = edge_number[i_edge];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
+            {   // after synchronization
+              auto node_owner = connectivity.nodeOwner();
 
-        EdgeArray<const size_t> edge_array{weak_edge_array};
+              bool is_synchronized = true;
+              for (NodeId i_node = 0; i_node < mesh_3d->numberOfNodes(); ++i_node) {
+                Array array         = node_array[i_node];
+                const size_t number = node_number[i_node];
+                const size_t owner  = node_owner[i_node];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-        REQUIRE(edge_array.connectivity_ptr() == weak_edge_array.connectivity_ptr());
+              REQUIRE(is_synchronized);
+            }
+          }
 
-        {   // before synchronization
-          auto edge_owner    = connectivity.edgeOwner();
-          auto edge_is_owned = connectivity.edgeIsOwned();
+          SECTION("edge")
+          {
+            WeakEdgeArray<size_t> weak_edge_array{connectivity, 4};
+            auto edge_number = connectivity.edgeNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (EdgeId i_edge = 0; i_edge < mesh_3d.numberOfEdges(); ++i_edge) {
-            Array array         = edge_array[i_edge];
-            const size_t number = edge_number[i_edge];
-            const size_t owner  = edge_owner[i_edge];
-            if (edge_is_owned[i_edge]) {
+            for (EdgeId i_edge = 0; i_edge < mesh_3d->numberOfEdges(); ++i_edge) {
+              Array array         = weak_edge_array[i_edge];
+              const size_t number = edge_number[i_edge];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
-              }
-            } else {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
             }
-          }
 
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
-
-        synchronize(weak_edge_array);
-
-        {   // after synchronization
-          auto edge_owner = connectivity.edgeOwner();
+            EdgeArray<const size_t> edge_array{weak_edge_array};
+
+            REQUIRE(edge_array.connectivity_ptr() == weak_edge_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto edge_owner    = connectivity.edgeOwner();
+              auto edge_is_owned = connectivity.edgeIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (EdgeId i_edge = 0; i_edge < mesh_3d->numberOfEdges(); ++i_edge) {
+                Array array         = edge_array[i_edge];
+                const size_t number = edge_number[i_edge];
+                const size_t owner  = edge_owner[i_edge];
+                if (edge_is_owned[i_edge]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
+              }
 
-          bool is_synchronized = true;
-          for (EdgeId i_edge = 0; i_edge < mesh_3d.numberOfEdges(); ++i_edge) {
-            Array array         = edge_array[i_edge];
-            const size_t number = edge_number[i_edge];
-            const size_t owner  = edge_owner[i_edge];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
 
-          REQUIRE(is_synchronized);
-        }
-      }
-
-      SECTION("face")
-      {
-        WeakFaceArray<size_t> weak_face_array{connectivity, 4};
-        auto face_number = connectivity.faceNumber();
+            synchronize(weak_edge_array);
 
-        for (FaceId i_face = 0; i_face < mesh_3d.numberOfFaces(); ++i_face) {
-          Array array         = weak_face_array[i_face];
-          const size_t number = face_number[i_face];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
+            {   // after synchronization
+              auto edge_owner = connectivity.edgeOwner();
 
-        FaceArray<const size_t> face_array{weak_face_array};
+              bool is_synchronized = true;
+              for (EdgeId i_edge = 0; i_edge < mesh_3d->numberOfEdges(); ++i_edge) {
+                Array array         = edge_array[i_edge];
+                const size_t number = edge_number[i_edge];
+                const size_t owner  = edge_owner[i_edge];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-        REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+              REQUIRE(is_synchronized);
+            }
+          }
 
-        {   // before synchronization
-          auto face_owner    = connectivity.faceOwner();
-          auto face_is_owned = connectivity.faceIsOwned();
+          SECTION("face")
+          {
+            WeakFaceArray<size_t> weak_face_array{connectivity, 4};
+            auto face_number = connectivity.faceNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (FaceId i_face = 0; i_face < mesh_3d.numberOfFaces(); ++i_face) {
-            Array array         = face_array[i_face];
-            const size_t number = face_number[i_face];
-            const size_t owner  = face_owner[i_face];
-            if (face_is_owned[i_face]) {
+            for (FaceId i_face = 0; i_face < mesh_3d->numberOfFaces(); ++i_face) {
+              Array array         = weak_face_array[i_face];
+              const size_t number = face_number[i_face];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
-              }
-            } else {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
             }
-          }
 
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
-
-        synchronize(weak_face_array);
-
-        {   // after synchronization
-          auto face_owner = connectivity.faceOwner();
+            FaceArray<const size_t> face_array{weak_face_array};
+
+            REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto face_owner    = connectivity.faceOwner();
+              auto face_is_owned = connectivity.faceIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (FaceId i_face = 0; i_face < mesh_3d->numberOfFaces(); ++i_face) {
+                Array array         = face_array[i_face];
+                const size_t number = face_number[i_face];
+                const size_t owner  = face_owner[i_face];
+                if (face_is_owned[i_face]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
+              }
 
-          bool is_synchronized = true;
-          for (FaceId i_face = 0; i_face < mesh_3d.numberOfFaces(); ++i_face) {
-            Array array         = face_array[i_face];
-            const size_t number = face_number[i_face];
-            const size_t owner  = face_owner[i_face];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
 
-          REQUIRE(is_synchronized);
-        }
-      }
+            synchronize(weak_face_array);
 
-      SECTION("cell")
-      {
-        WeakCellArray<size_t> weak_cell_array{connectivity, 4};
-        auto cell_number = connectivity.cellNumber();
+            {   // after synchronization
+              auto face_owner = connectivity.faceOwner();
 
-        for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
-          Array array         = weak_cell_array[i_cell];
-          const size_t number = cell_number[i_cell];
-          for (size_t i = 0; i < array.size(); ++i) {
-            array[i] = number + (parallel::rank() + 1) * i;
-          }
-        }
-
-        CellArray<const size_t> cell_array{weak_cell_array};
+              bool is_synchronized = true;
+              for (FaceId i_face = 0; i_face < mesh_3d->numberOfFaces(); ++i_face) {
+                Array array         = face_array[i_face];
+                const size_t number = face_number[i_face];
+                const size_t owner  = face_owner[i_face];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-        REQUIRE(cell_array.connectivity_ptr() == weak_cell_array.connectivity_ptr());
+              REQUIRE(is_synchronized);
+            }
+          }
 
-        {   // before synchronization
-          auto cell_owner    = connectivity.cellOwner();
-          auto cell_is_owned = connectivity.cellIsOwned();
+          SECTION("cell")
+          {
+            WeakCellArray<size_t> weak_cell_array{connectivity, 4};
+            auto cell_number = connectivity.cellNumber();
 
-          bool is_synchronized = (parallel::size() > 1);
-          bool is_valid        = true;
-          for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
-            Array array         = cell_array[i_cell];
-            const size_t number = cell_number[i_cell];
-            const size_t owner  = cell_owner[i_cell];
-            if (cell_is_owned[i_cell]) {
+            for (CellId i_cell = 0; i_cell < mesh_3d->numberOfCells(); ++i_cell) {
+              Array array         = weak_cell_array[i_cell];
+              const size_t number = cell_number[i_cell];
               for (size_t i = 0; i < array.size(); ++i) {
-                is_valid &= (array[i] == number + (owner + 1) * i);
+                array[i] = number + (parallel::rank() + 1) * i;
               }
-            } else {
-              for (size_t i = 0; i < array.size(); ++i) {
-                is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+
+            CellArray<const size_t> cell_array{weak_cell_array};
+
+            REQUIRE(cell_array.connectivity_ptr() == weak_cell_array.connectivity_ptr());
+
+            {   // before synchronization
+              auto cell_owner    = connectivity.cellOwner();
+              auto cell_is_owned = connectivity.cellIsOwned();
+
+              bool is_synchronized = (parallel::size() > 1);
+              bool is_valid        = true;
+              for (CellId i_cell = 0; i_cell < mesh_3d->numberOfCells(); ++i_cell) {
+                Array array         = cell_array[i_cell];
+                const size_t number = cell_number[i_cell];
+                const size_t owner  = cell_owner[i_cell];
+                if (cell_is_owned[i_cell]) {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_valid &= (array[i] == number + (owner + 1) * i);
+                  }
+                } else {
+                  for (size_t i = 0; i < array.size(); ++i) {
+                    is_synchronized &= (array[i] == number + (owner + 1) * i);
+                  }
+                }
               }
+
+              REQUIRE(is_valid);
+              REQUIRE(not is_synchronized);
             }
-          }
 
-          REQUIRE(is_valid);
-          REQUIRE(not is_synchronized);
-        }
+            synchronize(weak_cell_array);
 
-        synchronize(weak_cell_array);
+            {   // after synchronization
+              auto cell_owner = connectivity.cellOwner();
 
-        {   // after synchronization
-          auto cell_owner = connectivity.cellOwner();
+              bool is_synchronized = true;
+              for (CellId i_cell = 0; i_cell < mesh_3d->numberOfCells(); ++i_cell) {
+                Array array         = cell_array[i_cell];
+                const size_t number = cell_number[i_cell];
+                const size_t owner  = cell_owner[i_cell];
+                for (size_t i = 0; i < array.size(); ++i) {
+                  is_synchronized &= (array[i] == number + (owner + 1) * i);
+                }
+              }
 
-          bool is_synchronized = true;
-          for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
-            Array array         = cell_array[i_cell];
-            const size_t number = cell_number[i_cell];
-            const size_t owner  = cell_owner[i_cell];
-            for (size_t i = 0; i < array.size(); ++i) {
-              is_synchronized &= (array[i] == number + (owner + 1) * i);
+              REQUIRE(is_synchronized);
             }
           }
-
-          REQUIRE(is_synchronized);
         }
       }
     }
diff --git a/tests/test_ItemValue.cpp b/tests/test_ItemValue.cpp
index 1c4ab49faf757cc0b2e871f9bf4e0ba33061ece7..84778227aa28e7a23267f9ec355822d257718c89 100644
--- a/tests/test_ItemValue.cpp
+++ b/tests/test_ItemValue.cpp
@@ -26,119 +26,167 @@ TEST_CASE("ItemValue", "[mesh]")
 
   SECTION("1D")
   {
-    const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
-    const Connectivity<1>& connectivity  = mesh_1d.connectivity();
-
-    REQUIRE_NOTHROW(NodeValue<int>{connectivity});
-    REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
-    REQUIRE_NOTHROW(FaceValue<int>{connectivity});
-    REQUIRE_NOTHROW(CellValue<int>{connectivity});
-
-    REQUIRE(NodeValue<int>{connectivity}.isBuilt());
-    REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
-    REQUIRE(FaceValue<int>{connectivity}.isBuilt());
-    REQUIRE(CellValue<int>{connectivity}.isBuilt());
-
-    NodeValue<int> node_value{connectivity};
-    EdgeValue<int> edge_value{connectivity};
-    FaceValue<int> face_value{connectivity};
-    CellValue<int> cell_value{connectivity};
-
-    REQUIRE(edge_value.numberOfItems() == node_value.numberOfItems());
-    REQUIRE(face_value.numberOfItems() == node_value.numberOfItems());
-    REQUIRE(cell_value.numberOfItems() + 1 == node_value.numberOfItems());
+    std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_1d = named_mesh.mesh();
+
+        const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+        REQUIRE_NOTHROW(NodeValue<int>{connectivity});
+        REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
+        REQUIRE_NOTHROW(FaceValue<int>{connectivity});
+        REQUIRE_NOTHROW(CellValue<int>{connectivity});
+
+        REQUIRE(NodeValue<int>{connectivity}.isBuilt());
+        REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
+        REQUIRE(FaceValue<int>{connectivity}.isBuilt());
+        REQUIRE(CellValue<int>{connectivity}.isBuilt());
+
+        NodeValue<int> node_value{connectivity};
+        EdgeValue<int> edge_value{connectivity};
+        FaceValue<int> face_value{connectivity};
+        CellValue<int> cell_value{connectivity};
+
+        REQUIRE(edge_value.numberOfItems() == node_value.numberOfItems());
+        REQUIRE(face_value.numberOfItems() == node_value.numberOfItems());
+        REQUIRE(cell_value.numberOfItems() + 1 == node_value.numberOfItems());
+      }
+    }
   }
 
   SECTION("2D")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
 
-    REQUIRE_NOTHROW(NodeValue<int>{connectivity});
-    REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
-    REQUIRE_NOTHROW(FaceValue<int>{connectivity});
-    REQUIRE_NOTHROW(CellValue<int>{connectivity});
+        const Connectivity<2>& connectivity = mesh_2d->connectivity();
 
-    REQUIRE(NodeValue<int>{connectivity}.isBuilt());
-    REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
-    REQUIRE(FaceValue<int>{connectivity}.isBuilt());
-    REQUIRE(CellValue<int>{connectivity}.isBuilt());
+        REQUIRE_NOTHROW(NodeValue<int>{connectivity});
+        REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
+        REQUIRE_NOTHROW(FaceValue<int>{connectivity});
+        REQUIRE_NOTHROW(CellValue<int>{connectivity});
 
-    EdgeValue<int> edge_value{connectivity};
-    FaceValue<int> face_value{connectivity};
+        REQUIRE(NodeValue<int>{connectivity}.isBuilt());
+        REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
+        REQUIRE(FaceValue<int>{connectivity}.isBuilt());
+        REQUIRE(CellValue<int>{connectivity}.isBuilt());
 
-    REQUIRE(edge_value.numberOfItems() == face_value.numberOfItems());
+        EdgeValue<int> edge_value{connectivity};
+        FaceValue<int> face_value{connectivity};
+
+        REQUIRE(edge_value.numberOfItems() == face_value.numberOfItems());
+      }
+    }
   }
 
   SECTION("3D")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
-
-    REQUIRE_NOTHROW(NodeValue<int>{connectivity});
-    REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
-    REQUIRE_NOTHROW(FaceValue<int>{connectivity});
-    REQUIRE_NOTHROW(CellValue<int>{connectivity});
-
-    REQUIRE(NodeValue<int>{connectivity}.isBuilt());
-    REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
-    REQUIRE(FaceValue<int>{connectivity}.isBuilt());
-    REQUIRE(CellValue<int>{connectivity}.isBuilt());
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
+
+        const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+        REQUIRE_NOTHROW(NodeValue<int>{connectivity});
+        REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
+        REQUIRE_NOTHROW(FaceValue<int>{connectivity});
+        REQUIRE_NOTHROW(CellValue<int>{connectivity});
+
+        REQUIRE(NodeValue<int>{connectivity}.isBuilt());
+        REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
+        REQUIRE(FaceValue<int>{connectivity}.isBuilt());
+        REQUIRE(CellValue<int>{connectivity}.isBuilt());
+      }
+    }
   }
 
   SECTION("set values from array")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    CellValue<size_t> cell_value{connectivity};
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
 
-    Array<size_t> values{cell_value.numberOfItems()};
-    for (size_t i = 0; i < values.size(); ++i) {
-      values[i] = i;
-    }
+        const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-    cell_value = values;
+        CellValue<size_t> cell_value{connectivity};
 
-    for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
-      REQUIRE(cell_value[i_cell] == i_cell);
+        Array<size_t> values{cell_value.numberOfItems()};
+        for (size_t i = 0; i < values.size(); ++i) {
+          values[i] = i;
+        }
+
+        cell_value = values;
+
+        for (CellId i_cell = 0; i_cell < mesh_3d->numberOfCells(); ++i_cell) {
+          REQUIRE(cell_value[i_cell] == i_cell);
+        }
+      }
     }
   }
 
   SECTION("copy")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    CellValue<int> cell_value{connectivity};
-    cell_value.fill(parallel::rank());
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
 
-    CellValue<const int> const_cell_value;
-    const_cell_value = copy(cell_value);
+        const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-    cell_value.fill(0);
+        CellValue<int> cell_value{connectivity};
+        cell_value.fill(parallel::rank());
 
-    for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
-      REQUIRE(cell_value[i_cell] == 0);
-    }
+        CellValue<const int> const_cell_value;
+        const_cell_value = copy(cell_value);
 
-    for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
-      REQUIRE(const_cell_value[i_cell] == static_cast<std::int64_t>(parallel::rank()));
+        cell_value.fill(0);
+
+        for (CellId i_cell = 0; i_cell < mesh_3d->numberOfCells(); ++i_cell) {
+          REQUIRE(cell_value[i_cell] == 0);
+        }
+
+        for (CellId i_cell = 0; i_cell < mesh_3d->numberOfCells(); ++i_cell) {
+          REQUIRE(const_cell_value[i_cell] == static_cast<std::int64_t>(parallel::rank()));
+        }
+      }
     }
   }
 
   SECTION("WeakItemValue")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
+
+        const Connectivity<2>& connectivity = mesh_2d->connectivity();
 
-    WeakFaceValue<int> weak_face_value{connectivity};
+        WeakFaceValue<int> weak_face_value{connectivity};
 
-    weak_face_value.fill(parallel::rank());
+        weak_face_value.fill(parallel::rank());
 
-    FaceValue<const int> face_value{weak_face_value};
+        FaceValue<const int> face_value{weak_face_value};
 
-    REQUIRE(face_value.connectivity_ptr() == weak_face_value.connectivity_ptr());
+        REQUIRE(face_value.connectivity_ptr() == weak_face_value.connectivity_ptr());
+      }
+    }
   }
 
 #ifndef NDEBUG
@@ -161,48 +209,80 @@ TEST_CASE("ItemValue", "[mesh]")
 
     SECTION("checking for bounds violation")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-      CellValue<int> cell_value{connectivity};
-      CellId invalid_cell_id = connectivity.numberOfCells();
-      REQUIRE_THROWS_AS(cell_value[invalid_cell_id], AssertError);
+          CellValue<int> cell_value{connectivity};
+          CellId invalid_cell_id = connectivity.numberOfCells();
+          REQUIRE_THROWS_AS(cell_value[invalid_cell_id], AssertError);
 
-      FaceValue<int> face_value{connectivity};
-      FaceId invalid_face_id = connectivity.numberOfFaces();
-      REQUIRE_THROWS_AS(face_value[invalid_face_id], AssertError);
+          FaceValue<int> face_value{connectivity};
+          FaceId invalid_face_id = connectivity.numberOfFaces();
+          REQUIRE_THROWS_AS(face_value[invalid_face_id], AssertError);
 
-      EdgeValue<int> edge_value{connectivity};
-      EdgeId invalid_edge_id = connectivity.numberOfEdges();
-      REQUIRE_THROWS_AS(edge_value[invalid_edge_id], AssertError);
+          EdgeValue<int> edge_value{connectivity};
+          EdgeId invalid_edge_id = connectivity.numberOfEdges();
+          REQUIRE_THROWS_AS(edge_value[invalid_edge_id], AssertError);
 
-      NodeValue<int> node_value{connectivity};
-      NodeId invalid_node_id = connectivity.numberOfNodes();
-      REQUIRE_THROWS_AS(node_value[invalid_node_id], AssertError);
+          NodeValue<int> node_value{connectivity};
+          NodeId invalid_node_id = connectivity.numberOfNodes();
+          REQUIRE_THROWS_AS(node_value[invalid_node_id], AssertError);
+        }
+      }
     }
 
     SECTION("set values from invalid array size")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      CellValue<size_t> cell_value{connectivity};
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
 
-      Array<size_t> values{3 + cell_value.numberOfItems()};
-      REQUIRE_THROWS_AS(cell_value = values, AssertError);
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          CellValue<size_t> cell_value{connectivity};
+
+          Array<size_t> values{3 + cell_value.numberOfItems()};
+          REQUIRE_THROWS_AS(cell_value = values, AssertError);
+        }
+      }
     }
 
     SECTION("invalid copy_to")
     {
-      const Mesh<Connectivity<2>>& mesh_2d   = *MeshDataBaseForTests::get().cartesianMesh2D();
-      const Connectivity<2>& connectivity_2d = mesh_2d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity_2d = mesh_2d->connectivity();
+
+          std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+          for (auto named_mesh : mesh_list) {
+            SECTION(named_mesh.name())
+            {
+              auto mesh_3d = named_mesh.mesh();
 
-      const Mesh<Connectivity<3>>& mesh_3d   = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity_3d = mesh_3d.connectivity();
+              const Connectivity<3>& connectivity_3d = mesh_3d->connectivity();
 
-      CellValue<int> cell_2d_value{connectivity_2d};
-      CellValue<int> cell_3d_value{connectivity_3d};
-      REQUIRE_THROWS_AS(copy_to(cell_2d_value, cell_3d_value), AssertError);
+              CellValue<int> cell_2d_value{connectivity_2d};
+              CellValue<int> cell_3d_value{connectivity_3d};
+              REQUIRE_THROWS_AS(copy_to(cell_2d_value, cell_3d_value), AssertError);
+            }
+          }
+        }
+      }
     }
   }
 #endif   // NDEBUG
diff --git a/tests/test_ItemValueUtils.cpp b/tests/test_ItemValueUtils.cpp
index b77dfecd58fe2aa2ee2addcc483f631f3e3ec62f..bee143e751de72affa1134998d8561a997bc7f12 100644
--- a/tests/test_ItemValueUtils.cpp
+++ b/tests/test_ItemValueUtils.cpp
@@ -17,38 +17,46 @@ TEST_CASE("ItemValueUtils", "[mesh]")
 {
   SECTION("Synchronize")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    WeakFaceValue<int> weak_face_value{connectivity};
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
 
-    weak_face_value.fill(parallel::rank());
+        const Connectivity<2>& connectivity = mesh_2d->connectivity();
 
-    FaceValue<const int> face_value{weak_face_value};
+        WeakFaceValue<int> weak_face_value{connectivity};
 
-    REQUIRE(face_value.connectivity_ptr() == weak_face_value.connectivity_ptr());
+        weak_face_value.fill(parallel::rank());
 
-    {   // before synchronization
-      auto face_owner    = connectivity.faceOwner();
-      auto face_is_owned = connectivity.faceIsOwned();
+        FaceValue<const int> face_value{weak_face_value};
 
-      for (FaceId i_face = 0; i_face < mesh_2d.numberOfFaces(); ++i_face) {
-        if (face_is_owned[i_face]) {
-          REQUIRE(face_owner[i_face] == face_value[i_face]);
-        } else {
-          REQUIRE(face_owner[i_face] != face_value[i_face]);
+        REQUIRE(face_value.connectivity_ptr() == weak_face_value.connectivity_ptr());
+
+        {   // before synchronization
+          auto face_owner    = connectivity.faceOwner();
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          for (FaceId i_face = 0; i_face < mesh_2d->numberOfFaces(); ++i_face) {
+            if (face_is_owned[i_face]) {
+              REQUIRE(face_owner[i_face] == face_value[i_face]);
+            } else {
+              REQUIRE(face_owner[i_face] != face_value[i_face]);
+            }
+          }
         }
-      }
-    }
 
-    synchronize(weak_face_value);
+        synchronize(weak_face_value);
 
-    {   // after synchronization
-      auto face_owner    = connectivity.faceOwner();
-      auto face_is_owned = connectivity.faceIsOwned();
+        {   // after synchronization
+          auto face_owner    = connectivity.faceOwner();
+          auto face_is_owned = connectivity.faceIsOwned();
 
-      for (FaceId i_face = 0; i_face < mesh_2d.numberOfFaces(); ++i_face) {
-        REQUIRE(face_owner[i_face] == face_value[i_face]);
+          for (FaceId i_face = 0; i_face < mesh_2d->numberOfFaces(); ++i_face) {
+            REQUIRE(face_owner[i_face] == face_value[i_face]);
+          }
+        }
       }
     }
   }
@@ -57,59 +65,83 @@ TEST_CASE("ItemValueUtils", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
-      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      CellValue<int> cell_value{connectivity};
-      cell_value.fill(-1);
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
 
-      auto cell_is_owned = connectivity.cellIsOwned();
-      parallel_for(
-        mesh_1d.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-          if (cell_is_owned[cell_id]) {
-            cell_value[cell_id] = 10 + parallel::rank();
-          }
-        });
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          CellValue<int> cell_value{connectivity};
+          cell_value.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_1d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                cell_value[cell_id] = 10 + parallel::rank();
+              }
+            });
 
-      REQUIRE(min(cell_value) == 10);
+          REQUIRE(min(cell_value) == 10);
+        }
+      }
     }
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      CellValue<int> cell_value{connectivity};
-      cell_value.fill(-1);
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
 
-      auto cell_is_owned = connectivity.cellIsOwned();
-      parallel_for(
-        mesh_2d.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-          if (cell_is_owned[cell_id]) {
-            cell_value[cell_id] = 10 + parallel::rank();
-          }
-        });
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
 
-      REQUIRE(min(cell_value) == 10);
+          CellValue<int> cell_value{connectivity};
+          cell_value.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_2d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                cell_value[cell_id] = 10 + parallel::rank();
+              }
+            });
+
+          REQUIRE(min(cell_value) == 10);
+        }
+      }
     }
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      CellValue<int> cell_value{connectivity};
-      cell_value.fill(-1);
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
 
-      auto cell_is_owned = connectivity.cellIsOwned();
-      parallel_for(
-        mesh_3d.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-          if (cell_is_owned[cell_id]) {
-            cell_value[cell_id] = 10 + parallel::rank();
-          }
-        });
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          CellValue<int> cell_value{connectivity};
+          cell_value.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_3d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                cell_value[cell_id] = 10 + parallel::rank();
+              }
+            });
 
-      REQUIRE(min(cell_value) == 10);
+          REQUIRE(min(cell_value) == 10);
+        }
+      }
     }
   }
 
@@ -117,59 +149,83 @@ TEST_CASE("ItemValueUtils", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
-      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      CellValue<size_t> cell_value{connectivity};
-      cell_value.fill(std::numeric_limits<size_t>::max());
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
 
-      auto cell_is_owned = connectivity.cellIsOwned();
-      parallel_for(
-        mesh_1d.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-          if (cell_is_owned[cell_id]) {
-            cell_value[cell_id] = parallel::rank() + 1;
-          }
-        });
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
 
-      REQUIRE(max(cell_value) == parallel::size());
+          CellValue<size_t> cell_value{connectivity};
+          cell_value.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_1d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                cell_value[cell_id] = parallel::rank() + 1;
+              }
+            });
+
+          REQUIRE(max(cell_value) == parallel::size());
+        }
+      }
     }
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      CellValue<size_t> cell_value{connectivity};
-      cell_value.fill(std::numeric_limits<size_t>::max());
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
 
-      auto cell_is_owned = connectivity.cellIsOwned();
-      parallel_for(
-        mesh_2d.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-          if (cell_is_owned[cell_id]) {
-            cell_value[cell_id] = parallel::rank() + 1;
-          }
-        });
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          CellValue<size_t> cell_value{connectivity};
+          cell_value.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_2d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                cell_value[cell_id] = parallel::rank() + 1;
+              }
+            });
 
-      REQUIRE(max(cell_value) == parallel::size());
+          REQUIRE(max(cell_value) == parallel::size());
+        }
+      }
     }
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      CellValue<size_t> cell_value{connectivity};
-      cell_value.fill(std::numeric_limits<size_t>::max());
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
 
-      auto cell_is_owned = connectivity.cellIsOwned();
-      parallel_for(
-        mesh_3d.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-          if (cell_is_owned[cell_id]) {
-            cell_value[cell_id] = parallel::rank() + 1;
-          }
-        });
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-      REQUIRE(max(cell_value) == parallel::size());
+          CellValue<size_t> cell_value{connectivity};
+          cell_value.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_3d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                cell_value[cell_id] = parallel::rank() + 1;
+              }
+            });
+
+          REQUIRE(max(cell_value) == parallel::size());
+        }
+      }
     }
   }
 
@@ -177,65 +233,89 @@ TEST_CASE("ItemValueUtils", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
-      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      CellValue<size_t> cell_value{connectivity};
-      cell_value.fill(5);
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
 
-      auto cell_is_owned = connectivity.cellIsOwned();
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
 
-      const size_t global_number_of_cells = [&] {
-        size_t number_of_cells = 0;
-        for (CellId cell_id = 0; cell_id < cell_is_owned.numberOfItems(); ++cell_id) {
-          number_of_cells += cell_is_owned[cell_id];
-        }
-        return parallel::allReduceSum(number_of_cells);
-      }();
+          CellValue<size_t> cell_value{connectivity};
+          cell_value.fill(5);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
 
-      REQUIRE(sum(cell_value) == 5 * global_number_of_cells);
+          const size_t global_number_of_cells = [&] {
+            size_t number_of_cells = 0;
+            for (CellId cell_id = 0; cell_id < cell_is_owned.numberOfItems(); ++cell_id) {
+              number_of_cells += cell_is_owned[cell_id];
+            }
+            return parallel::allReduceSum(number_of_cells);
+          }();
+
+          REQUIRE(sum(cell_value) == 5 * global_number_of_cells);
+        }
+      }
     }
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      FaceValue<size_t> face_value{connectivity};
-      face_value.fill(2);
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
 
-      auto face_is_owned = connectivity.faceIsOwned();
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
 
-      const size_t global_number_of_faces = [&] {
-        size_t number_of_faces = 0;
-        for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
-          number_of_faces += face_is_owned[face_id];
-        }
-        return parallel::allReduceSum(number_of_faces);
-      }();
+          FaceValue<size_t> face_value{connectivity};
+          face_value.fill(2);
 
-      REQUIRE(sum(face_value) == 2 * global_number_of_faces);
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_faces = [&] {
+            size_t number_of_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_faces += face_is_owned[face_id];
+            }
+            return parallel::allReduceSum(number_of_faces);
+          }();
+
+          REQUIRE(sum(face_value) == 2 * global_number_of_faces);
+        }
+      }
     }
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      NodeValue<size_t> node_value{connectivity};
-      node_value.fill(3);
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
 
-      auto node_is_owned = connectivity.nodeIsOwned();
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-      const size_t global_number_of_nodes = [&] {
-        size_t number_of_nodes = 0;
-        for (NodeId node_id = 0; node_id < node_is_owned.numberOfItems(); ++node_id) {
-          number_of_nodes += node_is_owned[node_id];
-        }
-        return parallel::allReduceSum(number_of_nodes);
-      }();
+          NodeValue<size_t> node_value{connectivity};
+          node_value.fill(3);
+
+          auto node_is_owned = connectivity.nodeIsOwned();
 
-      REQUIRE(sum(node_value) == 3 * global_number_of_nodes);
+          const size_t global_number_of_nodes = [&] {
+            size_t number_of_nodes = 0;
+            for (NodeId node_id = 0; node_id < node_is_owned.numberOfItems(); ++node_id) {
+              number_of_nodes += node_is_owned[node_id];
+            }
+            return parallel::allReduceSum(number_of_nodes);
+          }();
+
+          REQUIRE(sum(node_value) == 3 * global_number_of_nodes);
+        }
+      }
     }
   }
 }
diff --git a/tests/test_LineTransformation.cpp b/tests/test_LineTransformation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..908d2d693973d13d48288cda7f7d021ee187c7a7
--- /dev/null
+++ b/tests/test_LineTransformation.cpp
@@ -0,0 +1,118 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/LineTransformation.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("LineTransformation", "[geometry]")
+{
+  SECTION("1D")
+  {
+    using R1 = TinyVector<1>;
+
+    const R1 a = 0.3;
+    const R1 b = 2.7;
+
+    const LineTransformation<1> t(a, b);
+
+    REQUIRE(t(-1)[0] == Catch::Approx(0.3));
+    REQUIRE(t(0)[0] == Catch::Approx(1.5));
+    REQUIRE(t(1)[0] == Catch::Approx(2.7));
+
+    REQUIRE(t.jacobianDeterminant() == Catch::Approx(1.2));
+
+    auto p = [](const R1& X) {
+      const double x = X[0];
+      return 2 * x * x + 3 * x + 2;
+    };
+
+    QuadratureFormula<1> qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor(2));
+
+    double sum = 0;
+    for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+      sum += qf.weight(i) * t.jacobianDeterminant() * p(t(qf.point(i)));
+    }
+
+    auto P = [](const R1& X) {
+      const double x = X[0];
+      return 2. / 3 * x * x * x + 3. / 2 * x * x + 2 * x;
+    };
+
+    REQUIRE(sum == Catch::Approx(P(b) - P(a)));
+  }
+
+  SECTION("2D")
+  {
+    using R2 = TinyVector<2>;
+
+    const R2 a = {0.3, 1.2};
+    const R2 b = {2.7, 0.7};
+
+    const LineTransformation<2> t(a, b);
+
+    REQUIRE(t(-1)[0] == Catch::Approx(0.3));
+    REQUIRE(t(-1)[1] == Catch::Approx(1.2));
+    REQUIRE(t(0)[0] == Catch::Approx(1.5));
+    REQUIRE(t(0)[1] == Catch::Approx(0.95));
+    REQUIRE(t(1)[0] == Catch::Approx(2.7));
+    REQUIRE(t(1)[1] == Catch::Approx(0.7));
+
+    REQUIRE(t.velocityNorm() == Catch::Approx(l2Norm(0.5 * (b - a))));
+
+    auto p = [](const R2& X) {
+      const double x = X[0];
+      const double y = X[1];
+      return 2 * x * x + 3 * x - 3 * y * y + y + 2;
+    };
+
+    QuadratureFormula<1> qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor(2));
+
+    double sum = 0;
+    for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+      sum += qf.weight(i) * t.velocityNorm() * p(t(qf.point(i)));
+    }
+
+    REQUIRE(sum == Catch::Approx(24.8585155630822));
+  }
+
+  SECTION("3D")
+  {
+    using R3 = TinyVector<3>;
+
+    const R3 a = {0.3, 1.2, -1};
+    const R3 b = {2.7, 0.7, 0.3};
+
+    const LineTransformation<3> t(a, b);
+
+    REQUIRE(t(-1)[0] == Catch::Approx(0.3));
+    REQUIRE(t(-1)[1] == Catch::Approx(1.2));
+    REQUIRE(t(-1)[2] == Catch::Approx(-1.));
+    REQUIRE(t(0)[0] == Catch::Approx(1.5));
+    REQUIRE(t(0)[1] == Catch::Approx(0.95));
+    REQUIRE(t(0)[2] == Catch::Approx(-0.35));
+    REQUIRE(t(1)[0] == Catch::Approx(2.7));
+    REQUIRE(t(1)[1] == Catch::Approx(0.7));
+    REQUIRE(t(1)[2] == Catch::Approx(0.3));
+
+    REQUIRE(t.velocityNorm() == Catch::Approx(l2Norm(0.5 * (b - a))));
+
+    auto p = [](const R3& X) {
+      const double x = X[0];
+      const double y = X[1];
+      const double z = X[2];
+      return 2 * x * x + 3 * x - 3 * y * y + y + 2 * z * z - 0.5 * z + 2;
+    };
+
+    QuadratureFormula<1> qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor(2));
+
+    double sum = 0;
+    for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+      sum += qf.weight(i) * t.velocityNorm() * p(t(qf.point(i)));
+    }
+
+    REQUIRE(sum == Catch::Approx(30.08440406681767));
+  }
+}
diff --git a/tests/test_OFStream.cpp b/tests/test_OFStream.cpp
index 5ca9c38fd4b8f01f6ed28a263d9199905481edf5..edcee297572862e12d62a8a5032757ea84b364e2 100644
--- a/tests/test_OFStream.cpp
+++ b/tests/test_OFStream.cpp
@@ -12,7 +12,7 @@ TEST_CASE("OFStream", "[language]")
 {
   SECTION("ofstream")
   {
-    const std::string basename = std::filesystem::temp_directory_path().append("ofstream_");
+    const std::string basename = std::filesystem::path{PUGS_BINARY_DIR}.append("tests").append("ofstream_");
     const std::string filename = basename + std::to_string(parallel::rank());
 
     // Ensures that the file is closed after this line
diff --git a/tests/test_PrismGaussQuadrature.cpp b/tests/test_PrismGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ceff287e5ac5c823c9f427ba6801babfeaa47481
--- /dev/null
+++ b/tests/test_PrismGaussQuadrature.cpp
@@ -0,0 +1,686 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/PrismGaussQuadrature.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/TetrahedronTransformation.hpp>
+#include <utils/Exceptions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("PrismGaussQuadrature", "[analysis]")
+{
+  auto integrate = [](auto f, auto quadrature_formula) {
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    auto value = weight_list[0] * f(point_list[0]);
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(point_list[i]);
+    }
+
+    return value;
+  };
+
+  auto integrate_on_prism = [](auto f, auto quadrature_formula, const std::array<TinyVector<3>, 4>& tetrahedron) {
+    const auto& A = tetrahedron[0];
+    const auto& B = tetrahedron[1];
+    const auto& C = tetrahedron[2];
+    const auto& D = tetrahedron[3];
+
+    TinyMatrix<3> J;
+    for (size_t i = 0; i < 3; ++i) {
+      J(i, 0) = B[i] - A[i];
+      J(i, 1) = C[i] - A[i];
+      J(i, 2) = 0.5 * (D[i] - A[i]);
+    }
+    TinyVector s = 0.5 * (A + D);
+
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+    auto value       = weight_list[0] * f(J * (point_list[0]) + s);
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(J * (point_list[i]) + s);
+    }
+
+    return det(J) * value;
+  };
+
+  auto get_order = [&integrate, &integrate_on_prism](auto f, auto quadrature_formula, const double exact_value) {
+    using R3 = TinyVector<3>;
+
+    const R3 A{+0, +0, -1};
+    const R3 B{+1, +0, -1};
+    const R3 C{+0, +1, -1};
+    const R3 D{+0, +0, +1};
+    const R3 E{+1, +0, +1};
+    const R3 F{+0, +1, +1};
+
+    const R3 M_AB   = 0.5 * (A + B);
+    const R3 M_AC   = 0.5 * (A + C);
+    const R3 M_BC   = 0.5 * (B + C);
+    const R3 M_DE   = 0.5 * (D + E);
+    const R3 M_AD   = 0.5 * (A + D);
+    const R3 M_BE   = 0.5 * (B + E);
+    const R3 M_CF   = 0.5 * (C + F);
+    const R3 M_ABED = 0.25 * (A + B + E + D);
+    const R3 M_BCFE = 0.25 * (B + C + E + F);
+    const R3 M_ADFC = 0.25 * (A + D + F + C);
+
+    const double int_T_hat = integrate(f, quadrature_formula);
+    const double int_refined   //
+      = integrate_on_prism(f, quadrature_formula, {A, M_AB, M_AC, M_AD}) +
+        integrate_on_prism(f, quadrature_formula, {B, M_BC, M_AB, M_BE}) +
+        integrate_on_prism(f, quadrature_formula, {C, M_AC, M_BC, M_CF}) +
+        integrate_on_prism(f, quadrature_formula, {M_AB, M_BC, M_AC, M_ABED}) +
+        integrate_on_prism(f, quadrature_formula, {M_AD, M_ABED, M_ADFC, D}) +
+        integrate_on_prism(f, quadrature_formula, {M_BE, M_BCFE, M_ABED, E}) +
+        integrate_on_prism(f, quadrature_formula, {M_CF, M_ADFC, M_BCFE, F}) +
+        integrate_on_prism(f, quadrature_formula, {M_ABED, M_BCFE, M_ADFC, M_DE});
+
+    return -std::log(std::abs(int_refined - exact_value) / std::abs(int_T_hat - exact_value)) / std::log(2);
+  };
+
+  auto p0 = [](const TinyVector<3>&) { return 4; };
+  auto p1 = [](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return 2 * x + 3 * y + z - 1;
+  };
+  auto p2 = [&p1](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p1(X) * (2.5 * x - 3 * y + z + 3);
+  };
+  auto p3 = [&p2](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p2(X) * (3 * x + 2 * y - 3 * z - 1);
+  };
+  auto p4 = [&p3](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p3(X) * (2 * x - 0.5 * y - 1.3 * z + 1);
+  };
+  auto p5 = [&p4](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p4(X) * (-0.1 * x + 1.3 * y - 3 * z + 1);
+  };
+  auto p6 = [&p5](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p5(X) * 7875. / 143443 * (2 * x - y + 4 * z + 1);
+  };
+  auto p7 = [&p6](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p6(X) * (0.7 * x - 2.7 * y + 1.3 * z - 2);
+  };
+  auto p8 = [&p7](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p7(X) * (0.3 * x + 1.2 * y - 0.7 * z + 0.2);
+  };
+  auto p9 = [&p8](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p8(X) * (-0.2 * x + 1.1 * y - 0.5 * z + 0.6);
+  };
+  auto p10 = [&p9](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p9(X) * (0.7 * x - 0.6 * y - 0.7 * z - 0.2);
+  };
+  auto p11 = [&p10](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p10(X) * (-1.3 * x + 0.6 * y - 1.3 * z + 0.7);
+  };
+  auto p12 = [&p11](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p11(X) * (0.3 * x - 0.7 * y + 0.3 * z + 0.7);
+  };
+  auto p13 = [&p12](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p12(X) * (0.9 * x + 0.2 * y - 0.4 * z + 0.5);
+  };
+  auto p14 = [&p13](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p13(X) * (0.6 * x - 1.2 * y + 0.7 * z - 0.4);
+  };
+  auto p15 = [&p14](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p14(X) * (-0.2 * x - 0.7 * y + 0.9 * z + 0.8);
+  };
+  auto p16 = [&p15](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p15(X) * (0.7 * x + 0.2 * y - 0.6 * z + 0.4);
+  };
+  auto p17 = [&p16](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p16(X) * (-0.1 * x + 0.8 * y + 0.3 * z - 0.2);
+  };
+  auto p18 = [&p17](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p17(X) * (0.7 * x - 0.2 * y - 0.3 * z + 0.8);
+  };
+  auto p19 = [&p18](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p18(X) * (-0.7 * x + 1.2 * y + 1.3 * z + 0.8);
+  };
+  auto p20 = [&p19](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p19(X) * (0.7 * x - 1.2 * y + 0.3 * z - 0.6);
+  };
+  auto p21 = [&p20](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p20(X) * (0.7 * x - 1.2 * y + 0.3 * z - 0.6);
+  };
+
+  SECTION("degree 1")
+  {
+    const QuadratureFormula<3>& l1 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(1));
+
+    REQUIRE(l1.numberOfPoints() == 1);
+
+    REQUIRE(integrate(p0, l1) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l1) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l1) != Catch::Approx(47. / 24));
+
+    REQUIRE(get_order(p2, l1, 47. / 24) == Catch::Approx(2));
+  }
+
+  SECTION("degree 2")
+  {
+    const QuadratureFormula<3>& l2 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(2));
+
+    REQUIRE(l2.numberOfPoints() == 5);
+
+    REQUIRE(integrate(p0, l2) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l2) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l2) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l2) != Catch::Approx(-427. / 360));
+
+    // In a weird way, one gets 4th order on this test
+    REQUIRE(get_order(p3, l2, -427. / 360) == Catch::Approx(4));
+  }
+
+  SECTION("degree 3")
+  {
+    const QuadratureFormula<3>& l3 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(3));
+
+    REQUIRE(l3.numberOfPoints() == 8);
+
+    REQUIRE(integrate(p0, l3) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l3) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l3) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l3) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l3) != Catch::Approx(1003. / 3600));
+
+    REQUIRE(get_order(p4, l3, 1003. / 3600) == Catch::Approx(4));
+  }
+
+  SECTION("degree 4")
+  {
+    const QuadratureFormula<3>& l4 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(4));
+
+    REQUIRE(l4.numberOfPoints() == 11);
+
+    REQUIRE(integrate(p0, l4) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l4) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l4) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l4) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l4) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l4) != Catch::Approx(34031. / 18000));
+
+    REQUIRE(get_order(p5, l4, 34031. / 18000) >= Catch::Approx(5));
+  }
+
+  SECTION("degree 5")
+  {
+    const QuadratureFormula<3>& l5 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(5));
+
+    REQUIRE(l5.numberOfPoints() == 16);
+
+    REQUIRE(integrate(p0, l5) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l5) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l5) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l5) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l5) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l5) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l5) != Catch::Approx(36346369. / 55082112));
+
+    REQUIRE(get_order(p6, l5, 36346369. / 55082112) == Catch::Approx(6));
+  }
+
+  SECTION("degree 6")
+  {
+    const QuadratureFormula<3>& l6 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(6));
+
+    REQUIRE(l6.numberOfPoints() == 28);
+
+    REQUIRE(integrate(p0, l6) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l6) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l6) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l6) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l6) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l6) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l6) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l6) != Catch::Approx(-1128861017. / 918035200));
+
+    // In a weird way, one gets 8th order on this test
+    REQUIRE(get_order(p7, l6, -1128861017. / 918035200) == Catch::Approx(8));
+  }
+
+  SECTION("degree 7")
+  {
+    const QuadratureFormula<3>& l7 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(7));
+
+    REQUIRE(l7.numberOfPoints() == 35);
+
+    REQUIRE(integrate(p0, l7) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l7) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l7) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l7) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l7) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l7) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l7) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l7) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l7) != Catch::Approx(-21178319419. / 27541056000));
+
+    REQUIRE(get_order(p8, l7, -21178319419. / 27541056000) == Catch::Approx(8));
+  }
+
+  SECTION("degree 8")
+  {
+    const QuadratureFormula<3>& l8 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(8));
+
+    REQUIRE(l8.numberOfPoints() == 46);
+
+    REQUIRE(integrate(p0, l8) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l8) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l8) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l8) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l8) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l8) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l8) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l8) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l8) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l8) != Catch::Approx(-5483758803191. / 9088548480000));
+
+    // In a weird way, one gets 10th order on this test
+    REQUIRE(get_order(p9, l8, -5483758803191. / 9088548480000) == Catch::Approx(10));
+  }
+
+  SECTION("degree 9")
+  {
+    const QuadratureFormula<3>& l9 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(9));
+
+    REQUIRE(l9.numberOfPoints() == 59);
+
+    REQUIRE(integrate(p0, l9) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l9) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l9) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l9) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l9) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l9) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l9) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l9) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l9) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l9) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l9) != Catch::Approx(-9456848221657. / 22721371200000));
+
+    REQUIRE(get_order(p10, l9, -9456848221657. / 22721371200000) == Catch::Approx(10));
+  }
+
+  SECTION("degree 10")
+  {
+    const QuadratureFormula<3>& l10 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(10));
+
+    REQUIRE(l10.numberOfPoints() == 84);
+
+    REQUIRE(integrate(p0, l10) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l10) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l10) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l10) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l10) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l10) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l10) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l10) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l10) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l10) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l10) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l10) != Catch::Approx(-4571362439539697. / 7518708288000000));
+
+    // In a weird way, one gets 12th order on this test
+    REQUIRE(get_order(p11, l10, -4571362439539697. / 7518708288000000) == Catch::Approx(12));
+  }
+
+  SECTION("degree 11")
+  {
+    const QuadratureFormula<3>& l11 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(11));
+
+    REQUIRE(l11.numberOfPoints() == 99);
+
+    REQUIRE(integrate(p0, l11) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l11) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l11) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l11) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l11) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l11) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l11) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l11) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l11) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l11) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l11) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l11) == Catch::Approx(-4571362439539697. / 7518708288000000));
+    REQUIRE(integrate(p12, l11) != Catch::Approx(-491755535075074133. / 1378429852800000000));
+
+    REQUIRE(get_order(p12, l11, -491755535075074133. / 1378429852800000000) == Catch::Approx(12));
+  }
+
+  SECTION("degree 12")
+  {
+    const QuadratureFormula<3>& l12 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(12));
+
+    REQUIRE(l12.numberOfPoints() == 136);
+
+    REQUIRE(integrate(p0, l12) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l12) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l12) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l12) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l12) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l12) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l12) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l12) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l12) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l12) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l12) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l12) == Catch::Approx(-4571362439539697. / 7518708288000000));
+    REQUIRE(integrate(p12, l12) == Catch::Approx(-491755535075074133. / 1378429852800000000));
+    REQUIRE(integrate(p13, l12) != Catch::Approx(-1620413117251976393. / 4135289558400000000));
+
+    // In a weird way, one gets almost 14th order on this test
+    REQUIRE(get_order(p13, l12, -1620413117251976393. / 4135289558400000000) == Catch::Approx(14).margin(0.01));
+  }
+
+  SECTION("degree 13")
+  {
+    const QuadratureFormula<3>& l13 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(13));
+
+    REQUIRE(l13.numberOfPoints() == 162);
+
+    REQUIRE(integrate(p0, l13) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l13) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l13) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l13) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l13) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l13) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l13) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l13) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l13) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l13) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l13) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l13) == Catch::Approx(-4571362439539697. / 7518708288000000));
+    REQUIRE(integrate(p12, l13) == Catch::Approx(-491755535075074133. / 1378429852800000000));
+    REQUIRE(integrate(p13, l13) == Catch::Approx(-1620413117251976393. / 4135289558400000000));
+    REQUIRE(integrate(p14, l13) != Catch::Approx(296918520496968826367. / 827057911680000000000.));
+
+    REQUIRE(get_order(p14, l13, 296918520496968826367. / 827057911680000000000.) == Catch::Approx(14));
+  }
+
+  SECTION("degree 14")
+  {
+    const QuadratureFormula<3>& l14 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(14));
+
+    REQUIRE(l14.numberOfPoints() == 194);
+
+    REQUIRE(integrate(p0, l14) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l14) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l14) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l14) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l14) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l14) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l14) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l14) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l14) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l14) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l14) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l14) == Catch::Approx(-4571362439539697. / 7518708288000000));
+    REQUIRE(integrate(p12, l14) == Catch::Approx(-491755535075074133. / 1378429852800000000));
+    REQUIRE(integrate(p13, l14) == Catch::Approx(-1620413117251976393. / 4135289558400000000));
+    REQUIRE(integrate(p14, l14) == Catch::Approx(296918520496968826367. / 827057911680000000000.));
+    REQUIRE(integrate(p15, l14) != Catch::Approx(-7727953154629829488841. / 210899767478400000000000.));
+
+    // In a weird way, one gets almost 16th order on this test
+    REQUIRE(get_order(p15, l14, -7727953154629829488841. / 210899767478400000000000.) ==
+            Catch::Approx(16).margin(0.01));
+  }
+
+  SECTION("degree 15")
+  {
+    const QuadratureFormula<3>& l15 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(15));
+
+    REQUIRE(l15.numberOfPoints() == 238);
+
+    REQUIRE(integrate(p0, l15) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l15) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l15) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l15) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l15) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l15) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l15) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l15) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l15) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l15) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l15) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l15) == Catch::Approx(-4571362439539697. / 7518708288000000));
+    REQUIRE(integrate(p12, l15) == Catch::Approx(-491755535075074133. / 1378429852800000000));
+    REQUIRE(integrate(p13, l15) == Catch::Approx(-1620413117251976393. / 4135289558400000000));
+    REQUIRE(integrate(p14, l15) == Catch::Approx(296918520496968826367. / 827057911680000000000.));
+    REQUIRE(integrate(p15, l15) == Catch::Approx(-7727953154629829488841. / 210899767478400000000000.));
+    REQUIRE(integrate(p16, l15) != Catch::Approx(-18153283669186101815689. / 527249418696000000000000.));
+
+    REQUIRE(get_order(p16, l15, -18153283669186101815689. / 527249418696000000000000.) == Catch::Approx(16));
+  }
+
+  SECTION("degree 16")
+  {
+    const QuadratureFormula<3>& l16 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(16));
+
+    REQUIRE(l16.numberOfPoints() == 287);
+
+    REQUIRE(integrate(p0, l16) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l16) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l16) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l16) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l16) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l16) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l16) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l16) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l16) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l16) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l16) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l16) == Catch::Approx(-4571362439539697. / 7518708288000000));
+    REQUIRE(integrate(p12, l16) == Catch::Approx(-491755535075074133. / 1378429852800000000));
+    REQUIRE(integrate(p13, l16) == Catch::Approx(-1620413117251976393. / 4135289558400000000));
+    REQUIRE(integrate(p14, l16) == Catch::Approx(296918520496968826367. / 827057911680000000000.));
+    REQUIRE(integrate(p15, l16) == Catch::Approx(-7727953154629829488841. / 210899767478400000000000.));
+    REQUIRE(integrate(p16, l16) == Catch::Approx(-18153283669186101815689. / 527249418696000000000000.));
+    REQUIRE(integrate(p17, l16) != Catch::Approx(5157361121064510230030071. / 200354779104480000000000000.));
+
+    // In a weird way, one gets almost 18th order on this test
+    REQUIRE(get_order(p17, l16, 5157361121064510230030071. / 200354779104480000000000000.) ==
+            Catch::Approx(18).margin(0.1));
+  }
+
+  SECTION("degree 17")
+  {
+    const QuadratureFormula<3>& l17 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(17));
+
+    REQUIRE(l17.numberOfPoints() == 338);
+
+    REQUIRE(integrate(p0, l17) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l17) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l17) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l17) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l17) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l17) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l17) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l17) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l17) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l17) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l17) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l17) == Catch::Approx(-4571362439539697. / 7518708288000000));
+    REQUIRE(integrate(p12, l17) == Catch::Approx(-491755535075074133. / 1378429852800000000));
+    REQUIRE(integrate(p13, l17) == Catch::Approx(-1620413117251976393. / 4135289558400000000));
+    REQUIRE(integrate(p14, l17) == Catch::Approx(296918520496968826367. / 827057911680000000000.));
+    REQUIRE(integrate(p15, l17) == Catch::Approx(-7727953154629829488841. / 210899767478400000000000.));
+    REQUIRE(integrate(p16, l17) == Catch::Approx(-18153283669186101815689. / 527249418696000000000000.));
+    REQUIRE(integrate(p17, l17) == Catch::Approx(5157361121064510230030071. / 200354779104480000000000000.));
+    REQUIRE(integrate(p18, l17) !=
+            Catch::Approx(195337148397715128549413507. / 5609933814925440000000000000.).epsilon(1E-10));
+
+    REQUIRE(get_order(p18, l17, 195337148397715128549413507. / 5609933814925440000000000000.) == Catch::Approx(18));
+  }
+
+  SECTION("degree 18")
+  {
+    const QuadratureFormula<3>& l18 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(18));
+
+    REQUIRE(l18.numberOfPoints() == 396);
+
+    REQUIRE(integrate(p0, l18) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l18) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l18) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l18) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l18) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l18) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l18) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l18) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l18) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l18) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l18) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l18) == Catch::Approx(-4571362439539697. / 7518708288000000));
+    REQUIRE(integrate(p12, l18) == Catch::Approx(-491755535075074133. / 1378429852800000000));
+    REQUIRE(integrate(p13, l18) == Catch::Approx(-1620413117251976393. / 4135289558400000000));
+    REQUIRE(integrate(p14, l18) == Catch::Approx(296918520496968826367. / 827057911680000000000.));
+    REQUIRE(integrate(p15, l18) == Catch::Approx(-7727953154629829488841. / 210899767478400000000000.));
+    REQUIRE(integrate(p16, l18) == Catch::Approx(-18153283669186101815689. / 527249418696000000000000.));
+    REQUIRE(integrate(p17, l18) == Catch::Approx(5157361121064510230030071. / 200354779104480000000000000.));
+    REQUIRE(integrate(p18, l18) == Catch::Approx(195337148397715128549413507. / 5609933814925440000000000000.));
+    REQUIRE(integrate(p19, l18) !=
+            Catch::Approx(-417563570921497136922189149. / 14024834537313600000000000000.).epsilon(1E-10));
+
+    // In a weird way, one gets almost 20th order on this test
+    REQUIRE(get_order(p19, l18, -417563570921497136922189149. / 14024834537313600000000000000.) ==
+            Catch::Approx(20).margin(0.01));
+  }
+
+  SECTION("degree 19")
+  {
+    const QuadratureFormula<3>& l19 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(19));
+
+    REQUIRE(l19.numberOfPoints() == 420);
+
+    REQUIRE(integrate(p0, l19) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l19) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l19) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l19) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l19) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l19) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l19) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l19) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l19) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l19) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l19) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l19) == Catch::Approx(-4571362439539697. / 7518708288000000));
+    REQUIRE(integrate(p12, l19) == Catch::Approx(-491755535075074133. / 1378429852800000000));
+    REQUIRE(integrate(p13, l19) == Catch::Approx(-1620413117251976393. / 4135289558400000000));
+    REQUIRE(integrate(p14, l19) == Catch::Approx(296918520496968826367. / 827057911680000000000.));
+    REQUIRE(integrate(p15, l19) == Catch::Approx(-7727953154629829488841. / 210899767478400000000000.));
+    REQUIRE(integrate(p16, l19) == Catch::Approx(-18153283669186101815689. / 527249418696000000000000.));
+    REQUIRE(integrate(p17, l19) == Catch::Approx(5157361121064510230030071. / 200354779104480000000000000.));
+    REQUIRE(integrate(p18, l19) == Catch::Approx(195337148397715128549413507. / 5609933814925440000000000000.));
+    REQUIRE(integrate(p19, l19) == Catch::Approx(-417563570921497136922189149. / 14024834537313600000000000000.));
+    REQUIRE(integrate(p20, l19) !=
+            Catch::Approx(4740816174053415637444760963. / 205697573213932800000000000000.).epsilon(1E-10));
+
+    REQUIRE(get_order(p20, l19, 4740816174053415637444760963. / 205697573213932800000000000000.) ==
+            Catch::Approx(20).margin(0.01));
+  }
+
+  SECTION("degree 20")
+  {
+    const QuadratureFormula<3>& l20 = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(20));
+
+    REQUIRE(l20.numberOfPoints() == 518);
+
+    REQUIRE(integrate(p0, l20) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l20) == Catch::Approx(2. / 3));
+    REQUIRE(integrate(p2, l20) == Catch::Approx(47. / 24));
+    REQUIRE(integrate(p3, l20) == Catch::Approx(-427. / 360));
+    REQUIRE(integrate(p4, l20) == Catch::Approx(1003. / 3600));
+    REQUIRE(integrate(p5, l20) == Catch::Approx(34031. / 18000));
+    REQUIRE(integrate(p6, l20) == Catch::Approx(36346369. / 55082112));
+    REQUIRE(integrate(p7, l20) == Catch::Approx(-1128861017. / 918035200));
+    REQUIRE(integrate(p8, l20) == Catch::Approx(-21178319419. / 27541056000));
+    REQUIRE(integrate(p9, l20) == Catch::Approx(-5483758803191. / 9088548480000));
+    REQUIRE(integrate(p10, l20) == Catch::Approx(-9456848221657. / 22721371200000));
+    REQUIRE(integrate(p11, l20) == Catch::Approx(-4571362439539697. / 7518708288000000));
+    REQUIRE(integrate(p12, l20) == Catch::Approx(-491755535075074133. / 1378429852800000000));
+    REQUIRE(integrate(p13, l20) == Catch::Approx(-1620413117251976393. / 4135289558400000000));
+    REQUIRE(integrate(p14, l20) == Catch::Approx(296918520496968826367. / 827057911680000000000.));
+    REQUIRE(integrate(p15, l20) == Catch::Approx(-7727953154629829488841. / 210899767478400000000000.));
+    REQUIRE(integrate(p16, l20) == Catch::Approx(-18153283669186101815689. / 527249418696000000000000.));
+    REQUIRE(integrate(p17, l20) == Catch::Approx(5157361121064510230030071. / 200354779104480000000000000.));
+    REQUIRE(integrate(p18, l20) == Catch::Approx(195337148397715128549413507. / 5609933814925440000000000000.));
+    REQUIRE(integrate(p19, l20) == Catch::Approx(-417563570921497136922189149. / 14024834537313600000000000000.));
+    REQUIRE(integrate(p20, l20) == Catch::Approx(4740816174053415637444760963. / 205697573213932800000000000000.));
+    REQUIRE(integrate(p21, l20) !=
+            Catch::Approx(-164372186128198750911065614811351. / 7096566275880681600000000000000000.).epsilon(1E-10));
+
+    // In a weird way, one gets almost 22th order on this test
+    REQUIRE(get_order(p21, l20, -164372186128198750911065614811351. / 7096566275880681600000000000000000.) ==
+            Catch::Approx(22).margin(0.01));
+  }
+
+  SECTION("max implemented degree")
+  {
+    REQUIRE(QuadratureManager::instance().maxPrismDegree(QuadratureType::Gauss) == PrismGaussQuadrature::max_degree);
+  }
+}
diff --git a/tests/test_PrismTransformation.cpp b/tests/test_PrismTransformation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f933fe308fbc64915faf04449aeca364f0904053
--- /dev/null
+++ b/tests/test_PrismTransformation.cpp
@@ -0,0 +1,106 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/PrismTransformation.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("PrismTransformation", "[geometry]")
+{
+  using R3 = TinyVector<3>;
+
+  const R3 a_hat = {0, 0, -1};
+  const R3 b_hat = {1, 0, -1};
+  const R3 c_hat = {0, 1, -1};
+  const R3 d_hat = {0, 0, +1};
+  const R3 e_hat = {1, 0, +1};
+  const R3 f_hat = {0, 1, +1};
+
+  const R3 m_hat = {1. / 3, 1. / 3, 0};
+
+  const R3 a = {1, 2, 0};
+  const R3 b = {3, 1, 3};
+  const R3 c = {2, 5, 2};
+  const R3 d = {0, 3, 1};
+  const R3 e = {1, 2, 5};
+  const R3 f = {3, 1, 7};
+
+  const PrismTransformation t(a, b, c, d, e, f);
+
+  SECTION("points")
+  {
+    REQUIRE(l2Norm(t(a_hat) - a) == Catch::Approx(0));
+    REQUIRE(l2Norm(t(b_hat) - b) == Catch::Approx(0));
+    REQUIRE(l2Norm(t(c_hat) - c) == Catch::Approx(0));
+    REQUIRE(l2Norm(t(d_hat) - d) == Catch::Approx(0));
+    REQUIRE(l2Norm(t(e_hat) - e) == Catch::Approx(0));
+    REQUIRE(l2Norm(t(f_hat) - f) == Catch::Approx(0));
+
+    R3 m = (1. / 6) * (a + b + c + d + e + f);
+
+    REQUIRE(l2Norm(t(m_hat) - m) == Catch::Approx(0).margin(1E-14));
+  }
+
+  SECTION("Jacobian determinant")
+  {
+    SECTION("at points")
+    {
+      auto detJ = [](const R3 X) {
+        const double& x = X[0];
+        const double& y = X[1];
+        const double& z = X[2];
+
+        return ((2 * y + 0.5 * x + 0.5) * (z + 2) - (y - 0.5 * x - 0.5) * (2 * z + 4)) +
+               (1.5 - 0.5 * z) * ((2 * y + 0.5 * x + 0.5) * (0.5 - 2.5 * z) - (0.5 - 2.5 * y) * (2 * z + 4)) +
+               (0.5 * z + 3.5) * ((0.5 - 2.5 * y) * (z + 2) - (y - 0.5 * x - 0.5) * (0.5 - 2.5 * z));
+      };
+
+      REQUIRE(t.jacobianDeterminant(a_hat) == Catch::Approx(detJ(a_hat)));
+      REQUIRE(t.jacobianDeterminant(b_hat) == Catch::Approx(detJ(b_hat)));
+      REQUIRE(t.jacobianDeterminant(c_hat) == Catch::Approx(detJ(c_hat)));
+      REQUIRE(t.jacobianDeterminant(d_hat) == Catch::Approx(detJ(d_hat)));
+      REQUIRE(t.jacobianDeterminant(e_hat) == Catch::Approx(detJ(e_hat)));
+      REQUIRE(t.jacobianDeterminant(f_hat) == Catch::Approx(detJ(f_hat)));
+
+      REQUIRE(t.jacobianDeterminant(m_hat) == Catch::Approx(detJ(m_hat)));
+    }
+
+    SECTION("volume calculation")
+    {
+      // due to the z component of the jacobian determinant, degree 3
+      // polynomials must be exactly integrated
+      const QuadratureFormula<3>& gauss = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(3));
+
+      double volume = 0;
+      for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+        volume += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i));
+      }
+
+      // 11/2 is actually the volume of the prism
+      REQUIRE(volume == Catch::Approx(11. / 2));
+    }
+
+    SECTION("exact polynomial integration")
+    {
+      auto p = [](const R3& X) {
+        const double x = X[0];
+        const double y = X[1];
+        const double z = X[2];
+
+        return 3 * x * x + 2 * y * y + 3 * z * z + 4 * x + 3 * y + 2 * z + 1;
+      };
+
+      // 5 is the minimum quadrature rule to integrate the polynomial on the prism
+      const QuadratureFormula<3>& gauss = QuadratureManager::instance().getPrismFormula(GaussQuadratureDescriptor(5));
+
+      double integral = 0;
+      for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+        integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i)));
+      }
+
+      REQUIRE(integral == Catch::Approx(30377. / 90));
+    }
+  }
+}
diff --git a/tests/test_PugsFunctionAdapter.cpp b/tests/test_PugsFunctionAdapter.cpp
index 76f3a06aa3f96c208e6c02c16f8006e22160a8f6..cb0dbc7c5e1b76dfeeb6390ddca70524e22d7dc1 100644
--- a/tests/test_PugsFunctionAdapter.cpp
+++ b/tests/test_PugsFunctionAdapter.cpp
@@ -33,7 +33,7 @@ class TestBinary<OutputType(InputType...)> : public PugsFunctionAdapter<OutputTy
     auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
     auto convert_result = Adapter::getResultConverter(expression.m_data_type);
 
-    Array<ExecutionPolicy> context_list = Adapter::getContextList(expression);
+    auto context_list = Adapter::getContextList(expression);
 
     auto& execution_policy = context_list[0];
 
@@ -50,7 +50,7 @@ class TestBinary<OutputType(InputType...)> : public PugsFunctionAdapter<OutputTy
     auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
     auto convert_result = Adapter::getResultConverter(expression.m_data_type);
 
-    Array<ExecutionPolicy> context_list = Adapter::getContextList(expression);
+    auto context_list = Adapter::getContextList(expression);
 
     auto& execution_policy = context_list[0];
 
diff --git a/tests/test_PyramidGaussQuadrature.cpp b/tests/test_PyramidGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e648c96fb13df144945e7c73cdf381545ff1169b
--- /dev/null
+++ b/tests/test_PyramidGaussQuadrature.cpp
@@ -0,0 +1,570 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/PyramidGaussQuadrature.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/TetrahedronTransformation.hpp>
+#include <utils/Exceptions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("PyramidGaussQuadrature", "[analysis]")
+{
+  auto integrate = [](auto f, auto quadrature_formula) {
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    auto value = weight_list[0] * f(point_list[0]);
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(point_list[i]);
+    }
+
+    return value;
+  };
+
+  auto p0 = [](const TinyVector<3>&) { return 4; };
+  auto p1 = [](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return 2 * x + 3 * y + z - 1;
+  };
+  auto p2 = [&p1](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p1(X) * (2.5 * x - 3 * y + z + 3);
+  };
+  auto p3 = [&p2](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p2(X) * (3 * x + 2 * y - 3 * z - 1);
+  };
+  auto p4 = [&p3](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p3(X) * (2 * x - 0.5 * y - 1.3 * z + 1);
+  };
+  auto p5 = [&p4](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p4(X) * (-0.1 * x + 1.3 * y - 3 * z + 1);
+  };
+  auto p6 = [&p5](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p5(X) * 7875. / 143443 * (2 * x - y + 4 * z + 1);
+  };
+  auto p7 = [&p6](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p6(X) * (0.7 * x - 2.7 * y + 1.3 * z - 2);
+  };
+  auto p8 = [&p7](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p7(X) * (0.3 * x + 1.2 * y - 0.7 * z + 0.2);
+  };
+  auto p9 = [&p8](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p8(X) * (-0.2 * x + 1.1 * y - 0.5 * z + 0.6);
+  };
+  auto p10 = [&p9](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p9(X) * (0.7 * x - 0.6 * y - 0.7 * z - 0.2);
+  };
+  auto p11 = [&p10](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p10(X) * (-1.3 * x + 0.6 * y - 1.3 * z + 0.7);
+  };
+  auto p12 = [&p11](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p11(X) * (0.3 * x - 0.7 * y + 0.3 * z + 0.7);
+  };
+  auto p13 = [&p12](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p12(X) * (0.9 * x + 0.2 * y - 0.4 * z + 0.5);
+  };
+  auto p14 = [&p13](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p13(X) * (0.6 * x - 1.2 * y + 0.7 * z - 0.4);
+  };
+  auto p15 = [&p14](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p14(X) * (-0.2 * x - 0.7 * y + 0.9 * z + 0.8);
+  };
+  auto p16 = [&p15](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p15(X) * (0.7 * x + 0.2 * y - 0.6 * z + 0.4);
+  };
+  auto p17 = [&p16](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p16(X) * (-0.1 * x + 0.8 * y + 0.3 * z - 0.2);
+  };
+  auto p18 = [&p17](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p17(X) * (0.7 * x - 0.2 * y - 0.3 * z + 0.8);
+  };
+  auto p19 = [&p18](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p18(X) * (-0.7 * x + 1.2 * y + 1.3 * z + 0.8);
+  };
+  auto p20 = [&p19](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p19(X) * (0.7 * x - 1.2 * y + 0.3 * z - 0.6);
+  };
+  auto p21 = [&p20](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p20(X) * (0.7 * x - 1.2 * y + 0.3 * z - 0.6);
+  };
+
+  SECTION("degree 1")
+  {
+    const QuadratureFormula<3>& l1 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(1));
+
+    REQUIRE(l1.numberOfPoints() == 1);
+
+    REQUIRE(integrate(p0, l1) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l1) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l1) != Catch::Approx(-64. / 15));
+  }
+
+  SECTION("degree 2")
+  {
+    const QuadratureFormula<3>& l2 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(2));
+
+    REQUIRE(l2.numberOfPoints() == 5);
+
+    REQUIRE(integrate(p0, l2) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l2) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l2) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l2) != Catch::Approx(83. / 5));
+  }
+
+  SECTION("degree 3")
+  {
+    const QuadratureFormula<3>& l3 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(3));
+
+    REQUIRE(l3.numberOfPoints() == 6);
+
+    REQUIRE(integrate(p0, l3) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l3) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l3) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l3) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l3) != Catch::Approx(26809. / 3150));
+  }
+
+  SECTION("degree 4")
+  {
+    const QuadratureFormula<3>& l4 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(4));
+
+    REQUIRE(l4.numberOfPoints() == 10);
+
+    REQUIRE(integrate(p0, l4) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l4) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l4) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l4) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l4) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l4) != Catch::Approx(42881. / 63000));
+  }
+
+  SECTION("degree 5")
+  {
+    const QuadratureFormula<3>& l5 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(5));
+
+    REQUIRE(l5.numberOfPoints() == 15);
+
+    REQUIRE(integrate(p0, l5) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l5) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l5) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l5) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l5) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l5) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l5) != Catch::Approx(-59509. / 1290987));
+  }
+
+  SECTION("degree 6")
+  {
+    const QuadratureFormula<3>& l6 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(6));
+
+    REQUIRE(l6.numberOfPoints() == 23);
+
+    REQUIRE(integrate(p0, l6) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l6) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l6) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l6) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l6) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l6) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l6) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l6) != Catch::Approx(-79258447. / 64549350));
+  }
+
+  SECTION("degree 7")
+  {
+    const QuadratureFormula<3>& l7 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(7));
+
+    REQUIRE(l7.numberOfPoints() == 31);
+
+    REQUIRE(integrate(p0, l7) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l7) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l7) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l7) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l7) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l7) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l7) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l7) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l7) != Catch::Approx(-64936890181. / 56803428000));
+  }
+
+  SECTION("degree 8")
+  {
+    const QuadratureFormula<3>& l8 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(8));
+
+    REQUIRE(l8.numberOfPoints() == 47);
+
+    REQUIRE(integrate(p0, l8) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l8) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l8) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l8) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l8) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l8) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l8) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l8) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l8) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l8) != Catch::Approx(-46104457917. / 31557460000));
+  }
+
+  SECTION("degree 9")
+  {
+    const QuadratureFormula<3>& l9 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(9));
+
+    REQUIRE(l9.numberOfPoints() == 62);
+
+    REQUIRE(integrate(p0, l9) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l9) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l9) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l9) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l9) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l9) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l9) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l9) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l9) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l9) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l9) != Catch::Approx(14564160020837. / 73844456400000));
+  }
+
+  SECTION("degree 10")
+  {
+    const QuadratureFormula<3>& l10 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(10));
+
+    REQUIRE(l10.numberOfPoints() == 80);
+
+    REQUIRE(integrate(p0, l10) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l10) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l10) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l10) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l10) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l10) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l10) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l10) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l10) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l10) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l10) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l10) != Catch::Approx(70717900459291. / 1723037316000000));
+  }
+
+  SECTION("degree 11")
+  {
+    const QuadratureFormula<3>& l11 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(11));
+
+    REQUIRE(l11.numberOfPoints() == 103);
+
+    REQUIRE(integrate(p0, l11) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l11) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l11) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l11) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l11) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l11) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l11) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l11) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l11) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l11) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l11) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l11) == Catch::Approx(70717900459291. / 1723037316000000));
+    REQUIRE(integrate(p12, l11) != Catch::Approx(4088535221940569. / 129227798700000000));
+  }
+
+  SECTION("degree 12")
+  {
+    const QuadratureFormula<3>& l12 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(12));
+
+    REQUIRE(l12.numberOfPoints() == 127);
+
+    REQUIRE(integrate(p0, l12) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l12) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l12) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l12) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l12) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l12) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l12) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l12) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l12) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l12) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l12) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l12) == Catch::Approx(70717900459291. / 1723037316000000));
+    REQUIRE(integrate(p12, l12) == Catch::Approx(4088535221940569. / 129227798700000000));
+    REQUIRE(integrate(p13, l12) != Catch::Approx(4202215015498883. / 129227798700000000));
+  }
+
+  SECTION("degree 13")
+  {
+    const QuadratureFormula<3>& l13 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(13));
+
+    REQUIRE(l13.numberOfPoints() == 152);
+
+    REQUIRE(integrate(p0, l13) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l13) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l13) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l13) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l13) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l13) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l13) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l13) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l13) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l13) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l13) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l13) == Catch::Approx(70717900459291. / 1723037316000000));
+    REQUIRE(integrate(p12, l13) == Catch::Approx(4088535221940569. / 129227798700000000));
+    REQUIRE(integrate(p13, l13) == Catch::Approx(4202215015498883. / 129227798700000000));
+    REQUIRE(integrate(p14, l13) != Catch::Approx(-13139133580740403. / 4992892222500000000.));
+  }
+
+  SECTION("degree 14")
+  {
+    const QuadratureFormula<3>& l14 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(14));
+
+    REQUIRE(l14.numberOfPoints() == 184);
+
+    REQUIRE(integrate(p0, l14) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l14) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l14) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l14) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l14) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l14) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l14) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l14) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l14) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l14) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l14) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l14) == Catch::Approx(70717900459291. / 1723037316000000));
+    REQUIRE(integrate(p12, l14) == Catch::Approx(4088535221940569. / 129227798700000000));
+    REQUIRE(integrate(p13, l14) == Catch::Approx(4202215015498883. / 129227798700000000));
+    REQUIRE(integrate(p14, l14) == Catch::Approx(-13139133580740403. / 4992892222500000000.));
+    REQUIRE(integrate(p15, l14) != Catch::Approx(50695835504084747233. / 3295308866850000000000.));
+  }
+
+  SECTION("degree 15")
+  {
+    const QuadratureFormula<3>& l15 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(15));
+
+    REQUIRE(l15.numberOfPoints() == 234);
+
+    REQUIRE(integrate(p0, l15) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l15) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l15) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l15) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l15) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l15) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l15) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l15) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l15) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l15) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l15) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l15) == Catch::Approx(70717900459291. / 1723037316000000));
+    REQUIRE(integrate(p12, l15) == Catch::Approx(4088535221940569. / 129227798700000000));
+    REQUIRE(integrate(p13, l15) == Catch::Approx(4202215015498883. / 129227798700000000));
+    REQUIRE(integrate(p14, l15) == Catch::Approx(-13139133580740403. / 4992892222500000000.));
+    REQUIRE(integrate(p15, l15) == Catch::Approx(50695835504084747233. / 3295308866850000000000.));
+    REQUIRE(integrate(p16, l15) != Catch::Approx(7438848232461834482681. / 834811579602000000000000.));
+  }
+
+  SECTION("degree 16")
+  {
+    const QuadratureFormula<3>& l16 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(16));
+
+    REQUIRE(l16.numberOfPoints() == 285);
+
+    REQUIRE(integrate(p0, l16) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l16) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l16) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l16) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l16) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l16) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l16) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l16) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l16) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l16) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l16) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l16) == Catch::Approx(70717900459291. / 1723037316000000));
+    REQUIRE(integrate(p12, l16) == Catch::Approx(4088535221940569. / 129227798700000000));
+    REQUIRE(integrate(p13, l16) == Catch::Approx(4202215015498883. / 129227798700000000));
+    REQUIRE(integrate(p14, l16) == Catch::Approx(-13139133580740403. / 4992892222500000000.));
+    REQUIRE(integrate(p15, l16) == Catch::Approx(50695835504084747233. / 3295308866850000000000.));
+    REQUIRE(integrate(p16, l16) == Catch::Approx(7438848232461834482681. / 834811579602000000000000.));
+    REQUIRE(integrate(p17, l16) != Catch::Approx(-49370451351776632471. / 4384514598750000000000.));
+  }
+
+  SECTION("degree 17")
+  {
+    const QuadratureFormula<3>& l17 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(17));
+
+    REQUIRE(l17.numberOfPoints() == 319);
+
+    REQUIRE(integrate(p0, l17) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l17) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l17) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l17) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l17) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l17) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l17) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l17) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l17) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l17) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l17) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l17) == Catch::Approx(70717900459291. / 1723037316000000));
+    REQUIRE(integrate(p12, l17) == Catch::Approx(4088535221940569. / 129227798700000000));
+    REQUIRE(integrate(p13, l17) == Catch::Approx(4202215015498883. / 129227798700000000));
+    REQUIRE(integrate(p14, l17) == Catch::Approx(-13139133580740403. / 4992892222500000000.));
+    REQUIRE(integrate(p15, l17) == Catch::Approx(50695835504084747233. / 3295308866850000000000.));
+    REQUIRE(integrate(p16, l17) == Catch::Approx(7438848232461834482681. / 834811579602000000000000.));
+    REQUIRE(integrate(p17, l17) == Catch::Approx(-49370451351776632471. / 4384514598750000000000.));
+    REQUIRE(integrate(p18, l17) != Catch::Approx(-3041981344499113218848083. / 194789368573800000000000000.));
+  }
+
+  SECTION("degree 18")
+  {
+    const QuadratureFormula<3>& l18 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(18));
+
+    REQUIRE(l18.numberOfPoints() == 357);
+
+    REQUIRE(integrate(p0, l18) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l18) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l18) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l18) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l18) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l18) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l18) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l18) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l18) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l18) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l18) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l18) == Catch::Approx(70717900459291. / 1723037316000000));
+    REQUIRE(integrate(p12, l18) == Catch::Approx(4088535221940569. / 129227798700000000));
+    REQUIRE(integrate(p13, l18) == Catch::Approx(4202215015498883. / 129227798700000000));
+    REQUIRE(integrate(p14, l18) == Catch::Approx(-13139133580740403. / 4992892222500000000.));
+    REQUIRE(integrate(p15, l18) == Catch::Approx(50695835504084747233. / 3295308866850000000000.));
+    REQUIRE(integrate(p16, l18) == Catch::Approx(7438848232461834482681. / 834811579602000000000000.));
+    REQUIRE(integrate(p17, l18) == Catch::Approx(-49370451351776632471. / 4384514598750000000000.));
+    REQUIRE(integrate(p18, l18) == Catch::Approx(-3041981344499113218848083. / 194789368573800000000000000.));
+    REQUIRE(integrate(p19, l18) != Catch::Approx(6741839335620301740899793. / 892784605963250000000000000.));
+  }
+
+  SECTION("degree 19")
+  {
+    const QuadratureFormula<3>& l19 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(19));
+
+    REQUIRE(l19.numberOfPoints() == 418);
+
+    REQUIRE(integrate(p0, l19) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l19) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l19) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l19) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l19) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l19) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l19) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l19) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l19) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l19) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l19) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l19) == Catch::Approx(70717900459291. / 1723037316000000));
+    REQUIRE(integrate(p12, l19) == Catch::Approx(4088535221940569. / 129227798700000000));
+    REQUIRE(integrate(p13, l19) == Catch::Approx(4202215015498883. / 129227798700000000));
+    REQUIRE(integrate(p14, l19) == Catch::Approx(-13139133580740403. / 4992892222500000000.));
+    REQUIRE(integrate(p15, l19) == Catch::Approx(50695835504084747233. / 3295308866850000000000.));
+    REQUIRE(integrate(p16, l19) == Catch::Approx(7438848232461834482681. / 834811579602000000000000.));
+    REQUIRE(integrate(p17, l19) == Catch::Approx(-49370451351776632471. / 4384514598750000000000.));
+    REQUIRE(integrate(p18, l19) == Catch::Approx(-3041981344499113218848083. / 194789368573800000000000000.));
+    REQUIRE(integrate(p19, l19) == Catch::Approx(6741839335620301740899793. / 892784605963250000000000000.));
+    REQUIRE(integrate(p20, l19) != Catch::Approx(50574805739660969727328017511. / 4928171024917140000000000000000.));
+  }
+
+  SECTION("degree 20")
+  {
+    const QuadratureFormula<3>& l20 = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(20));
+
+    REQUIRE(l20.numberOfPoints() == 489);
+
+    REQUIRE(integrate(p0, l20) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l20) == Catch::Approx(-1));
+    REQUIRE(integrate(p2, l20) == Catch::Approx(-64. / 15));
+    REQUIRE(integrate(p3, l20) == Catch::Approx(83. / 5));
+    REQUIRE(integrate(p4, l20) == Catch::Approx(26809. / 3150));
+    REQUIRE(integrate(p5, l20) == Catch::Approx(42881. / 63000));
+    REQUIRE(integrate(p6, l20) == Catch::Approx(-59509. / 1290987));
+    REQUIRE(integrate(p7, l20) == Catch::Approx(-79258447. / 64549350));
+    REQUIRE(integrate(p8, l20) == Catch::Approx(-64936890181. / 56803428000));
+    REQUIRE(integrate(p9, l20) == Catch::Approx(-46104457917. / 31557460000));
+    REQUIRE(integrate(p10, l20) == Catch::Approx(14564160020837. / 73844456400000));
+    REQUIRE(integrate(p11, l20) == Catch::Approx(70717900459291. / 1723037316000000));
+    REQUIRE(integrate(p12, l20) == Catch::Approx(4088535221940569. / 129227798700000000));
+    REQUIRE(integrate(p13, l20) == Catch::Approx(4202215015498883. / 129227798700000000));
+    REQUIRE(integrate(p14, l20) == Catch::Approx(-13139133580740403. / 4992892222500000000.));
+    REQUIRE(integrate(p15, l20) == Catch::Approx(50695835504084747233. / 3295308866850000000000.));
+    REQUIRE(integrate(p16, l20) == Catch::Approx(7438848232461834482681. / 834811579602000000000000.));
+    REQUIRE(integrate(p17, l20) == Catch::Approx(-49370451351776632471. / 4384514598750000000000.));
+    REQUIRE(integrate(p18, l20) == Catch::Approx(-3041981344499113218848083. / 194789368573800000000000000.));
+    REQUIRE(integrate(p19, l20) == Catch::Approx(6741839335620301740899793. / 892784605963250000000000000.));
+    REQUIRE(integrate(p20, l20) == Catch::Approx(50574805739660969727328017511. / 4928171024917140000000000000000.));
+    REQUIRE(integrate(p21, l20) != Catch::Approx(796248143552124247176376796357. / 110883848060635650000000000000000.));
+  }
+
+  SECTION("max implemented degree")
+  {
+    REQUIRE(QuadratureManager::instance().maxPyramidDegree(QuadratureType::Gauss) ==
+            PyramidGaussQuadrature::max_degree);
+  }
+}
diff --git a/tests/test_PyramidTransformation.cpp b/tests/test_PyramidTransformation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..23b7369e625b20307ddab31d222b1b2ae3d607b4
--- /dev/null
+++ b/tests/test_PyramidTransformation.cpp
@@ -0,0 +1,98 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/CubeTransformation.hpp>
+#include <geometry/PyramidTransformation.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("PyramidTransformation", "[geometry]")
+{
+  using R3 = TinyVector<3>;
+
+  const R3 a_hat = {-1, -1, +0};
+  const R3 b_hat = {+1, -1, +0};
+  const R3 c_hat = {+1, +1, +0};
+  const R3 d_hat = {-1, +1, +0};
+  const R3 e_hat = {+0, +0, +1};
+
+  const R3 m_hat = {0, 0, 1. / 5};
+
+  const R3 a = {1, 2, 0};
+  const R3 b = {3, 1, 3};
+  const R3 c = {2, 5, 2};
+  const R3 d = {0, 3, 1};
+  const R3 e = {1, 2, 5};
+
+  const PyramidTransformation t(a, b, c, d, e);
+
+  SECTION("points")
+  {
+    REQUIRE(l2Norm(t(a_hat) - a) == Catch::Approx(0));
+    REQUIRE(l2Norm(t(b_hat) - b) == Catch::Approx(0));
+    REQUIRE(l2Norm(t(c_hat) - c) == Catch::Approx(0));
+    REQUIRE(l2Norm(t(d_hat) - d) == Catch::Approx(0));
+    REQUIRE(l2Norm(t(e_hat) - e) == Catch::Approx(0));
+
+    R3 m = (1. / 5) * (a + b + c + d + e);
+    REQUIRE(l2Norm(t(m_hat) - m) == Catch::Approx(0).margin(1E-14));
+  }
+
+  SECTION("Jacobian determinant")
+  {
+    SECTION("at points")
+    {
+      auto detJ = [](const R3 X) {
+        const double& x = X[0];
+        const double& y = X[1];
+
+        return (43 * x + 13 * y + 93) / 16;
+      };
+
+      REQUIRE(t.jacobianDeterminant(a_hat) == Catch::Approx(detJ(a_hat)));
+      REQUIRE(t.jacobianDeterminant(b_hat) == Catch::Approx(detJ(b_hat)));
+      REQUIRE(t.jacobianDeterminant(c_hat) == Catch::Approx(detJ(c_hat)));
+      REQUIRE(t.jacobianDeterminant(d_hat) == Catch::Approx(detJ(d_hat)));
+      REQUIRE(t.jacobianDeterminant(e_hat) == Catch::Approx(detJ(e_hat)));
+
+      REQUIRE(t.jacobianDeterminant(m_hat) == Catch::Approx(detJ(m_hat)));
+    }
+
+    SECTION("volume calculation")
+    {
+      // The jacobian determinant is a degree 1 polynomial
+      const QuadratureFormula<3>& gauss = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(1));
+
+      double volume = 0;
+      for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+        volume += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i));
+      }
+
+      // 31 / 4 is actually the volume of the pyramid
+      REQUIRE(volume == Catch::Approx(31. / 4));
+    }
+
+    SECTION("exact polynomial integration")
+    {
+      auto p = [](const R3& X) {
+        const double x = X[0];
+        const double y = X[1];
+        const double z = X[2];
+
+        return 3 * x * x + 2 * y * y + 3 * z * z + 4 * x + 3 * y + 2 * z + 1;
+      };
+
+      // 4 is the minimum quadrature rule to integrate the polynomial on the pyramid
+      const QuadratureFormula<3>& gauss = QuadratureManager::instance().getPyramidFormula(GaussQuadratureDescriptor(4));
+      double integral                   = 0;
+      for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+        integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i)));
+      }
+
+      REQUIRE(integral == Catch::Approx(213095. / 448));
+    }
+  }
+}
diff --git a/tests/test_SmallArray.cpp b/tests/test_SmallArray.cpp
index 9b5d482791f9df0a19c41c64cb55070c5bf8be24..251134047754e6c2b8f5012c4d1713d973fa7823 100644
--- a/tests/test_SmallArray.cpp
+++ b/tests/test_SmallArray.cpp
@@ -1,6 +1,8 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_all.hpp>
 
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
 #include <utils/PugsAssert.hpp>
 #include <utils/SmallArray.hpp>
 #include <utils/Types.hpp>
@@ -281,5 +283,71 @@ TEST_CASE("SmallArray", "[utils]")
       REQUIRE(array[i] == std::numeric_limits<int>::max() / 2);
     }
   }
+
+  SECTION("checking for SmallArray reductions")
+  {
+    SmallArray<int> a(10);
+    a[0] = 13;
+    a[1] = 1;
+    a[2] = 8;
+    a[3] = -3;
+    a[4] = 23;
+    a[5] = -1;
+    a[6] = 13;
+    a[7] = 0;
+    a[8] = 12;
+    a[9] = 9;
+
+    SECTION("Min")
+    {
+      REQUIRE(min(a) == -3);
+    }
+
+    SECTION("Max")
+    {
+      REQUIRE(max(a) == 23);
+    }
+
+    SECTION("Sum")
+    {
+      REQUIRE((sum(a) == 75));
+    }
+
+    SECTION("TinyVector Sum")
+    {
+      using N2 = TinyVector<2, int>;
+      SmallArray<N2> b(10);
+      b[0] = {13, 2};
+      b[1] = {1, 3};
+      b[2] = {8, -2};
+      b[3] = {-3, 2};
+      b[4] = {23, 4};
+      b[5] = {-1, -3};
+      b[6] = {13, 17};
+      b[7] = {0, 9};
+      b[8] = {12, 13};
+      b[9] = {9, -17};
+
+      REQUIRE((sum(b) == N2{75, 28}));
+    }
+
+    SECTION("TinyMatrix Sum")
+    {
+      using N22 = TinyMatrix<2, 2, int>;
+      SmallArray<N22> b(10);
+      b[0] = {13, 2, 0, 1};
+      b[1] = {1, 3, 6, 3};
+      b[2] = {8, -2, -1, 21};
+      b[3] = {-3, 2, 5, 12};
+      b[4] = {23, 4, 7, 1};
+      b[5] = {-1, -3, 33, 11};
+      b[6] = {13, 17, 12, 13};
+      b[7] = {0, 9, 1, 14};
+      b[8] = {12, 13, -3, -71};
+      b[9] = {9, -17, 0, 16};
+
+      REQUIRE((sum(b) == N22{75, 28, 60, 21}));
+    }
+  }
 #endif   // NDEBUG
 }
diff --git a/tests/test_SquareGaussQuadrature.cpp b/tests/test_SquareGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e6b9d6e5ee066cd8db48c6a2297bfc3d12cfcd8
--- /dev/null
+++ b/tests/test_SquareGaussQuadrature.cpp
@@ -0,0 +1,480 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <algebra/TinyMatrix.hpp>
+
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <analysis/SquareGaussQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("SquareGaussQuadrature", "[analysis]")
+{
+  auto integrate = [](auto f, auto quadrature_formula) {
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    auto value = weight_list[0] * f(point_list[0]);
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(point_list[i]);
+    }
+
+    return value;
+  };
+
+  auto integrate_on_rectangle = [](auto f, auto quadrature_formula, const std::array<TinyVector<2>, 3>& triangle) {
+    const auto& A = triangle[0];
+    const auto& B = triangle[1];
+    const auto& C = triangle[2];
+
+    TinyMatrix<2> J;
+    for (size_t i = 0; i < 2; ++i) {
+      J(i, 0) = 0.5 * (B[i] - A[i]);
+      J(i, 1) = 0.5 * (C[i] - A[i]);
+    }
+    TinyVector s = 0.5 * (B + C);
+
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    auto value = weight_list[0] * f(J * (point_list[0]) + s);
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(J * (point_list[i]) + s);
+    }
+
+    return det(J) * value;
+  };
+
+  auto get_order = [&integrate, &integrate_on_rectangle](auto f, auto quadrature_formula, const double exact_value) {
+    using R2               = TinyVector<2>;
+    const double int_K_hat = integrate(f, quadrature_formula);
+    const double int_refined   //
+      = integrate_on_rectangle(f, quadrature_formula, {R2{-1, -1}, R2{0, -1}, R2{-1, 0}}) +
+        integrate_on_rectangle(f, quadrature_formula, {R2{0, -1}, R2{1, -1}, R2{0, 0}}) +
+        integrate_on_rectangle(f, quadrature_formula, {R2{-1, 0}, R2{0, 0}, R2{-1, 1}}) +
+        integrate_on_rectangle(f, quadrature_formula, {R2{0, 0}, R2{1, 0}, R2{0, 1}});
+
+    return -std::log((int_refined - exact_value) / (int_K_hat - exact_value)) / std::log(2);
+  };
+
+  auto p0 = [](const TinyVector<2>&) { return 2; };
+  auto p1 = [](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return 2 * x + 3 * y + 1;
+  };
+  auto p2 = [&p1](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p1(X) * (2.5 * x - 3 * y + 3);
+  };
+  auto p3 = [&p2](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p2(X) * (-1.5 * x + 3 * y - 3);
+  };
+  auto p4 = [&p3](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p3(X) * (x + y + 1);
+  };
+  auto p5 = [&p4](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p4(X) * (-0.2 * x - 1.3 * y - 0.7);
+  };
+  auto p6 = [&p5](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p5(X) * (3 * x - 2 * y + 3);
+  };
+  auto p7 = [&p6](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p6(X) * (-2 * x + 4 * y - 7);
+  };
+  auto p8 = [&p7](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p7(X) * (2 * x - 3 * y - 3);
+  };
+  auto p9 = [&p8](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p8(X) * (x + 2 * y - 1.7);
+  };
+  auto p10 = [&p9](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p9(X) * (-1.3 * x - 1.7 * y + 1.3);
+  };
+  auto p11 = [&p10](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p10(X) * (0.8 * x - 3.1 * y + 0.6);
+  };
+  auto p12 = [&p11](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p11(X) * (1.8 * x + 1.3 * y - 0.3);
+  };
+  auto p13 = [&p12](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p12(X) * (-0.9 * x + 1.1 * y - 0.6);
+  };
+  auto p14 = [&p13](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p13(X) * (0.6 * x + 0.3 * y + 1.1);
+  };
+  auto p15 = [&p14](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p14(X) * (0.5 * x - 0.4 * y - 1.1);
+  };
+  auto p16 = [&p15](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p15(X) * (-0.3 * x - 0.3 * y - 0.2);
+  };
+  auto p17 = [&p16](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p16(X) * (-0.1 * x - 0.4 * y - 0.3);
+  };
+  auto p18 = [&p17](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p17(X) * (0.2 * x + 0.3 * y + 0.3);
+  };
+  auto p19 = [&p18](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p18(X) * (2.1 * x + 3.3 * y - 0.3);
+  };
+  auto p20 = [&p19](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p19(X) * (1.2 * x - 2.1 * y + 0.6);
+  };
+  auto p21 = [&p20](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p20(X) * (-1.3 * x - 1.2 * y + 0.7);
+  };
+  auto p22 = [&p21](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p21(X) * (1.1 * x - 2.2 * y - 0.3);
+  };
+
+  SECTION("degree 0 and 1")
+  {
+    const QuadratureFormula<2>& l1 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(1));
+
+    REQUIRE(l1.numberOfPoints() == 1);
+
+    REQUIRE(integrate(p0, l1) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l1) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l1) != Catch::Approx(20. / 3));
+
+    REQUIRE(get_order(p2, l1, 20. / 3) == Catch::Approx(2));
+  }
+
+  SECTION("degree 2 and 3")
+  {
+    const QuadratureFormula<2>& l2 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(2));
+    const QuadratureFormula<2>& l3 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(3));
+
+    REQUIRE(&l2 == &l3);
+
+    REQUIRE(l3.numberOfPoints() == 4);
+
+    REQUIRE(integrate(p0, l3) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l3) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l3) == Catch::Approx(20. / 3));
+    REQUIRE(integrate(p3, l3) == Catch::Approx(-13));
+    REQUIRE(integrate(p4, l3) != Catch::Approx(-1184. / 15));
+
+    REQUIRE(get_order(p4, l3, -1184. / 15) == Catch::Approx(4));
+  }
+
+  SECTION("degree 4 and 5")
+  {
+    const QuadratureFormula<2>& l4 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(4));
+    const QuadratureFormula<2>& l5 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(5));
+
+    REQUIRE(&l4 == &l5);
+
+    REQUIRE(l5.numberOfPoints() == 8);
+
+    REQUIRE(integrate(p0, l5) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l5) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l5) == Catch::Approx(20. / 3));
+    REQUIRE(integrate(p3, l5) == Catch::Approx(-13));
+    REQUIRE(integrate(p4, l5) == Catch::Approx(-1184. / 15));
+    REQUIRE(integrate(p5, l5) == Catch::Approx(1971. / 25));
+    REQUIRE(integrate(p6, l5) != Catch::Approx(60441. / 175));
+
+    REQUIRE(get_order(p6, l5, 60441. / 175) == Catch::Approx(6));
+  }
+
+  SECTION("degree 6 and 7")
+  {
+    const QuadratureFormula<2>& l6 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(6));
+    const QuadratureFormula<2>& l7 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(7));
+
+    REQUIRE(&l6 == &l7);
+
+    REQUIRE(l7.numberOfPoints() == 12);
+
+    REQUIRE(integrate(p0, l7) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l7) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l7) == Catch::Approx(20. / 3));
+    REQUIRE(integrate(p3, l7) == Catch::Approx(-13));
+    REQUIRE(integrate(p4, l7) == Catch::Approx(-1184. / 15));
+    REQUIRE(integrate(p5, l7) == Catch::Approx(1971. / 25));
+    REQUIRE(integrate(p6, l7) == Catch::Approx(60441. / 175));
+    REQUIRE(integrate(p7, l7) == Catch::Approx(-1307119. / 525));
+    REQUIRE(integrate(p8, l7) != Catch::Approx(957697. / 175));
+
+    REQUIRE(get_order(p8, l7, 957697. / 175) == Catch::Approx(8));
+  }
+
+  SECTION("degree 8 and 9")
+  {
+    const QuadratureFormula<2>& l8 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(8));
+    const QuadratureFormula<2>& l9 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(9));
+
+    REQUIRE(&l8 == &l9);
+
+    REQUIRE(l9.numberOfPoints() == 20);
+
+    REQUIRE(integrate(p0, l9) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l9) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l9) == Catch::Approx(20. / 3));
+    REQUIRE(integrate(p3, l9) == Catch::Approx(-13));
+    REQUIRE(integrate(p4, l9) == Catch::Approx(-1184. / 15));
+    REQUIRE(integrate(p5, l9) == Catch::Approx(1971. / 25));
+    REQUIRE(integrate(p6, l9) == Catch::Approx(60441. / 175));
+    REQUIRE(integrate(p7, l9) == Catch::Approx(-1307119. / 525));
+    REQUIRE(integrate(p8, l9) == Catch::Approx(957697. / 175));
+    REQUIRE(integrate(p9, l9) == Catch::Approx(-196981. / 5250));
+    REQUIRE(integrate(p10, l9) != Catch::Approx(78447601. / 577500));
+
+    REQUIRE(get_order(p10, l9, 78447601. / 577500) == Catch::Approx(10));
+  }
+
+  SECTION("degree 10 and 11")
+  {
+    const QuadratureFormula<2>& l10 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(10));
+    const QuadratureFormula<2>& l11 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(11));
+
+    REQUIRE(&l10 == &l11);
+
+    REQUIRE(l11.numberOfPoints() == 28);
+
+    REQUIRE(integrate(p0, l11) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l11) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l11) == Catch::Approx(20. / 3));
+    REQUIRE(integrate(p3, l11) == Catch::Approx(-13));
+    REQUIRE(integrate(p4, l11) == Catch::Approx(-1184. / 15));
+    REQUIRE(integrate(p5, l11) == Catch::Approx(1971. / 25));
+    REQUIRE(integrate(p6, l11) == Catch::Approx(60441. / 175));
+    REQUIRE(integrate(p7, l11) == Catch::Approx(-1307119. / 525));
+    REQUIRE(integrate(p8, l11) == Catch::Approx(957697. / 175));
+    REQUIRE(integrate(p9, l11) == Catch::Approx(-196981. / 5250));
+    REQUIRE(integrate(p10, l11) == Catch::Approx(78447601. / 577500));
+    REQUIRE(integrate(p11, l11) == Catch::Approx(673235482069. / 86625000));
+    REQUIRE(integrate(p12, l11) != Catch::Approx(-4092600398251. / 303187500));
+
+    REQUIRE(get_order(p12, l11, -4092600398251. / 303187500) == Catch::Approx(12));
+  }
+
+  SECTION("degree 12 and 13")
+  {
+    const QuadratureFormula<2>& l12 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(12));
+    const QuadratureFormula<2>& l13 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(13));
+
+    REQUIRE(&l12 == &l13);
+
+    REQUIRE(l13.numberOfPoints() == 37);
+
+    REQUIRE(integrate(p0, l13) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l13) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l13) == Catch::Approx(20. / 3));
+    REQUIRE(integrate(p3, l13) == Catch::Approx(-13));
+    REQUIRE(integrate(p4, l13) == Catch::Approx(-1184. / 15));
+    REQUIRE(integrate(p5, l13) == Catch::Approx(1971. / 25));
+    REQUIRE(integrate(p6, l13) == Catch::Approx(60441. / 175));
+    REQUIRE(integrate(p7, l13) == Catch::Approx(-1307119. / 525));
+    REQUIRE(integrate(p8, l13) == Catch::Approx(957697. / 175));
+    REQUIRE(integrate(p9, l13) == Catch::Approx(-196981. / 5250));
+    REQUIRE(integrate(p10, l13) == Catch::Approx(78447601. / 577500));
+    REQUIRE(integrate(p11, l13) == Catch::Approx(673235482069. / 86625000));
+    REQUIRE(integrate(p12, l13) == Catch::Approx(-4092600398251. / 303187500));
+    REQUIRE(integrate(p13, l13) == Catch::Approx(77231697272647. / 5053125000));
+    REQUIRE(integrate(p14, l13) != Catch::Approx(46574962939049. / 9384375000));
+
+    REQUIRE(get_order(p14, l13, 46574962939049. / 9384375000) == Catch::Approx(14));
+  }
+
+  SECTION("degree 14 and 15")
+  {
+    const QuadratureFormula<2>& l14 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(14));
+    const QuadratureFormula<2>& l15 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(15));
+
+    REQUIRE(&l14 == &l15);
+
+    REQUIRE(l15.numberOfPoints() == 48);
+
+    REQUIRE(integrate(p0, l15) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l15) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l15) == Catch::Approx(20. / 3));
+    REQUIRE(integrate(p3, l15) == Catch::Approx(-13));
+    REQUIRE(integrate(p4, l15) == Catch::Approx(-1184. / 15));
+    REQUIRE(integrate(p5, l15) == Catch::Approx(1971. / 25));
+    REQUIRE(integrate(p6, l15) == Catch::Approx(60441. / 175));
+    REQUIRE(integrate(p7, l15) == Catch::Approx(-1307119. / 525));
+    REQUIRE(integrate(p8, l15) == Catch::Approx(957697. / 175));
+    REQUIRE(integrate(p9, l15) == Catch::Approx(-196981. / 5250));
+    REQUIRE(integrate(p10, l15) == Catch::Approx(78447601. / 577500));
+    REQUIRE(integrate(p11, l15) == Catch::Approx(673235482069. / 86625000));
+    REQUIRE(integrate(p12, l15) == Catch::Approx(-4092600398251. / 303187500));
+    REQUIRE(integrate(p13, l15) == Catch::Approx(77231697272647. / 5053125000));
+    REQUIRE(integrate(p14, l15) == Catch::Approx(46574962939049. / 9384375000));
+    REQUIRE(integrate(p15, l15) == Catch::Approx(-4818864487842259. / 1094843750000));
+    REQUIRE(integrate(p16, l15) != Catch::Approx(-89885697514686141. / 23265429687500));
+
+    REQUIRE(get_order(p16, l15, -89885697514686141. / 23265429687500) == Catch::Approx(16));
+  }
+
+  SECTION("degree 16 and 17")
+  {
+    const QuadratureFormula<2>& l16 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(16));
+    const QuadratureFormula<2>& l17 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(17));
+
+    REQUIRE(&l16 == &l17);
+
+    REQUIRE(l17.numberOfPoints() == 60);
+
+    REQUIRE(integrate(p0, l17) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l17) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l17) == Catch::Approx(20. / 3));
+    REQUIRE(integrate(p3, l17) == Catch::Approx(-13));
+    REQUIRE(integrate(p4, l17) == Catch::Approx(-1184. / 15));
+    REQUIRE(integrate(p5, l17) == Catch::Approx(1971. / 25));
+    REQUIRE(integrate(p6, l17) == Catch::Approx(60441. / 175));
+    REQUIRE(integrate(p7, l17) == Catch::Approx(-1307119. / 525));
+    REQUIRE(integrate(p8, l17) == Catch::Approx(957697. / 175));
+    REQUIRE(integrate(p9, l17) == Catch::Approx(-196981. / 5250));
+    REQUIRE(integrate(p10, l17) == Catch::Approx(78447601. / 577500));
+    REQUIRE(integrate(p11, l17) == Catch::Approx(673235482069. / 86625000));
+    REQUIRE(integrate(p12, l17) == Catch::Approx(-4092600398251. / 303187500));
+    REQUIRE(integrate(p13, l17) == Catch::Approx(77231697272647. / 5053125000));
+    REQUIRE(integrate(p14, l17) == Catch::Approx(46574962939049. / 9384375000));
+    REQUIRE(integrate(p15, l17) == Catch::Approx(-4818864487842259. / 1094843750000));
+    REQUIRE(integrate(p16, l17) == Catch::Approx(-89885697514686141. / 23265429687500));
+    REQUIRE(integrate(p17, l17) == Catch::Approx(16706355156097391. / 12085937500000));
+    REQUIRE(integrate(p18, l17) != Catch::Approx(495866230514635109957. / 397838847656250000.).epsilon(1E-8));
+
+    REQUIRE(get_order(p18, l17, 495866230514635109957. / 397838847656250000.) == Catch::Approx(18).margin(0.001));
+  }
+
+  SECTION("degree 18 and 19")
+  {
+    const QuadratureFormula<2>& l18 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(18));
+    const QuadratureFormula<2>& l19 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(19));
+
+    REQUIRE(&l18 == &l19);
+
+    REQUIRE(l19.numberOfPoints() == 72);
+
+    REQUIRE(integrate(p0, l19) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l19) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l19) == Catch::Approx(20. / 3));
+    REQUIRE(integrate(p3, l19) == Catch::Approx(-13));
+    REQUIRE(integrate(p4, l19) == Catch::Approx(-1184. / 15));
+    REQUIRE(integrate(p5, l19) == Catch::Approx(1971. / 25));
+    REQUIRE(integrate(p6, l19) == Catch::Approx(60441. / 175));
+    REQUIRE(integrate(p7, l19) == Catch::Approx(-1307119. / 525));
+    REQUIRE(integrate(p8, l19) == Catch::Approx(957697. / 175));
+    REQUIRE(integrate(p9, l19) == Catch::Approx(-196981. / 5250));
+    REQUIRE(integrate(p10, l19) == Catch::Approx(78447601. / 577500));
+    REQUIRE(integrate(p11, l19) == Catch::Approx(673235482069. / 86625000));
+    REQUIRE(integrate(p12, l19) == Catch::Approx(-4092600398251. / 303187500));
+    REQUIRE(integrate(p13, l19) == Catch::Approx(77231697272647. / 5053125000));
+    REQUIRE(integrate(p14, l19) == Catch::Approx(46574962939049. / 9384375000));
+    REQUIRE(integrate(p15, l19) == Catch::Approx(-4818864487842259. / 1094843750000));
+    REQUIRE(integrate(p16, l19) == Catch::Approx(-89885697514686141. / 23265429687500));
+    REQUIRE(integrate(p17, l19) == Catch::Approx(16706355156097391. / 12085937500000));
+    REQUIRE(integrate(p18, l19) == Catch::Approx(495866230514635109957. / 397838847656250000.));
+    REQUIRE(integrate(p19, l19) == Catch::Approx(703712939580204375319. / 132612949218750000.));
+    REQUIRE(integrate(p20, l19) != Catch::Approx(-891851528496270127477. / 884086328125000000.).epsilon(1E-8));
+
+    REQUIRE(get_order(p20, l19, -891851528496270127477. / 884086328125000000.) == Catch::Approx(20).margin(0.001));
+  }
+
+  SECTION("degree 20 and 21")
+  {
+    const QuadratureFormula<2>& l20 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(20));
+    const QuadratureFormula<2>& l21 = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(21));
+
+    REQUIRE(&l20 == &l21);
+
+    REQUIRE(l21.numberOfPoints() == 85);
+
+    REQUIRE(integrate(p0, l21) == Catch::Approx(8));
+    REQUIRE(integrate(p1, l21) == Catch::Approx(4));
+    REQUIRE(integrate(p2, l21) == Catch::Approx(20. / 3));
+    REQUIRE(integrate(p3, l21) == Catch::Approx(-13));
+    REQUIRE(integrate(p4, l21) == Catch::Approx(-1184. / 15));
+    REQUIRE(integrate(p5, l21) == Catch::Approx(1971. / 25));
+    REQUIRE(integrate(p6, l21) == Catch::Approx(60441. / 175));
+    REQUIRE(integrate(p7, l21) == Catch::Approx(-1307119. / 525));
+    REQUIRE(integrate(p8, l21) == Catch::Approx(957697. / 175));
+    REQUIRE(integrate(p9, l21) == Catch::Approx(-196981. / 5250));
+    REQUIRE(integrate(p10, l21) == Catch::Approx(78447601. / 577500));
+    REQUIRE(integrate(p11, l21) == Catch::Approx(673235482069. / 86625000));
+    REQUIRE(integrate(p12, l21) == Catch::Approx(-4092600398251. / 303187500));
+    REQUIRE(integrate(p13, l21) == Catch::Approx(77231697272647. / 5053125000));
+    REQUIRE(integrate(p14, l21) == Catch::Approx(46574962939049. / 9384375000));
+    REQUIRE(integrate(p15, l21) == Catch::Approx(-4818864487842259. / 1094843750000));
+    REQUIRE(integrate(p16, l21) == Catch::Approx(-89885697514686141. / 23265429687500));
+    REQUIRE(integrate(p17, l21) == Catch::Approx(16706355156097391. / 12085937500000));
+    REQUIRE(integrate(p18, l21) == Catch::Approx(495866230514635109957. / 397838847656250000.));
+    REQUIRE(integrate(p19, l21) == Catch::Approx(703712939580204375319. / 132612949218750000.));
+    REQUIRE(integrate(p20, l21) == Catch::Approx(-891851528496270127477. / 884086328125000000.));
+    REQUIRE(integrate(p21, l21) == Catch::Approx(31710268999580650802107. / 66306474609375000000.));
+    REQUIRE(integrate(p22, l21) != Catch::Approx(-1827205780869627586647799. / 726213769531250000000.).epsilon(1E-8));
+
+    REQUIRE(get_order(p22, l21, -1827205780869627586647799. / 726213769531250000000.) ==
+            Catch::Approx(22).margin(0.01));
+  }
+
+  SECTION("max implemented degree")
+  {
+    REQUIRE(QuadratureManager::instance().maxSquareDegree(QuadratureType::Gauss) == SquareGaussQuadrature::max_degree);
+  }
+
+  SECTION("Access functions")
+  {
+    const QuadratureFormula<2>& quadrature_formula =
+      QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(7));
+
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    REQUIRE(point_list.size() == quadrature_formula.numberOfPoints());
+    REQUIRE(weight_list.size() == quadrature_formula.numberOfPoints());
+
+    for (size_t i = 0; i < quadrature_formula.numberOfPoints(); ++i) {
+      REQUIRE(&point_list[i] == &quadrature_formula.point(i));
+      REQUIRE(&weight_list[i] == &quadrature_formula.weight(i));
+    }
+  }
+}
diff --git a/tests/test_SquareTransformation.cpp b/tests/test_SquareTransformation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a1ead02fc75d25f5ff0194b4157e22497d6005dc
--- /dev/null
+++ b/tests/test_SquareTransformation.cpp
@@ -0,0 +1,273 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussLobattoQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/SquareTransformation.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("SquareTransformation", "[geometry]")
+{
+  SECTION("2D")
+  {
+    using R2 = TinyVector<2>;
+
+    const R2 a_hat = {-1, -1};
+    const R2 b_hat = {+1, -1};
+    const R2 c_hat = {+1, +1};
+    const R2 d_hat = {-1, +1};
+
+    const R2 m_hat = zero;
+
+    const R2 a = {0, 0};
+    const R2 b = {8, -2};
+    const R2 c = {12, 7};
+    const R2 d = {3, 7};
+
+    const R2 m = 0.25 * (a + b + c + d);
+
+    const SquareTransformation<2> t(a, b, c, d);
+
+    SECTION("values")
+    {
+      REQUIRE(t(a_hat)[0] == Catch::Approx(a[0]));
+      REQUIRE(t(a_hat)[1] == Catch::Approx(a[1]));
+
+      REQUIRE(t(b_hat)[0] == Catch::Approx(b[0]));
+      REQUIRE(t(b_hat)[1] == Catch::Approx(b[1]));
+
+      REQUIRE(t(c_hat)[0] == Catch::Approx(c[0]));
+      REQUIRE(t(c_hat)[1] == Catch::Approx(c[1]));
+
+      REQUIRE(t(d_hat)[0] == Catch::Approx(d[0]));
+      REQUIRE(t(d_hat)[1] == Catch::Approx(d[1]));
+
+      REQUIRE(t(m_hat)[0] == Catch::Approx(m[0]));
+      REQUIRE(t(m_hat)[1] == Catch::Approx(m[1]));
+    }
+
+    SECTION("Jacobian determinant")
+    {
+      SECTION("at points")
+      {
+        auto detJ = [](const R2& X) {
+          const double x = X[0];
+          const double y = X[1];
+
+          return 0.25 * ((0.5 * x + 4) * (y + 17) - 0.5 * (x + 7) * (y - 1));
+        };
+
+        REQUIRE(t.jacobianDeterminant(a_hat) == Catch::Approx(detJ(a_hat)));
+        REQUIRE(t.jacobianDeterminant(b_hat) == Catch::Approx(detJ(b_hat)));
+        REQUIRE(t.jacobianDeterminant(c_hat) == Catch::Approx(detJ(c_hat)));
+        REQUIRE(t.jacobianDeterminant(d_hat) == Catch::Approx(detJ(d_hat)));
+
+        REQUIRE(t.jacobianDeterminant(m_hat) == Catch::Approx(detJ(m_hat)));
+      }
+
+      SECTION("Gauss order 1")
+      {
+        // One point is enough in 2d
+        const QuadratureFormula<2>& gauss =
+          QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(1));
+
+        double surface = 0;
+        for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+          surface += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i));
+        }
+
+        // 71.5 is actually the surface of the quadrangle
+        REQUIRE(surface == Catch::Approx(71.5));
+      }
+
+      SECTION("Gauss order 3")
+      {
+        auto p = [](const R2& X) {
+          const double& x = X[0];
+          const double& y = X[1];
+
+          return (2 * x + 3 * y + 2) * (x - 2 * y - 1);
+        };
+
+        // Jacbian determinant is a degree 1 polynomial, so the
+        // following formula is required to reach exactness
+        const QuadratureFormula<2>& gauss =
+          QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(3));
+
+        double integral = 0;
+        for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+          integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(t(gauss.point(i)));
+        }
+
+        REQUIRE(integral == Catch::Approx(-76277. / 24));
+      }
+    }
+  }
+
+  SECTION("degenerate 2D ")
+  {
+    SECTION("2D")
+    {
+      using R2 = TinyVector<2>;
+
+      const R2 a = {1, 2};
+      const R2 b = {3, 1};
+      const R2 c = {2, 5};
+
+      const SquareTransformation<2> t(a, b, c, c);
+
+      auto p = [](const R2& X) {
+        const double x = X[0];
+        const double y = X[1];
+        return 2 * x * x + 3 * x * y + y * y + 3 * y + 1;
+      };
+
+      SECTION("Gauss")
+      {
+        QuadratureFormula<2> qf = QuadratureManager::instance().getSquareFormula(GaussQuadratureDescriptor(2));
+
+        double sum = 0;
+        for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+          R2 xi = qf.point(i);
+          sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi));
+        }
+
+        REQUIRE(sum == Catch::Approx(3437. / 24));
+      }
+
+      SECTION("Gauss Legendre")
+      {
+        QuadratureFormula<2> qf = QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(2));
+
+        double sum = 0;
+        for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+          R2 xi = qf.point(i);
+          sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi));
+        }
+
+        REQUIRE(sum == Catch::Approx(3437. / 24));
+      }
+
+      SECTION("Gauss Lobatto")
+      {
+        QuadratureFormula<2> qf = QuadratureManager::instance().getSquareFormula(GaussLobattoQuadratureDescriptor(2));
+
+        double sum = 0;
+        for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+          R2 xi = qf.point(i);
+          sum += qf.weight(i) * t.jacobianDeterminant(xi) * p(t(xi));
+        }
+
+        REQUIRE(sum == Catch::Approx(3437. / 24));
+      }
+    }
+  }
+
+  SECTION("3D")
+  {
+    using R2 = TinyVector<2>;
+
+    const R2 a_hat = {-1, -1};
+    const R2 b_hat = {+1, -1};
+    const R2 c_hat = {+1, +1};
+    const R2 d_hat = {-1, +1};
+
+    const R2 m_hat = zero;
+
+    using R3 = TinyVector<3>;
+
+    const R3 a = {0, 0, -1};
+    const R3 b = {8, -2, 3};
+    const R3 c = {12, 7, 2};
+    const R3 d = {3, 7, 1};
+
+    const R3 m = 0.25 * (a + b + c + d);
+
+    const SquareTransformation<3> t(a, b, c, d);
+
+    SECTION("values")
+    {
+      REQUIRE(t(a_hat)[0] == Catch::Approx(a[0]));
+      REQUIRE(t(a_hat)[1] == Catch::Approx(a[1]));
+      REQUIRE(t(a_hat)[2] == Catch::Approx(a[2]));
+
+      REQUIRE(t(b_hat)[0] == Catch::Approx(b[0]));
+      REQUIRE(t(b_hat)[1] == Catch::Approx(b[1]));
+      REQUIRE(t(b_hat)[2] == Catch::Approx(b[2]));
+
+      REQUIRE(t(c_hat)[0] == Catch::Approx(c[0]));
+      REQUIRE(t(c_hat)[1] == Catch::Approx(c[1]));
+      REQUIRE(t(c_hat)[2] == Catch::Approx(c[2]));
+
+      REQUIRE(t(d_hat)[0] == Catch::Approx(d[0]));
+      REQUIRE(t(d_hat)[1] == Catch::Approx(d[1]));
+      REQUIRE(t(d_hat)[2] == Catch::Approx(d[2]));
+
+      REQUIRE(t(m_hat)[0] == Catch::Approx(m[0]));
+      REQUIRE(t(m_hat)[1] == Catch::Approx(m[1]));
+      REQUIRE(t(m_hat)[2] == Catch::Approx(m[2]));
+    }
+
+    SECTION("Area variation norm")
+    {
+      auto area_variation_norm = [&](const R2& X) {
+        const double x = X[0];
+        const double y = X[1];
+
+        const R3 J1 = 0.25 * (-a + b + c - d);
+        const R3 J2 = 0.25 * (-a - b + c + d);
+        const R3 J3 = 0.25 * (a - b + c - d);
+
+        return l2Norm(crossProduct(J1 + y * J3, J2 + x * J3));
+      };
+
+      SECTION("at points")
+      {
+        REQUIRE(t.areaVariationNorm(a_hat) == Catch::Approx(area_variation_norm(a_hat)));
+        REQUIRE(t.areaVariationNorm(b_hat) == Catch::Approx(area_variation_norm(b_hat)));
+        REQUIRE(t.areaVariationNorm(c_hat) == Catch::Approx(area_variation_norm(c_hat)));
+        REQUIRE(t.areaVariationNorm(d_hat) == Catch::Approx(area_variation_norm(d_hat)));
+
+        REQUIRE(t.areaVariationNorm(m_hat) == Catch::Approx(area_variation_norm(m_hat)));
+      }
+
+      auto p = [](const R3& X) {
+        const double x = X[0];
+        const double y = X[1];
+        const double z = X[2];
+        return 2 * x * x + 3 * x - 3 * y * y + y + 2 * z * z - 0.5 * z + 2;
+      };
+
+      SECTION("Gauss order 3")
+      {
+        // Due to the area variation term (square root of a
+        // polynomial), Gauss-like quadrature cannot be exact for
+        // general 3d quadrangle.
+        //
+        // Moreover, even the surface of general 3d quadrangle cannot
+        // be computed exactly.
+        //
+        // So for this test we integrate the following function:
+        // f(x,y,z) := p(x,y,z) * |dA(t^-1(x,y,z))|
+        //
+        // dA denotes the area change on the reference element. This
+        // function can be exactly integrated since computing the
+        // integral on the reference quadrangle, one gets a dA^2,
+        // which is a polynomial.
+        const QuadratureFormula<2>& gauss =
+          QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(4));
+
+        double integral = 0;
+        for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
+          const double fX = (t.areaVariationNorm(gauss.point(i)) * p(t(gauss.point(i))));
+          integral += gauss.weight(i) * t.areaVariationNorm(gauss.point(i)) * fX;
+        }
+
+        REQUIRE(integral == Catch::Approx(60519715. / 576));
+      }
+    }
+  }
+}
diff --git a/tests/test_SubItemArrayPerItem.cpp b/tests/test_SubItemArrayPerItem.cpp
index d1cb315046b5450e975b7ced5e795103bb6453ad..c72f98b76de01f6562aac21c5cc008716606a0a5 100644
--- a/tests/test_SubItemArrayPerItem.cpp
+++ b/tests/test_SubItemArrayPerItem.cpp
@@ -64,457 +64,483 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
-      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      SECTION("per cell")
-      {
-        NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
-        REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(node_array_per_cell.numberOfArrays() == number_of_arrays(node_array_per_cell));
-        REQUIRE(node_array_per_cell.sizeOfArrays() == 3);
-
-        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &=
-              (cell_to_node_matrix[cell_id].size() == node_array_per_cell.numberOfSubArrays(cell_id)) and
-              (node_array_per_cell.itemTable(cell_id).numberOfRows() == node_array_per_cell.numberOfSubArrays(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        const NodeArrayPerCell<const int> const_node_array_per_cell = node_array_per_cell;
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (const_node_array_per_cell.itemTable(cell_id).numberOfRows() ==
-                           node_array_per_cell.numberOfSubArrays(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
-        REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(edge_array_per_cell.numberOfArrays() == number_of_arrays(edge_array_per_cell));
-        REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          SECTION("per cell")
+          {
+            NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
+            REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(node_array_per_cell.numberOfArrays() == number_of_arrays(node_array_per_cell));
+            REQUIRE(node_array_per_cell.sizeOfArrays() == 3);
+
+            auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &=
+                  (cell_to_node_matrix[cell_id].size() == node_array_per_cell.numberOfSubArrays(cell_id)) and
+                  (node_array_per_cell.itemTable(cell_id).numberOfRows() ==
+                   node_array_per_cell.numberOfSubArrays(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_array_per_cell.numberOfSubArrays(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
+            const NodeArrayPerCell<const int> const_node_array_per_cell = node_array_per_cell;
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (const_node_array_per_cell.itemTable(cell_id).numberOfRows() ==
+                               node_array_per_cell.numberOfSubArrays(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        FaceArrayPerCell<int> face_array_per_cell{connectivity, 2};
-        REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(face_array_per_cell.numberOfArrays() == number_of_arrays(face_array_per_cell));
-        REQUIRE(face_array_per_cell.sizeOfArrays() == 2);
+            EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
+            REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(edge_array_per_cell.numberOfArrays() == number_of_arrays(edge_array_per_cell));
+            REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
+
+            auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_array_per_cell.numberOfSubArrays(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_face_matrix[cell_id].size() == face_array_per_cell.numberOfSubArrays(cell_id));
+            FaceArrayPerCell<int> face_array_per_cell{connectivity, 2};
+            REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(face_array_per_cell.numberOfArrays() == number_of_arrays(face_array_per_cell));
+            REQUIRE(face_array_per_cell.sizeOfArrays() == 2);
+
+            auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_face_matrix[cell_id].size() == face_array_per_cell.numberOfSubArrays(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
-        }
-      }
 
-      SECTION("per face")
-      {
-        CellArrayPerFace<int> cell_array_per_face{connectivity, 2};
-        REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(cell_array_per_face.numberOfArrays() == number_of_arrays(cell_array_per_face));
-        REQUIRE(cell_array_per_face.sizeOfArrays() == 2);
-
-        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
-        {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_cell_matrix[face_id].size() == cell_array_per_face.numberOfSubArrays(face_id));
+          SECTION("per face")
+          {
+            CellArrayPerFace<int> cell_array_per_face{connectivity, 2};
+            REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(cell_array_per_face.numberOfArrays() == number_of_arrays(cell_array_per_face));
+            REQUIRE(cell_array_per_face.sizeOfArrays() == 2);
+
+            auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_cell_matrix[face_id].size() == cell_array_per_face.numberOfSubArrays(face_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
-        }
-      }
-
-      SECTION("per edge")
-      {
-        CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
-        REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(cell_array_per_edge.numberOfArrays() == number_of_arrays(cell_array_per_edge));
-        REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
 
-        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
-        {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_array_per_edge.numberOfSubArrays(edge_id));
+          SECTION("per edge")
+          {
+            CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
+            REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(cell_array_per_edge.numberOfArrays() == number_of_arrays(cell_array_per_edge));
+            REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
+
+            auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_array_per_edge.numberOfSubArrays(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
-        }
-      }
-
-      SECTION("per node")
-      {
-        CellArrayPerNode<int> cell_array_per_node{connectivity, 4};
-        REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(cell_array_per_node.numberOfArrays() == number_of_arrays(cell_array_per_node));
-        REQUIRE(cell_array_per_node.sizeOfArrays() == 4);
 
-        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_cell_matrix[node_id].size() == cell_array_per_node.numberOfSubArrays(node_id));
+          SECTION("per node")
+          {
+            CellArrayPerNode<int> cell_array_per_node{connectivity, 4};
+            REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(cell_array_per_node.numberOfArrays() == number_of_arrays(cell_array_per_node));
+            REQUIRE(cell_array_per_node.sizeOfArrays() == 4);
+
+            auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_cell_matrix[node_id].size() == cell_array_per_node.numberOfSubArrays(node_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
         }
       }
     }
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
-
-      SECTION("per cell")
-      {
-        NodeArrayPerCell<int> node_array_per_cell{connectivity, 5};
-        REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(node_array_per_cell.numberOfArrays() == number_of_arrays(node_array_per_cell));
-        REQUIRE(node_array_per_cell.sizeOfArrays() == 5);
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_node_matrix[cell_id].size() == node_array_per_cell.numberOfSubArrays(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
-        REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(edge_array_per_cell.numberOfArrays() == number_of_arrays(edge_array_per_cell));
-        REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
-
-        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_array_per_cell.numberOfSubArrays(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
-        REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(face_array_per_cell.numberOfArrays() == number_of_arrays(face_array_per_cell));
-        REQUIRE(face_array_per_cell.sizeOfArrays() == 3);
-
-        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_face_matrix[cell_id].size() == face_array_per_cell.numberOfSubArrays(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-      }
-
-      SECTION("per face")
-      {
-        CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
-        REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(cell_array_per_face.numberOfArrays() == number_of_arrays(cell_array_per_face));
-        REQUIRE(cell_array_per_face.sizeOfArrays() == 3);
-
-        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
-        {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_cell_matrix[face_id].size() == cell_array_per_face.numberOfSubArrays(face_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        NodeArrayPerFace<int> node_array_per_face{connectivity, 2};
-        REQUIRE(node_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(node_array_per_face.numberOfArrays() == number_of_arrays(node_array_per_face));
-        REQUIRE(node_array_per_face.sizeOfArrays() == 2);
-
-        auto face_to_node_matrix = connectivity.faceToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_node_matrix[face_id].size() == node_array_per_face.numberOfSubArrays(face_id));
-          }
-          REQUIRE(is_correct);
-        }
-      }
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          SECTION("per cell")
+          {
+            NodeArrayPerCell<int> node_array_per_cell{connectivity, 5};
+            REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(node_array_per_cell.numberOfArrays() == number_of_arrays(node_array_per_cell));
+            REQUIRE(node_array_per_cell.sizeOfArrays() == 5);
+
+            auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_node_matrix[cell_id].size() == node_array_per_cell.numberOfSubArrays(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-      SECTION("per edge")
-      {
-        CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
-        REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(cell_array_per_edge.numberOfArrays() == number_of_arrays(cell_array_per_edge));
-        REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
+            EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
+            REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(edge_array_per_cell.numberOfArrays() == number_of_arrays(edge_array_per_cell));
+            REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
+
+            auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_array_per_cell.numberOfSubArrays(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
-        {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_array_per_edge.numberOfSubArrays(edge_id));
+            FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
+            REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(face_array_per_cell.numberOfArrays() == number_of_arrays(face_array_per_cell));
+            REQUIRE(face_array_per_cell.sizeOfArrays() == 3);
+
+            auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_face_matrix[cell_id].size() == face_array_per_cell.numberOfSubArrays(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
-        }
 
-        NodeArrayPerEdge<int> node_array_per_edge{connectivity, 5};
-        REQUIRE(node_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(node_array_per_edge.numberOfArrays() == number_of_arrays(node_array_per_edge));
-        REQUIRE(node_array_per_edge.sizeOfArrays() == 5);
+          SECTION("per face")
+          {
+            CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
+            REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(cell_array_per_face.numberOfArrays() == number_of_arrays(cell_array_per_face));
+            REQUIRE(cell_array_per_face.sizeOfArrays() == 3);
+
+            auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_cell_matrix[face_id].size() == cell_array_per_face.numberOfSubArrays(face_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_node_matrix[edge_id].size() == node_array_per_edge.numberOfSubArrays(edge_id));
+            NodeArrayPerFace<int> node_array_per_face{connectivity, 2};
+            REQUIRE(node_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(node_array_per_face.numberOfArrays() == number_of_arrays(node_array_per_face));
+            REQUIRE(node_array_per_face.sizeOfArrays() == 2);
+
+            auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_node_matrix[face_id].size() == node_array_per_face.numberOfSubArrays(face_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
-        }
-      }
 
-      SECTION("per node")
-      {
-        EdgeArrayPerNode<int> edge_array_per_node{connectivity, 4};
-        REQUIRE(edge_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(edge_array_per_node.numberOfArrays() == number_of_arrays(edge_array_per_node));
-        REQUIRE(edge_array_per_node.sizeOfArrays() == 4);
+          SECTION("per edge")
+          {
+            CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
+            REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(cell_array_per_edge.numberOfArrays() == number_of_arrays(cell_array_per_edge));
+            REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
+
+            auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_array_per_edge.numberOfSubArrays(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_edge_matrix[node_id].size() == edge_array_per_node.numberOfSubArrays(node_id));
+            NodeArrayPerEdge<int> node_array_per_edge{connectivity, 5};
+            REQUIRE(node_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(node_array_per_edge.numberOfArrays() == number_of_arrays(node_array_per_edge));
+            REQUIRE(node_array_per_edge.sizeOfArrays() == 5);
+
+            auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_node_matrix[edge_id].size() == node_array_per_edge.numberOfSubArrays(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
-        }
-
-        FaceArrayPerNode<int> face_array_per_node{connectivity, 3};
-        REQUIRE(face_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(face_array_per_node.numberOfArrays() == number_of_arrays(face_array_per_node));
-        REQUIRE(face_array_per_node.sizeOfArrays() == 3);
 
-        auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_face_matrix[node_id].size() == face_array_per_node.numberOfSubArrays(node_id));
-          }
-          REQUIRE(is_correct);
-        }
+          SECTION("per node")
+          {
+            EdgeArrayPerNode<int> edge_array_per_node{connectivity, 4};
+            REQUIRE(edge_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(edge_array_per_node.numberOfArrays() == number_of_arrays(edge_array_per_node));
+            REQUIRE(edge_array_per_node.sizeOfArrays() == 4);
+
+            auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_edge_matrix[node_id].size() == edge_array_per_node.numberOfSubArrays(node_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        CellArrayPerNode<int> cell_array_per_node{connectivity, 2};
-        REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(cell_array_per_node.numberOfArrays() == number_of_arrays(cell_array_per_node));
-        REQUIRE(cell_array_per_node.sizeOfArrays() == 2);
+            FaceArrayPerNode<int> face_array_per_node{connectivity, 3};
+            REQUIRE(face_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(face_array_per_node.numberOfArrays() == number_of_arrays(face_array_per_node));
+            REQUIRE(face_array_per_node.sizeOfArrays() == 3);
+
+            auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_face_matrix[node_id].size() == face_array_per_node.numberOfSubArrays(node_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_cell_matrix[node_id].size() == cell_array_per_node.numberOfSubArrays(node_id));
+            CellArrayPerNode<int> cell_array_per_node{connectivity, 2};
+            REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(cell_array_per_node.numberOfArrays() == number_of_arrays(cell_array_per_node));
+            REQUIRE(cell_array_per_node.sizeOfArrays() == 2);
+
+            auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_cell_matrix[node_id].size() == cell_array_per_node.numberOfSubArrays(node_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
         }
       }
     }
+
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
-
-      SECTION("per cell")
-      {
-        NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
-        REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(node_array_per_cell.numberOfArrays() == number_of_arrays(node_array_per_cell));
-        REQUIRE(node_array_per_cell.sizeOfArrays() == 3);
-
-        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_node_matrix[cell_id].size() == node_array_per_cell.numberOfSubArrays(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
-        REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(edge_array_per_cell.numberOfArrays() == number_of_arrays(edge_array_per_cell));
-        REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
-
-        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_array_per_cell.numberOfSubArrays(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
-        REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(face_array_per_cell.numberOfArrays() == number_of_arrays(face_array_per_cell));
-        REQUIRE(face_array_per_cell.sizeOfArrays() == 3);
-
-        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_face_matrix[cell_id].size() == face_array_per_cell.numberOfSubArrays(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-      }
-
-      SECTION("per face")
-      {
-        CellArrayPerFace<int> cell_array_per_face{connectivity, 5};
-        REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(cell_array_per_face.numberOfArrays() == number_of_arrays(cell_array_per_face));
-        REQUIRE(cell_array_per_face.sizeOfArrays() == 5);
-
-        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
-        {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_cell_matrix[face_id].size() == cell_array_per_face.numberOfSubArrays(face_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        EdgeArrayPerFace<int> edge_array_per_face{connectivity, 3};
-        REQUIRE(edge_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(edge_array_per_face.numberOfArrays() == number_of_arrays(edge_array_per_face));
-        REQUIRE(edge_array_per_face.sizeOfArrays() == 3);
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-        auto face_to_edge_matrix = connectivity.faceToEdgeMatrix();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_edge_matrix[face_id].size() == edge_array_per_face.numberOfSubArrays(face_id));
-          }
-          REQUIRE(is_correct);
-        }
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          SECTION("per cell")
+          {
+            NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
+            REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(node_array_per_cell.numberOfArrays() == number_of_arrays(node_array_per_cell));
+            REQUIRE(node_array_per_cell.sizeOfArrays() == 3);
+
+            auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_node_matrix[cell_id].size() == node_array_per_cell.numberOfSubArrays(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        NodeArrayPerFace<int> node_array_per_face{connectivity, 2};
-        REQUIRE(node_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(node_array_per_face.numberOfArrays() == number_of_arrays(node_array_per_face));
-        REQUIRE(node_array_per_face.sizeOfArrays() == 2);
+            EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
+            REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(edge_array_per_cell.numberOfArrays() == number_of_arrays(edge_array_per_cell));
+            REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
+
+            auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_array_per_cell.numberOfSubArrays(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        auto face_to_node_matrix = connectivity.faceToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_node_matrix[face_id].size() == node_array_per_face.numberOfSubArrays(face_id));
+            FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
+            REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(face_array_per_cell.numberOfArrays() == number_of_arrays(face_array_per_cell));
+            REQUIRE(face_array_per_cell.sizeOfArrays() == 3);
+
+            auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_face_matrix[cell_id].size() == face_array_per_cell.numberOfSubArrays(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
-        }
-      }
-
-      SECTION("per edge")
-      {
-        CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
-        REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(cell_array_per_edge.numberOfArrays() == number_of_arrays(cell_array_per_edge));
-        REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
 
-        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
-        {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_array_per_edge.numberOfSubArrays(edge_id));
-          }
-          REQUIRE(is_correct);
-        }
+          SECTION("per face")
+          {
+            CellArrayPerFace<int> cell_array_per_face{connectivity, 5};
+            REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(cell_array_per_face.numberOfArrays() == number_of_arrays(cell_array_per_face));
+            REQUIRE(cell_array_per_face.sizeOfArrays() == 5);
+
+            auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_cell_matrix[face_id].size() == cell_array_per_face.numberOfSubArrays(face_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        FaceArrayPerEdge<int> face_array_per_edge{connectivity, 5};
-        REQUIRE(face_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(face_array_per_edge.numberOfArrays() == number_of_arrays(face_array_per_edge));
-        REQUIRE(face_array_per_edge.sizeOfArrays() == 5);
+            EdgeArrayPerFace<int> edge_array_per_face{connectivity, 3};
+            REQUIRE(edge_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(edge_array_per_face.numberOfArrays() == number_of_arrays(edge_array_per_face));
+            REQUIRE(edge_array_per_face.sizeOfArrays() == 3);
+
+            auto face_to_edge_matrix = connectivity.faceToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_edge_matrix[face_id].size() == edge_array_per_face.numberOfSubArrays(face_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        auto edge_to_face_matrix = connectivity.edgeToFaceMatrix();
-        {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_face_matrix[edge_id].size() == face_array_per_edge.numberOfSubArrays(edge_id));
+            NodeArrayPerFace<int> node_array_per_face{connectivity, 2};
+            REQUIRE(node_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(node_array_per_face.numberOfArrays() == number_of_arrays(node_array_per_face));
+            REQUIRE(node_array_per_face.sizeOfArrays() == 2);
+
+            auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_node_matrix[face_id].size() == node_array_per_face.numberOfSubArrays(face_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
-        }
-
-        NodeArrayPerEdge<int> node_array_per_edge{connectivity, 3};
-        REQUIRE(node_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(node_array_per_edge.numberOfArrays() == number_of_arrays(node_array_per_edge));
-        REQUIRE(node_array_per_edge.sizeOfArrays() == 3);
 
-        auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_node_matrix[edge_id].size() == node_array_per_edge.numberOfSubArrays(edge_id));
-          }
-          REQUIRE(is_correct);
-        }
-      }
+          SECTION("per edge")
+          {
+            CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
+            REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(cell_array_per_edge.numberOfArrays() == number_of_arrays(cell_array_per_edge));
+            REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
+
+            auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_array_per_edge.numberOfSubArrays(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-      SECTION("per node")
-      {
-        EdgeArrayPerNode<int> edge_array_per_node{connectivity, 3};
-        REQUIRE(edge_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(edge_array_per_node.numberOfArrays() == number_of_arrays(edge_array_per_node));
-        REQUIRE(edge_array_per_node.sizeOfArrays() == 3);
+            FaceArrayPerEdge<int> face_array_per_edge{connectivity, 5};
+            REQUIRE(face_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(face_array_per_edge.numberOfArrays() == number_of_arrays(face_array_per_edge));
+            REQUIRE(face_array_per_edge.sizeOfArrays() == 5);
+
+            auto edge_to_face_matrix = connectivity.edgeToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_face_matrix[edge_id].size() == face_array_per_edge.numberOfSubArrays(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_edge_matrix[node_id].size() == edge_array_per_node.numberOfSubArrays(node_id));
+            NodeArrayPerEdge<int> node_array_per_edge{connectivity, 3};
+            REQUIRE(node_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(node_array_per_edge.numberOfArrays() == number_of_arrays(node_array_per_edge));
+            REQUIRE(node_array_per_edge.sizeOfArrays() == 3);
+
+            auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_node_matrix[edge_id].size() == node_array_per_edge.numberOfSubArrays(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
-        }
-
-        FaceArrayPerNode<int> face_array_per_node{connectivity, 4};
-        REQUIRE(face_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(face_array_per_node.numberOfArrays() == number_of_arrays(face_array_per_node));
-        REQUIRE(face_array_per_node.sizeOfArrays() == 4);
 
-        auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_face_matrix[node_id].size() == face_array_per_node.numberOfSubArrays(node_id));
-          }
-          REQUIRE(is_correct);
-        }
+          SECTION("per node")
+          {
+            EdgeArrayPerNode<int> edge_array_per_node{connectivity, 3};
+            REQUIRE(edge_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(edge_array_per_node.numberOfArrays() == number_of_arrays(edge_array_per_node));
+            REQUIRE(edge_array_per_node.sizeOfArrays() == 3);
+
+            auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_edge_matrix[node_id].size() == edge_array_per_node.numberOfSubArrays(node_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        CellArrayPerNode<int> cell_array_per_node{connectivity, 5};
-        REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(cell_array_per_node.numberOfArrays() == number_of_arrays(cell_array_per_node));
-        REQUIRE(cell_array_per_node.sizeOfArrays() == 5);
+            FaceArrayPerNode<int> face_array_per_node{connectivity, 4};
+            REQUIRE(face_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(face_array_per_node.numberOfArrays() == number_of_arrays(face_array_per_node));
+            REQUIRE(face_array_per_node.sizeOfArrays() == 4);
+
+            auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_face_matrix[node_id].size() == face_array_per_node.numberOfSubArrays(node_id));
+              }
+              REQUIRE(is_correct);
+            }
 
-        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_cell_matrix[node_id].size() == cell_array_per_node.numberOfSubArrays(node_id));
+            CellArrayPerNode<int> cell_array_per_node{connectivity, 5};
+            REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(cell_array_per_node.numberOfArrays() == number_of_arrays(cell_array_per_node));
+            REQUIRE(cell_array_per_node.sizeOfArrays() == 5);
+
+            auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_cell_matrix[node_id].size() == cell_array_per_node.numberOfSubArrays(node_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
         }
       }
     }
@@ -524,325 +550,365 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
-      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      EdgeArrayPerCell<size_t> edge_arrays_per_cell{connectivity, 3};
-      {
-        size_t array = 0;
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          for (size_t i_edge = 0; i_edge < edge_arrays_per_cell.numberOfSubArrays(cell_id); ++i_edge) {
-            for (size_t i = 0; i < edge_arrays_per_cell(cell_id, i_edge).size(); ++i) {
-              edge_arrays_per_cell(cell_id, i_edge)[i] = array++;
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          EdgeArrayPerCell<size_t> edge_arrays_per_cell{connectivity, 3};
+          {
+            size_t array = 0;
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              for (size_t i_edge = 0; i_edge < edge_arrays_per_cell.numberOfSubArrays(cell_id); ++i_edge) {
+                for (size_t i = 0; i < edge_arrays_per_cell(cell_id, i_edge).size(); ++i) {
+                  edge_arrays_per_cell(cell_id, i_edge)[i] = array++;
+                }
+              }
             }
           }
-        }
-      }
-      {
-        bool is_same = true;
-        size_t k     = 0;
-        for (size_t i = 0; i < edge_arrays_per_cell.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < edge_arrays_per_cell.sizeOfArrays(); ++j, ++k) {
-            is_same &= (edge_arrays_per_cell[i][j] == k);
+          {
+            bool is_same = true;
+            size_t k     = 0;
+            for (size_t i = 0; i < edge_arrays_per_cell.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < edge_arrays_per_cell.sizeOfArrays(); ++j, ++k) {
+                is_same &= (edge_arrays_per_cell[i][j] == k);
+              }
+            }
+            REQUIRE(is_same);
           }
-        }
-        REQUIRE(is_same);
-      }
 
-      {
-        size_t k = 0;
-        for (size_t i = 0; i < edge_arrays_per_cell.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < edge_arrays_per_cell.sizeOfArrays(); ++j, ++k) {
-            edge_arrays_per_cell[i][j] = k * k + 1;
+          {
+            size_t k = 0;
+            for (size_t i = 0; i < edge_arrays_per_cell.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < edge_arrays_per_cell.sizeOfArrays(); ++j, ++k) {
+                edge_arrays_per_cell[i][j] = k * k + 1;
+              }
+            }
           }
-        }
-      }
-      {
-        bool is_same = true;
-        size_t i     = 0;
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          for (size_t i_edge = 0; i_edge < edge_arrays_per_cell.numberOfSubArrays(cell_id); ++i_edge) {
-            for (size_t l = 0; l < edge_arrays_per_cell(cell_id, i_edge).size(); ++l, ++i) {
-              is_same &= (edge_arrays_per_cell(cell_id, i_edge)[l] == i * i + 1);
+          {
+            bool is_same = true;
+            size_t i     = 0;
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              for (size_t i_edge = 0; i_edge < edge_arrays_per_cell.numberOfSubArrays(cell_id); ++i_edge) {
+                for (size_t l = 0; l < edge_arrays_per_cell(cell_id, i_edge).size(); ++l, ++i) {
+                  is_same &= (edge_arrays_per_cell(cell_id, i_edge)[l] == i * i + 1);
+                }
+              }
             }
+            REQUIRE(is_same);
           }
-        }
-        REQUIRE(is_same);
-      }
 
-      const EdgeArrayPerCell<const size_t> const_edge_arrays_per_cell = edge_arrays_per_cell;
-      {
-        bool is_same = true;
-        size_t i     = 0;
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          const auto& cell_table = const_edge_arrays_per_cell.itemTable(cell_id);
-          for (size_t i_edge = 0; i_edge < cell_table.numberOfRows(); ++i_edge) {
-            const auto& array = cell_table[i_edge];
-            for (size_t l = 0; l < array.size(); ++l, ++i) {
-              is_same &= (array[l] == i * i + 1);
+          const EdgeArrayPerCell<const size_t> const_edge_arrays_per_cell = edge_arrays_per_cell;
+          {
+            bool is_same = true;
+            size_t i     = 0;
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              const auto& cell_table = const_edge_arrays_per_cell.itemTable(cell_id);
+              for (size_t i_edge = 0; i_edge < cell_table.numberOfRows(); ++i_edge) {
+                const auto& array = cell_table[i_edge];
+                for (size_t l = 0; l < array.size(); ++l, ++i) {
+                  is_same &= (array[l] == i * i + 1);
+                }
+              }
             }
+            REQUIRE(is_same);
           }
         }
-        REQUIRE(is_same);
       }
     }
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      CellArrayPerFace<size_t> cell_arrays_per_face{connectivity, 3};
-      {
-        size_t array = 0;
-        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-          for (size_t i_cell = 0; i_cell < cell_arrays_per_face.numberOfSubArrays(face_id); ++i_cell) {
-            for (size_t i = 0; i < cell_arrays_per_face(face_id, i_cell).size(); ++i) {
-              cell_arrays_per_face(face_id, i_cell)[i] = array++;
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          CellArrayPerFace<size_t> cell_arrays_per_face{connectivity, 3};
+          {
+            size_t array = 0;
+            for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+              for (size_t i_cell = 0; i_cell < cell_arrays_per_face.numberOfSubArrays(face_id); ++i_cell) {
+                for (size_t i = 0; i < cell_arrays_per_face(face_id, i_cell).size(); ++i) {
+                  cell_arrays_per_face(face_id, i_cell)[i] = array++;
+                }
+              }
             }
           }
-        }
-      }
-      {
-        bool is_same = true;
-        size_t k     = 0;
-        for (size_t i = 0; i < cell_arrays_per_face.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < cell_arrays_per_face.sizeOfArrays(); ++j, ++k) {
-            is_same &= (cell_arrays_per_face[i][j] == k);
+          {
+            bool is_same = true;
+            size_t k     = 0;
+            for (size_t i = 0; i < cell_arrays_per_face.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < cell_arrays_per_face.sizeOfArrays(); ++j, ++k) {
+                is_same &= (cell_arrays_per_face[i][j] == k);
+              }
+            }
+            REQUIRE(is_same);
           }
-        }
-        REQUIRE(is_same);
-      }
-      {
-        size_t k = 0;
-        for (size_t i = 0; i < cell_arrays_per_face.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < cell_arrays_per_face.sizeOfArrays(); ++j, ++k) {
-            cell_arrays_per_face[i][j] = 3 * k + 1;
+          {
+            size_t k = 0;
+            for (size_t i = 0; i < cell_arrays_per_face.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < cell_arrays_per_face.sizeOfArrays(); ++j, ++k) {
+                cell_arrays_per_face[i][j] = 3 * k + 1;
+              }
+            }
           }
-        }
-      }
-      {
-        bool is_same = true;
-        size_t i     = 0;
-        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-          for (size_t i_cell = 0; i_cell < cell_arrays_per_face.numberOfSubArrays(face_id); ++i_cell) {
-            for (size_t l = 0; l < cell_arrays_per_face(face_id, i_cell).size(); ++l, ++i) {
-              is_same &= (cell_arrays_per_face(face_id, i_cell)[l] == 3 * i + 1);
+          {
+            bool is_same = true;
+            size_t i     = 0;
+            for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+              for (size_t i_cell = 0; i_cell < cell_arrays_per_face.numberOfSubArrays(face_id); ++i_cell) {
+                for (size_t l = 0; l < cell_arrays_per_face(face_id, i_cell).size(); ++l, ++i) {
+                  is_same &= (cell_arrays_per_face(face_id, i_cell)[l] == 3 * i + 1);
+                }
+              }
             }
+            REQUIRE(is_same);
           }
-        }
-        REQUIRE(is_same);
-      }
 
-      const CellArrayPerFace<const size_t> const_cell_arrays_per_face = cell_arrays_per_face;
-      {
-        bool is_same = true;
-        size_t i     = 0;
-        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-          const auto& face_table = const_cell_arrays_per_face.itemTable(face_id);
-          for (size_t i_cell = 0; i_cell < face_table.numberOfRows(); ++i_cell) {
-            const auto& array = face_table[i_cell];
-            for (size_t l = 0; l < array.size(); ++l, ++i) {
-              is_same &= (array[l] == 3 * i + 1);
+          const CellArrayPerFace<const size_t> const_cell_arrays_per_face = cell_arrays_per_face;
+          {
+            bool is_same = true;
+            size_t i     = 0;
+            for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+              const auto& face_table = const_cell_arrays_per_face.itemTable(face_id);
+              for (size_t i_cell = 0; i_cell < face_table.numberOfRows(); ++i_cell) {
+                const auto& array = face_table[i_cell];
+                for (size_t l = 0; l < array.size(); ++l, ++i) {
+                  is_same &= (array[l] == 3 * i + 1);
+                }
+              }
             }
+            REQUIRE(is_same);
           }
         }
-        REQUIRE(is_same);
       }
     }
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      FaceArrayPerNode<size_t> face_arrays_per_node{connectivity, 3};
-      {
-        size_t array = 0;
-        for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-          for (size_t i_face = 0; i_face < face_arrays_per_node.numberOfSubArrays(node_id); ++i_face) {
-            for (size_t i = 0; i < face_arrays_per_node(node_id, i_face).size(); ++i)
-              face_arrays_per_node(node_id, i_face)[i] = array++;
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          FaceArrayPerNode<size_t> face_arrays_per_node{connectivity, 3};
+          {
+            size_t array = 0;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              for (size_t i_face = 0; i_face < face_arrays_per_node.numberOfSubArrays(node_id); ++i_face) {
+                for (size_t i = 0; i < face_arrays_per_node(node_id, i_face).size(); ++i)
+                  face_arrays_per_node(node_id, i_face)[i] = array++;
+              }
+            }
           }
-        }
-      }
-      {
-        bool is_same = true;
-        size_t k     = 0;
-        for (size_t i = 0; i < face_arrays_per_node.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < face_arrays_per_node.sizeOfArrays(); ++j, ++k) {
-            is_same &= (face_arrays_per_node[i][j] == k);
+          {
+            bool is_same = true;
+            size_t k     = 0;
+            for (size_t i = 0; i < face_arrays_per_node.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < face_arrays_per_node.sizeOfArrays(); ++j, ++k) {
+                is_same &= (face_arrays_per_node[i][j] == k);
+              }
+              REQUIRE(is_same);
+            }
           }
-          REQUIRE(is_same);
-        }
-      }
-      {
-        size_t k = 0;
-        for (size_t i = 0; i < face_arrays_per_node.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < face_arrays_per_node.sizeOfArrays(); ++j, ++k) {
-            face_arrays_per_node[i][j] = 3 + k * k;
+          {
+            size_t k = 0;
+            for (size_t i = 0; i < face_arrays_per_node.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < face_arrays_per_node.sizeOfArrays(); ++j, ++k) {
+                face_arrays_per_node[i][j] = 3 + k * k;
+              }
+            }
           }
-        }
-      }
-      {
-        bool is_same = true;
-        size_t i     = 0;
-        for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-          for (size_t i_face = 0; i_face < face_arrays_per_node.numberOfSubArrays(node_id); ++i_face) {
-            for (size_t l = 0; l < face_arrays_per_node(node_id, i_face).size(); ++l, ++i) {
-              is_same &= (face_arrays_per_node(node_id, i_face)[l] == 3 + i * i);
+          {
+            bool is_same = true;
+            size_t i     = 0;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              for (size_t i_face = 0; i_face < face_arrays_per_node.numberOfSubArrays(node_id); ++i_face) {
+                for (size_t l = 0; l < face_arrays_per_node(node_id, i_face).size(); ++l, ++i) {
+                  is_same &= (face_arrays_per_node(node_id, i_face)[l] == 3 + i * i);
+                }
+              }
             }
+            REQUIRE(is_same);
           }
-        }
-        REQUIRE(is_same);
-      }
 
-      const FaceArrayPerNode<const size_t> const_face_arrays_per_node = face_arrays_per_node;
-      {
-        bool is_same = true;
-        size_t i     = 0;
-        for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-          const auto& node_table = const_face_arrays_per_node.itemTable(node_id);
-          for (size_t i_face = 0; i_face < node_table.numberOfRows(); ++i_face) {
-            const auto& array = node_table[i_face];
-            for (size_t l = 0; l < array.size(); ++l, ++i) {
-              is_same &= (array[l] == 3 + i * i);
+          const FaceArrayPerNode<const size_t> const_face_arrays_per_node = face_arrays_per_node;
+          {
+            bool is_same = true;
+            size_t i     = 0;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              const auto& node_table = const_face_arrays_per_node.itemTable(node_id);
+              for (size_t i_face = 0; i_face < node_table.numberOfRows(); ++i_face) {
+                const auto& array = node_table[i_face];
+                for (size_t l = 0; l < array.size(); ++l, ++i) {
+                  is_same &= (array[l] == 3 + i * i);
+                }
+              }
             }
+            REQUIRE(is_same);
           }
         }
-        REQUIRE(is_same);
       }
     }
   }
 
   SECTION("copy")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    SECTION("classic")
-    {
-      NodeArrayPerCell<size_t> node_array_per_cell{connectivity, 3};
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
       {
-        size_t k = 0;
-        for (size_t i = 0; i < node_array_per_cell.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j, ++k) {
-            node_array_per_cell[i][j] = k;
-          }
-        }
-      }
-      NodeArrayPerCell<size_t> copy_node_array_per_cell = copy(node_array_per_cell);
-      {
-        bool is_same = true;
-        for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j) {
-            is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
-          }
-        }
+        auto mesh_3d = named_mesh.mesh();
 
-        REQUIRE(is_same);
-      }
+        const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-      {
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          auto cell_table = node_array_per_cell.itemTable(cell_id);
-          for (size_t i_node = 0; i_node < node_array_per_cell.numberOfSubArrays(cell_id); ++i_node) {
-            auto node_array = cell_table[i_node];
-            for (size_t i = 0; i < node_array.size(); ++i) {
-              node_array[i] = cell_id + i_node + i;
+        SECTION("classic")
+        {
+          NodeArrayPerCell<size_t> node_array_per_cell{connectivity, 3};
+          {
+            size_t k = 0;
+            for (size_t i = 0; i < node_array_per_cell.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j, ++k) {
+                node_array_per_cell[i][j] = k;
+              }
             }
           }
-        }
-      }
+          NodeArrayPerCell<size_t> copy_node_array_per_cell = copy(node_array_per_cell);
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j) {
+                is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+              }
+            }
 
-      {
-        bool is_same = true;
-        for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < copy_node_array_per_cell.sizeOfArrays(); ++j) {
-            is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+            REQUIRE(is_same);
           }
-        }
 
-        REQUIRE(not is_same);
-      }
-    }
+          {
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              auto cell_table = node_array_per_cell.itemTable(cell_id);
+              for (size_t i_node = 0; i_node < node_array_per_cell.numberOfSubArrays(cell_id); ++i_node) {
+                auto node_array = cell_table[i_node];
+                for (size_t i = 0; i < node_array.size(); ++i) {
+                  node_array[i] = cell_id + i_node + i;
+                }
+              }
+            }
+          }
 
-    SECTION("from weak")
-    {
-      WeakNodeArrayPerCell<size_t> node_array_per_cell{connectivity, 3};
-      {
-        size_t k = 0;
-        for (size_t i = 0; i < node_array_per_cell.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j, ++k) {
-            node_array_per_cell[i][j] = k;
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < copy_node_array_per_cell.sizeOfArrays(); ++j) {
+                is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+              }
+            }
+
+            REQUIRE(not is_same);
           }
         }
-      }
 
-      NodeArrayPerCell<size_t> copy_node_array_per_cell = copy(node_array_per_cell);
-      {
-        bool is_same = true;
-        for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j) {
-            is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+        SECTION("from weak")
+        {
+          WeakNodeArrayPerCell<size_t> node_array_per_cell{connectivity, 3};
+          {
+            size_t k = 0;
+            for (size_t i = 0; i < node_array_per_cell.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j, ++k) {
+                node_array_per_cell[i][j] = k;
+              }
+            }
           }
-        }
 
-        REQUIRE(is_same);
-      }
+          NodeArrayPerCell<size_t> copy_node_array_per_cell = copy(node_array_per_cell);
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j) {
+                is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+              }
+            }
 
-      {
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          auto cell_table = node_array_per_cell.itemTable(cell_id);
-          for (size_t i_node = 0; i_node < node_array_per_cell.numberOfSubArrays(cell_id); ++i_node) {
-            auto node_array = cell_table[i_node];
-            for (size_t i = 0; i < node_array.size(); ++i) {
-              node_array[i] = cell_id + i_node + i;
+            REQUIRE(is_same);
+          }
+
+          {
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              auto cell_table = node_array_per_cell.itemTable(cell_id);
+              for (size_t i_node = 0; i_node < node_array_per_cell.numberOfSubArrays(cell_id); ++i_node) {
+                auto node_array = cell_table[i_node];
+                for (size_t i = 0; i < node_array.size(); ++i) {
+                  node_array[i] = cell_id + i_node + i;
+                }
+              }
             }
           }
-        }
-      }
 
-      {
-        bool is_same = true;
-        for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
-          for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j) {
-            is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
+              for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j) {
+                is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+              }
+            }
+
+            REQUIRE(not is_same);
           }
         }
-
-        REQUIRE(not is_same);
       }
     }
   }
 
   SECTION("WeakSubItemArrayPerItem")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    WeakFaceArrayPerCell<int> weak_face_array_per_cell{connectivity, 3};
-    {
-      size_t k = 0;
-      for (size_t i = 0; i < weak_face_array_per_cell.numberOfArrays(); ++i) {
-        for (size_t j = 0; j < weak_face_array_per_cell.sizeOfArrays(); ++j, ++k) {
-          weak_face_array_per_cell[i][j] = k;
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
+
+        const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+        WeakFaceArrayPerCell<int> weak_face_array_per_cell{connectivity, 3};
+        {
+          size_t k = 0;
+          for (size_t i = 0; i < weak_face_array_per_cell.numberOfArrays(); ++i) {
+            for (size_t j = 0; j < weak_face_array_per_cell.sizeOfArrays(); ++j, ++k) {
+              weak_face_array_per_cell[i][j] = k;
+            }
+          }
         }
-      }
-    }
 
-    FaceArrayPerCell<const int> face_array_per_cell{weak_face_array_per_cell};
+        FaceArrayPerCell<const int> face_array_per_cell{weak_face_array_per_cell};
 
-    REQUIRE(face_array_per_cell.connectivity_ptr() == weak_face_array_per_cell.connectivity_ptr());
-    REQUIRE(face_array_per_cell.sizeOfArrays() == weak_face_array_per_cell.sizeOfArrays());
+        REQUIRE(face_array_per_cell.connectivity_ptr() == weak_face_array_per_cell.connectivity_ptr());
+        REQUIRE(face_array_per_cell.sizeOfArrays() == weak_face_array_per_cell.sizeOfArrays());
 
-    bool is_same = true;
-    for (size_t i = 0; i < weak_face_array_per_cell.numberOfArrays(); ++i) {
-      for (size_t j = 0; j < weak_face_array_per_cell.sizeOfArrays(); ++j) {
-        is_same &= (face_array_per_cell[i][j] == weak_face_array_per_cell[i][j]);
+        bool is_same = true;
+        for (size_t i = 0; i < weak_face_array_per_cell.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < weak_face_array_per_cell.sizeOfArrays(); ++j) {
+            is_same &= (face_array_per_cell[i][j] == weak_face_array_per_cell[i][j]);
+          }
+        }
+        REQUIRE(is_same);
       }
     }
-    REQUIRE(is_same);
   }
 
 #ifndef NDEBUG
@@ -889,71 +955,83 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
     SECTION("checking for bounds violation")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
-      {
-        FaceId invalid_face_id = connectivity.numberOfFaces();
-        REQUIRE_THROWS_AS(cell_array_per_face(invalid_face_id, 0), AssertError);
-      }
-      if (connectivity.numberOfFaces() > 0) {
-        FaceId face_id         = 0;
-        const auto& face_table = cell_array_per_face.itemTable(face_id);
-        REQUIRE_THROWS_AS(cell_array_per_face(face_id, face_table.numberOfRows()), AssertError);
-        REQUIRE_THROWS_AS(face_table[face_table.numberOfRows()], AssertError);
-        REQUIRE_THROWS_AS(cell_array_per_face.itemTable(face_id)[face_table.numberOfRows()], AssertError);
-        REQUIRE_THROWS_AS(cell_array_per_face.itemTable(face_id)[0][cell_array_per_face.sizeOfArrays()], AssertError);
-        REQUIRE_THROWS_AS(cell_array_per_face.itemTable(face_id)[0][cell_array_per_face.sizeOfArrays()] = 2,
-                          AssertError);
-      }
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
 
-      FaceArrayPerNode<int> face_array_per_node{connectivity, 5};
-      {
-        NodeId invalid_node_id = connectivity.numberOfNodes();
-        REQUIRE_THROWS_AS(face_array_per_node(invalid_node_id, 0), AssertError);
-      }
-      if (connectivity.numberOfNodes() > 0) {
-        NodeId node_id         = 0;
-        const auto& node_table = face_array_per_node.itemTable(node_id);
-        REQUIRE_THROWS_AS(face_array_per_node(node_id, node_table.numberOfRows()), AssertError);
-        REQUIRE_THROWS_AS(node_table[node_table.numberOfRows()], AssertError);
-        REQUIRE_THROWS_AS(face_array_per_node.itemTable(node_id)[node_table.numberOfRows()], AssertError);
-        REQUIRE_THROWS_AS(face_array_per_node.itemTable(node_id)[0][face_array_per_node.sizeOfArrays()], AssertError);
-        REQUIRE_THROWS_AS(face_array_per_node.itemTable(node_id)[0][face_array_per_node.sizeOfArrays()] = 2,
-                          AssertError);
-      }
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-      EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 3};
-      {
-        CellId invalid_cell_id = connectivity.numberOfCells();
-        REQUIRE_THROWS_AS(edge_array_per_cell(invalid_cell_id, 0), AssertError);
-      }
-      if (connectivity.numberOfCells() > 0) {
-        CellId cell_id         = 0;
-        const auto& cell_table = edge_array_per_cell.itemTable(cell_id);
-        REQUIRE_THROWS_AS(edge_array_per_cell(cell_id, cell_table.numberOfRows()), AssertError);
-        REQUIRE_THROWS_AS(cell_table[cell_table.numberOfRows()], AssertError);
-        REQUIRE_THROWS_AS(edge_array_per_cell.itemTable(cell_id)[cell_table.numberOfRows()], AssertError);
-        REQUIRE_THROWS_AS(edge_array_per_cell.itemTable(cell_id)[0][edge_array_per_cell.sizeOfArrays()], AssertError);
-        REQUIRE_THROWS_AS(edge_array_per_cell.itemTable(cell_id)[0][edge_array_per_cell.sizeOfArrays()] == 2,
-                          AssertError);
-      }
+          CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
+          {
+            FaceId invalid_face_id = connectivity.numberOfFaces();
+            REQUIRE_THROWS_AS(cell_array_per_face(invalid_face_id, 0), AssertError);
+          }
+          if (connectivity.numberOfFaces() > 0) {
+            FaceId face_id         = 0;
+            const auto& face_table = cell_array_per_face.itemTable(face_id);
+            REQUIRE_THROWS_AS(cell_array_per_face(face_id, face_table.numberOfRows()), AssertError);
+            REQUIRE_THROWS_AS(face_table[face_table.numberOfRows()], AssertError);
+            REQUIRE_THROWS_AS(cell_array_per_face.itemTable(face_id)[face_table.numberOfRows()], AssertError);
+            REQUIRE_THROWS_AS(cell_array_per_face.itemTable(face_id)[0][cell_array_per_face.sizeOfArrays()],
+                              AssertError);
+            REQUIRE_THROWS_AS(cell_array_per_face.itemTable(face_id)[0][cell_array_per_face.sizeOfArrays()] = 2,
+                              AssertError);
+          }
 
-      NodeArrayPerEdge<int> node_array_per_edge{connectivity, 3};
-      {
-        EdgeId invalid_edge_id = connectivity.numberOfEdges();
-        REQUIRE_THROWS_AS(node_array_per_edge(invalid_edge_id, 0), AssertError);
-      }
-      if (connectivity.numberOfEdges() > 0) {
-        EdgeId edge_id         = 0;
-        const auto& edge_table = node_array_per_edge.itemTable(edge_id);
-        REQUIRE_THROWS_AS(node_array_per_edge(edge_id, edge_table.numberOfRows()), AssertError);
-        REQUIRE_THROWS_AS(edge_table[edge_table.numberOfRows()], AssertError);
-        REQUIRE_THROWS_AS(node_array_per_edge.itemTable(edge_id)[edge_table.numberOfRows()], AssertError);
-        REQUIRE_THROWS_AS(node_array_per_edge.itemTable(edge_id)[0][node_array_per_edge.sizeOfArrays()], AssertError);
-        REQUIRE_THROWS_AS(node_array_per_edge.itemTable(edge_id)[0][node_array_per_edge.sizeOfArrays()] = 2,
-                          AssertError);
+          FaceArrayPerNode<int> face_array_per_node{connectivity, 5};
+          {
+            NodeId invalid_node_id = connectivity.numberOfNodes();
+            REQUIRE_THROWS_AS(face_array_per_node(invalid_node_id, 0), AssertError);
+          }
+          if (connectivity.numberOfNodes() > 0) {
+            NodeId node_id         = 0;
+            const auto& node_table = face_array_per_node.itemTable(node_id);
+            REQUIRE_THROWS_AS(face_array_per_node(node_id, node_table.numberOfRows()), AssertError);
+            REQUIRE_THROWS_AS(node_table[node_table.numberOfRows()], AssertError);
+            REQUIRE_THROWS_AS(face_array_per_node.itemTable(node_id)[node_table.numberOfRows()], AssertError);
+            REQUIRE_THROWS_AS(face_array_per_node.itemTable(node_id)[0][face_array_per_node.sizeOfArrays()],
+                              AssertError);
+            REQUIRE_THROWS_AS(face_array_per_node.itemTable(node_id)[0][face_array_per_node.sizeOfArrays()] = 2,
+                              AssertError);
+          }
+
+          EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 3};
+          {
+            CellId invalid_cell_id = connectivity.numberOfCells();
+            REQUIRE_THROWS_AS(edge_array_per_cell(invalid_cell_id, 0), AssertError);
+          }
+          if (connectivity.numberOfCells() > 0) {
+            CellId cell_id         = 0;
+            const auto& cell_table = edge_array_per_cell.itemTable(cell_id);
+            REQUIRE_THROWS_AS(edge_array_per_cell(cell_id, cell_table.numberOfRows()), AssertError);
+            REQUIRE_THROWS_AS(cell_table[cell_table.numberOfRows()], AssertError);
+            REQUIRE_THROWS_AS(edge_array_per_cell.itemTable(cell_id)[cell_table.numberOfRows()], AssertError);
+            REQUIRE_THROWS_AS(edge_array_per_cell.itemTable(cell_id)[0][edge_array_per_cell.sizeOfArrays()],
+                              AssertError);
+            REQUIRE_THROWS_AS(edge_array_per_cell.itemTable(cell_id)[0][edge_array_per_cell.sizeOfArrays()] == 2,
+                              AssertError);
+          }
+
+          NodeArrayPerEdge<int> node_array_per_edge{connectivity, 3};
+          {
+            EdgeId invalid_edge_id = connectivity.numberOfEdges();
+            REQUIRE_THROWS_AS(node_array_per_edge(invalid_edge_id, 0), AssertError);
+          }
+          if (connectivity.numberOfEdges() > 0) {
+            EdgeId edge_id         = 0;
+            const auto& edge_table = node_array_per_edge.itemTable(edge_id);
+            REQUIRE_THROWS_AS(node_array_per_edge(edge_id, edge_table.numberOfRows()), AssertError);
+            REQUIRE_THROWS_AS(edge_table[edge_table.numberOfRows()], AssertError);
+            REQUIRE_THROWS_AS(node_array_per_edge.itemTable(edge_id)[edge_table.numberOfRows()], AssertError);
+            REQUIRE_THROWS_AS(node_array_per_edge.itemTable(edge_id)[0][node_array_per_edge.sizeOfArrays()],
+                              AssertError);
+            REQUIRE_THROWS_AS(node_array_per_edge.itemTable(edge_id)[0][node_array_per_edge.sizeOfArrays()] = 2,
+                              AssertError);
+          }
+        }
       }
     }
   }
diff --git a/tests/test_SubItemValuePerItem.cpp b/tests/test_SubItemValuePerItem.cpp
index f9b71010b411c3a53b968703202882f1d4fd0df1..5eaadd94d88d07d2c802e9da867ab5ae2442aa5a 100644
--- a/tests/test_SubItemValuePerItem.cpp
+++ b/tests/test_SubItemValuePerItem.cpp
@@ -64,660 +64,724 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
-      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      SECTION("per cell")
-      {
-        NodeValuePerCell<int> node_value_per_cell{connectivity};
-        REQUIRE(node_value_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(node_value_per_cell.numberOfValues() == number_of_values(node_value_per_cell));
-
-        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &=
-              (cell_to_node_matrix[cell_id].size() == node_value_per_cell.numberOfSubValues(cell_id)) and
-              (node_value_per_cell.itemValues(cell_id).size() == node_value_per_cell.numberOfSubValues(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        const NodeValuePerCell<const int> const_node_value_per_cell = node_value_per_cell;
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &=
-              (const_node_value_per_cell.itemValues(cell_id).size() == node_value_per_cell.numberOfSubValues(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        EdgeValuePerCell<int> edge_value_per_cell{connectivity};
-        REQUIRE(edge_value_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(edge_value_per_cell.numberOfValues() == number_of_values(edge_value_per_cell));
-
-        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_value_per_cell.numberOfSubValues(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
-
-        FaceValuePerCell<int> face_value_per_cell{connectivity};
-        REQUIRE(face_value_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(face_value_per_cell.numberOfValues() == number_of_values(face_value_per_cell));
-
-        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_face_matrix[cell_id].size() == face_value_per_cell.numberOfSubValues(cell_id));
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          SECTION("per cell")
+          {
+            NodeValuePerCell<int> node_value_per_cell{connectivity};
+            REQUIRE(node_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(node_value_per_cell.numberOfValues() == number_of_values(node_value_per_cell));
+
+            auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &=
+                  (cell_to_node_matrix[cell_id].size() == node_value_per_cell.numberOfSubValues(cell_id)) and
+                  (node_value_per_cell.itemValues(cell_id).size() == node_value_per_cell.numberOfSubValues(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            const NodeValuePerCell<const int> const_node_value_per_cell = node_value_per_cell;
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (const_node_value_per_cell.itemValues(cell_id).size() ==
+                               node_value_per_cell.numberOfSubValues(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+            REQUIRE(edge_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(edge_value_per_cell.numberOfValues() == number_of_values(edge_value_per_cell));
+
+            auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_value_per_cell.numberOfSubValues(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            FaceValuePerCell<int> face_value_per_cell{connectivity};
+            REQUIRE(face_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(face_value_per_cell.numberOfValues() == number_of_values(face_value_per_cell));
+
+            auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_face_matrix[cell_id].size() == face_value_per_cell.numberOfSubValues(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
+          }
+
+          SECTION("per face")
+          {
+            CellValuePerFace<int> cell_value_per_face{connectivity};
+            REQUIRE(cell_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(cell_value_per_face.numberOfValues() == number_of_values(cell_value_per_face));
+
+            auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_cell_matrix[face_id].size() == cell_value_per_face.numberOfSubValues(face_id));
+              }
+              REQUIRE(is_correct);
+            }
+          }
+
+          SECTION("per edge")
+          {
+            CellValuePerEdge<int> cell_value_per_edge{connectivity};
+            REQUIRE(cell_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(cell_value_per_edge.numberOfValues() == number_of_values(cell_value_per_edge));
+
+            auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_value_per_edge.numberOfSubValues(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
+          }
+
+          SECTION("per node")
+          {
+            CellValuePerNode<int> cell_value_per_node{connectivity};
+            REQUIRE(cell_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(cell_value_per_node.numberOfValues() == number_of_values(cell_value_per_node));
+
+            auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_cell_matrix[node_id].size() == cell_value_per_node.numberOfSubValues(node_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
-        }
-      }
-
-      SECTION("per face")
-      {
-        CellValuePerFace<int> cell_value_per_face{connectivity};
-        REQUIRE(cell_value_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(cell_value_per_face.numberOfValues() == number_of_values(cell_value_per_face));
-
-        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
-        {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_cell_matrix[face_id].size() == cell_value_per_face.numberOfSubValues(face_id));
-          }
-          REQUIRE(is_correct);
         }
       }
+    }
 
-      SECTION("per edge")
-      {
-        CellValuePerEdge<int> cell_value_per_edge{connectivity};
-        REQUIRE(cell_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(cell_value_per_edge.numberOfValues() == number_of_values(cell_value_per_edge));
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_value_per_edge.numberOfSubValues(edge_id));
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          SECTION("per cell")
+          {
+            NodeValuePerCell<int> node_value_per_cell{connectivity};
+            REQUIRE(node_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(node_value_per_cell.numberOfValues() == number_of_values(node_value_per_cell));
+
+            auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_node_matrix[cell_id].size() == node_value_per_cell.numberOfSubValues(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+            REQUIRE(edge_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(edge_value_per_cell.numberOfValues() == number_of_values(edge_value_per_cell));
+
+            auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_value_per_cell.numberOfSubValues(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            FaceValuePerCell<int> face_value_per_cell{connectivity};
+            REQUIRE(face_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(face_value_per_cell.numberOfValues() == number_of_values(face_value_per_cell));
+
+            auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_face_matrix[cell_id].size() == face_value_per_cell.numberOfSubValues(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
+          }
+
+          SECTION("per face")
+          {
+            CellValuePerFace<int> cell_value_per_face{connectivity};
+            REQUIRE(cell_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(cell_value_per_face.numberOfValues() == number_of_values(cell_value_per_face));
+
+            auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_cell_matrix[face_id].size() == cell_value_per_face.numberOfSubValues(face_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            NodeValuePerFace<int> node_value_per_face{connectivity};
+            REQUIRE(node_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(node_value_per_face.numberOfValues() == number_of_values(node_value_per_face));
+
+            auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_node_matrix[face_id].size() == node_value_per_face.numberOfSubValues(face_id));
+              }
+              REQUIRE(is_correct);
+            }
+          }
+
+          SECTION("per edge")
+          {
+            CellValuePerEdge<int> cell_value_per_edge{connectivity};
+            REQUIRE(cell_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(cell_value_per_edge.numberOfValues() == number_of_values(cell_value_per_edge));
+
+            auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_value_per_edge.numberOfSubValues(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            NodeValuePerEdge<int> node_value_per_edge{connectivity};
+            REQUIRE(node_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(node_value_per_edge.numberOfValues() == number_of_values(node_value_per_edge));
+
+            auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_node_matrix[edge_id].size() == node_value_per_edge.numberOfSubValues(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
+          }
+
+          SECTION("per node")
+          {
+            EdgeValuePerNode<int> edge_value_per_node{connectivity};
+            REQUIRE(edge_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(edge_value_per_node.numberOfValues() == number_of_values(edge_value_per_node));
+
+            auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_edge_matrix[node_id].size() == edge_value_per_node.numberOfSubValues(node_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            FaceValuePerNode<int> face_value_per_node{connectivity};
+            REQUIRE(face_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(face_value_per_node.numberOfValues() == number_of_values(face_value_per_node));
+
+            auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_face_matrix[node_id].size() == face_value_per_node.numberOfSubValues(node_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            CellValuePerNode<int> cell_value_per_node{connectivity};
+            REQUIRE(cell_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(cell_value_per_node.numberOfValues() == number_of_values(cell_value_per_node));
+
+            auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_cell_matrix[node_id].size() == cell_value_per_node.numberOfSubValues(node_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
         }
       }
+    }
 
-      SECTION("per node")
-      {
-        CellValuePerNode<int> cell_value_per_node{connectivity};
-        REQUIRE(cell_value_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(cell_value_per_node.numberOfValues() == number_of_values(cell_value_per_node));
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_cell_matrix[node_id].size() == cell_value_per_node.numberOfSubValues(node_id));
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          SECTION("per cell")
+          {
+            NodeValuePerCell<int> node_value_per_cell{connectivity};
+            REQUIRE(node_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(node_value_per_cell.numberOfValues() == number_of_values(node_value_per_cell));
+
+            auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_node_matrix[cell_id].size() == node_value_per_cell.numberOfSubValues(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+            REQUIRE(edge_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(edge_value_per_cell.numberOfValues() == number_of_values(edge_value_per_cell));
+
+            auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_value_per_cell.numberOfSubValues(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            FaceValuePerCell<int> face_value_per_cell{connectivity};
+            REQUIRE(face_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+            REQUIRE(face_value_per_cell.numberOfValues() == number_of_values(face_value_per_cell));
+
+            auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+                is_correct &= (cell_to_face_matrix[cell_id].size() == face_value_per_cell.numberOfSubValues(cell_id));
+              }
+              REQUIRE(is_correct);
+            }
+          }
+
+          SECTION("per face")
+          {
+            CellValuePerFace<int> cell_value_per_face{connectivity};
+            REQUIRE(cell_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(cell_value_per_face.numberOfValues() == number_of_values(cell_value_per_face));
+
+            auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_cell_matrix[face_id].size() == cell_value_per_face.numberOfSubValues(face_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            EdgeValuePerFace<int> edge_value_per_face{connectivity};
+            REQUIRE(edge_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(edge_value_per_face.numberOfValues() == number_of_values(edge_value_per_face));
+
+            auto face_to_edge_matrix = connectivity.faceToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_edge_matrix[face_id].size() == edge_value_per_face.numberOfSubValues(face_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            NodeValuePerFace<int> node_value_per_face{connectivity};
+            REQUIRE(node_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+            REQUIRE(node_value_per_face.numberOfValues() == number_of_values(node_value_per_face));
+
+            auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+                is_correct &= (face_to_node_matrix[face_id].size() == node_value_per_face.numberOfSubValues(face_id));
+              }
+              REQUIRE(is_correct);
+            }
+          }
+
+          SECTION("per edge")
+          {
+            CellValuePerEdge<int> cell_value_per_edge{connectivity};
+            REQUIRE(cell_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(cell_value_per_edge.numberOfValues() == number_of_values(cell_value_per_edge));
+
+            auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_value_per_edge.numberOfSubValues(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            FaceValuePerEdge<int> face_value_per_edge{connectivity};
+            REQUIRE(face_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(face_value_per_edge.numberOfValues() == number_of_values(face_value_per_edge));
+
+            auto edge_to_face_matrix = connectivity.edgeToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_face_matrix[edge_id].size() == face_value_per_edge.numberOfSubValues(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            NodeValuePerEdge<int> node_value_per_edge{connectivity};
+            REQUIRE(node_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+            REQUIRE(node_value_per_edge.numberOfValues() == number_of_values(node_value_per_edge));
+
+            auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+            {
+              bool is_correct = true;
+              for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+                is_correct &= (edge_to_node_matrix[edge_id].size() == node_value_per_edge.numberOfSubValues(edge_id));
+              }
+              REQUIRE(is_correct);
+            }
+          }
+
+          SECTION("per node")
+          {
+            EdgeValuePerNode<int> edge_value_per_node{connectivity};
+            REQUIRE(edge_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(edge_value_per_node.numberOfValues() == number_of_values(edge_value_per_node));
+
+            auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_edge_matrix[node_id].size() == edge_value_per_node.numberOfSubValues(node_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            FaceValuePerNode<int> face_value_per_node{connectivity};
+            REQUIRE(face_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(face_value_per_node.numberOfValues() == number_of_values(face_value_per_node));
+
+            auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_face_matrix[node_id].size() == face_value_per_node.numberOfSubValues(node_id));
+              }
+              REQUIRE(is_correct);
+            }
+
+            CellValuePerNode<int> cell_value_per_node{connectivity};
+            REQUIRE(cell_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+            REQUIRE(cell_value_per_node.numberOfValues() == number_of_values(cell_value_per_node));
+
+            auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+            {
+              bool is_correct = true;
+              for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+                is_correct &= (node_to_cell_matrix[node_id].size() == cell_value_per_node.numberOfSubValues(node_id));
+              }
+              REQUIRE(is_correct);
+            }
           }
-          REQUIRE(is_correct);
         }
       }
     }
+  }
 
-    SECTION("2D")
+  SECTION("array view")
+  {
+    SECTION("1D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      SECTION("per cell")
-      {
-        NodeValuePerCell<int> node_value_per_cell{connectivity};
-        REQUIRE(node_value_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(node_value_per_cell.numberOfValues() == number_of_values(node_value_per_cell));
-
-        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_node_matrix[cell_id].size() == node_value_per_cell.numberOfSubValues(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
+          auto mesh_1d = named_mesh.mesh();
 
-        EdgeValuePerCell<int> edge_value_per_cell{connectivity};
-        REQUIRE(edge_value_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(edge_value_per_cell.numberOfValues() == number_of_values(edge_value_per_cell));
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
 
-        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_value_per_cell.numberOfSubValues(cell_id));
+          EdgeValuePerCell<size_t> edge_values_per_cell{connectivity};
+          {
+            size_t value = 0;
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              for (size_t i_edge = 0; i_edge < edge_values_per_cell.numberOfSubValues(cell_id); ++i_edge) {
+                edge_values_per_cell(cell_id, i_edge) = value++;
+              }
+            }
           }
-          REQUIRE(is_correct);
-        }
-
-        FaceValuePerCell<int> face_value_per_cell{connectivity};
-        REQUIRE(face_value_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(face_value_per_cell.numberOfValues() == number_of_values(face_value_per_cell));
-
-        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_face_matrix[cell_id].size() == face_value_per_cell.numberOfSubValues(cell_id));
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < edge_values_per_cell.numberOfValues(); ++i) {
+              is_same &= (edge_values_per_cell[i] == i);
+            }
+            REQUIRE(is_same);
           }
-          REQUIRE(is_correct);
-        }
-      }
-
-      SECTION("per face")
-      {
-        CellValuePerFace<int> cell_value_per_face{connectivity};
-        REQUIRE(cell_value_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(cell_value_per_face.numberOfValues() == number_of_values(cell_value_per_face));
 
-        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
-        {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_cell_matrix[face_id].size() == cell_value_per_face.numberOfSubValues(face_id));
+          for (size_t i = 0; i < edge_values_per_cell.numberOfValues(); ++i) {
+            edge_values_per_cell[i] = i * i + 1;
           }
-          REQUIRE(is_correct);
-        }
-
-        NodeValuePerFace<int> node_value_per_face{connectivity};
-        REQUIRE(node_value_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(node_value_per_face.numberOfValues() == number_of_values(node_value_per_face));
-
-        auto face_to_node_matrix = connectivity.faceToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_node_matrix[face_id].size() == node_value_per_face.numberOfSubValues(face_id));
+          {
+            bool is_same = true;
+            size_t i     = 0;
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              for (size_t i_edge = 0; i_edge < edge_values_per_cell.numberOfSubValues(cell_id); ++i_edge, ++i) {
+                is_same &= (edge_values_per_cell(cell_id, i_edge) == i * i + 1);
+              }
+            }
+            REQUIRE(is_same);
           }
-          REQUIRE(is_correct);
         }
       }
+    }
 
-      SECTION("per edge")
-      {
-        CellValuePerEdge<int> cell_value_per_edge{connectivity};
-        REQUIRE(cell_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(cell_value_per_edge.numberOfValues() == number_of_values(cell_value_per_edge));
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_value_per_edge.numberOfSubValues(edge_id));
-          }
-          REQUIRE(is_correct);
-        }
+          auto mesh_2d = named_mesh.mesh();
 
-        NodeValuePerEdge<int> node_value_per_edge{connectivity};
-        REQUIRE(node_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(node_value_per_edge.numberOfValues() == number_of_values(node_value_per_edge));
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
 
-        auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_node_matrix[edge_id].size() == node_value_per_edge.numberOfSubValues(edge_id));
+          CellValuePerFace<size_t> cell_values_per_face{connectivity};
+          {
+            size_t value = 0;
+            for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+              for (size_t i_cell = 0; i_cell < cell_values_per_face.numberOfSubValues(face_id); ++i_cell) {
+                cell_values_per_face(face_id, i_cell) = value++;
+              }
+            }
           }
-          REQUIRE(is_correct);
-        }
-      }
-
-      SECTION("per node")
-      {
-        EdgeValuePerNode<int> edge_value_per_node{connectivity};
-        REQUIRE(edge_value_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(edge_value_per_node.numberOfValues() == number_of_values(edge_value_per_node));
-
-        auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_edge_matrix[node_id].size() == edge_value_per_node.numberOfSubValues(node_id));
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < cell_values_per_face.numberOfValues(); ++i) {
+              is_same &= (cell_values_per_face[i] == i);
+            }
+            REQUIRE(is_same);
           }
-          REQUIRE(is_correct);
-        }
-
-        FaceValuePerNode<int> face_value_per_node{connectivity};
-        REQUIRE(face_value_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(face_value_per_node.numberOfValues() == number_of_values(face_value_per_node));
-
-        auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_face_matrix[node_id].size() == face_value_per_node.numberOfSubValues(node_id));
+          for (size_t i = 0; i < cell_values_per_face.numberOfValues(); ++i) {
+            cell_values_per_face[i] = 3 * i + 1;
           }
-          REQUIRE(is_correct);
-        }
-
-        CellValuePerNode<int> cell_value_per_node{connectivity};
-        REQUIRE(cell_value_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(cell_value_per_node.numberOfValues() == number_of_values(cell_value_per_node));
-
-        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_cell_matrix[node_id].size() == cell_value_per_node.numberOfSubValues(node_id));
+          {
+            bool is_same = true;
+            size_t i     = 0;
+            for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+              for (size_t i_cell = 0; i_cell < cell_values_per_face.numberOfSubValues(face_id); ++i_cell, ++i) {
+                is_same &= (cell_values_per_face(face_id, i_cell) == 3 * i + 1);
+              }
+            }
+            REQUIRE(is_same);
           }
-          REQUIRE(is_correct);
         }
       }
     }
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
-
-      SECTION("per cell")
-      {
-        NodeValuePerCell<int> node_value_per_cell{connectivity};
-        REQUIRE(node_value_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(node_value_per_cell.numberOfValues() == number_of_values(node_value_per_cell));
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
         {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_node_matrix[cell_id].size() == node_value_per_cell.numberOfSubValues(cell_id));
-          }
-          REQUIRE(is_correct);
-        }
+          auto mesh_3d = named_mesh.mesh();
 
-        EdgeValuePerCell<int> edge_value_per_cell{connectivity};
-        REQUIRE(edge_value_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(edge_value_per_cell.numberOfValues() == number_of_values(edge_value_per_cell));
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_value_per_cell.numberOfSubValues(cell_id));
+          FaceValuePerNode<size_t> face_values_per_node{connectivity};
+          {
+            size_t value = 0;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              for (size_t i_face = 0; i_face < face_values_per_node.numberOfSubValues(node_id); ++i_face) {
+                face_values_per_node.itemValues(node_id)[i_face] = value++;
+              }
+            }
+          }
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < face_values_per_node.numberOfValues(); ++i) {
+              is_same &= (face_values_per_node[i] == i);
+            }
+            REQUIRE(is_same);
           }
-          REQUIRE(is_correct);
-        }
-
-        FaceValuePerCell<int> face_value_per_cell{connectivity};
-        REQUIRE(face_value_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(face_value_per_cell.numberOfValues() == number_of_values(face_value_per_cell));
 
-        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
-        {
-          bool is_correct = true;
-          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-            is_correct &= (cell_to_face_matrix[cell_id].size() == face_value_per_cell.numberOfSubValues(cell_id));
+          for (size_t i = 0; i < face_values_per_node.numberOfValues(); ++i) {
+            face_values_per_node[i] = 3 + i * i;
+          }
+          {
+            bool is_same = true;
+            size_t i     = 0;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              for (size_t i_face = 0; i_face < face_values_per_node.numberOfSubValues(node_id); ++i_face, ++i) {
+                is_same &= (face_values_per_node.itemValues(node_id)[i_face] == 3 + i * i);
+              }
+            }
+            REQUIRE(is_same);
           }
-          REQUIRE(is_correct);
         }
       }
+    }
+  }
 
-      SECTION("per face")
-      {
-        CellValuePerFace<int> cell_value_per_face{connectivity};
-        REQUIRE(cell_value_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(cell_value_per_face.numberOfValues() == number_of_values(cell_value_per_face));
+  SECTION("copy")
+  {
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
-        {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_cell_matrix[face_id].size() == cell_value_per_face.numberOfSubValues(face_id));
-          }
-          REQUIRE(is_correct);
-        }
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
 
-        EdgeValuePerFace<int> edge_value_per_face{connectivity};
-        REQUIRE(edge_value_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(edge_value_per_face.numberOfValues() == number_of_values(edge_value_per_face));
+        const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-        auto face_to_edge_matrix = connectivity.faceToEdgeMatrix();
+        SECTION("classic")
         {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_edge_matrix[face_id].size() == edge_value_per_face.numberOfSubValues(face_id));
-          }
-          REQUIRE(is_correct);
-        }
+          NodeValuePerCell<size_t> node_value_per_cell{connectivity};
 
-        NodeValuePerFace<int> node_value_per_face{connectivity};
-        REQUIRE(node_value_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(node_value_per_face.numberOfValues() == number_of_values(node_value_per_face));
-
-        auto face_to_node_matrix = connectivity.faceToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-            is_correct &= (face_to_node_matrix[face_id].size() == node_value_per_face.numberOfSubValues(face_id));
+          {
+            size_t value = 0;
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
+                node_value_per_cell.itemValues(cell_id)[i_node] = value++;
+              }
+            }
           }
-          REQUIRE(is_correct);
-        }
-      }
 
-      SECTION("per edge")
-      {
-        CellValuePerEdge<int> cell_value_per_edge{connectivity};
-        REQUIRE(cell_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(cell_value_per_edge.numberOfValues() == number_of_values(cell_value_per_edge));
-
-        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
-        {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_value_per_edge.numberOfSubValues(edge_id));
-          }
-          REQUIRE(is_correct);
-        }
+          NodeValuePerCell<size_t> copy_node_value_per_cell = copy(node_value_per_cell);
 
-        FaceValuePerEdge<int> face_value_per_edge{connectivity};
-        REQUIRE(face_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(face_value_per_edge.numberOfValues() == number_of_values(face_value_per_edge));
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
+              is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
+            }
 
-        auto edge_to_face_matrix = connectivity.edgeToFaceMatrix();
-        {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_face_matrix[edge_id].size() == face_value_per_edge.numberOfSubValues(edge_id));
+            REQUIRE(is_same);
           }
-          REQUIRE(is_correct);
-        }
-
-        NodeValuePerEdge<int> node_value_per_edge{connectivity};
-        REQUIRE(node_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(node_value_per_edge.numberOfValues() == number_of_values(node_value_per_edge));
 
-        auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
-        {
-          bool is_correct = true;
-          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
-            is_correct &= (edge_to_node_matrix[edge_id].size() == node_value_per_edge.numberOfSubValues(edge_id));
+          {
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
+                node_value_per_cell.itemValues(cell_id)[i_node] = i_node;
+              }
+            }
           }
-          REQUIRE(is_correct);
-        }
-      }
 
-      SECTION("per node")
-      {
-        EdgeValuePerNode<int> edge_value_per_node{connectivity};
-        REQUIRE(edge_value_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(edge_value_per_node.numberOfValues() == number_of_values(edge_value_per_node));
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
+              is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
+            }
 
-        auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_edge_matrix[node_id].size() == edge_value_per_node.numberOfSubValues(node_id));
+            REQUIRE(not is_same);
           }
-          REQUIRE(is_correct);
         }
 
-        FaceValuePerNode<int> face_value_per_node{connectivity};
-        REQUIRE(face_value_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(face_value_per_node.numberOfValues() == number_of_values(face_value_per_node));
-
-        auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+        SECTION("from weak")
         {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_face_matrix[node_id].size() == face_value_per_node.numberOfSubValues(node_id));
-          }
-          REQUIRE(is_correct);
-        }
+          WeakNodeValuePerCell<size_t> node_value_per_cell{connectivity};
 
-        CellValuePerNode<int> cell_value_per_node{connectivity};
-        REQUIRE(cell_value_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(cell_value_per_node.numberOfValues() == number_of_values(cell_value_per_node));
-
-        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
-        {
-          bool is_correct = true;
-          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-            is_correct &= (node_to_cell_matrix[node_id].size() == cell_value_per_node.numberOfSubValues(node_id));
+          {
+            size_t value = 0;
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
+                node_value_per_cell.itemValues(cell_id)[i_node] = value++;
+              }
+            }
           }
-          REQUIRE(is_correct);
-        }
-      }
-    }
-  }
 
-  SECTION("array view")
-  {
-    SECTION("1D")
-    {
-      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
-      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+          NodeValuePerCell<size_t> copy_node_value_per_cell = copy(node_value_per_cell);
 
-      EdgeValuePerCell<size_t> edge_values_per_cell{connectivity};
-      {
-        size_t value = 0;
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          for (size_t i_edge = 0; i_edge < edge_values_per_cell.numberOfSubValues(cell_id); ++i_edge) {
-            edge_values_per_cell(cell_id, i_edge) = value++;
-          }
-        }
-      }
-      {
-        bool is_same = true;
-        for (size_t i = 0; i < edge_values_per_cell.numberOfValues(); ++i) {
-          is_same &= (edge_values_per_cell[i] == i);
-        }
-        REQUIRE(is_same);
-      }
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
+              is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
+            }
 
-      for (size_t i = 0; i < edge_values_per_cell.numberOfValues(); ++i) {
-        edge_values_per_cell[i] = i * i + 1;
-      }
-      {
-        bool is_same = true;
-        size_t i     = 0;
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          for (size_t i_edge = 0; i_edge < edge_values_per_cell.numberOfSubValues(cell_id); ++i_edge, ++i) {
-            is_same &= (edge_values_per_cell(cell_id, i_edge) == i * i + 1);
+            REQUIRE(is_same);
           }
-        }
-        REQUIRE(is_same);
-      }
-    }
-
-    SECTION("2D")
-    {
-      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
-      CellValuePerFace<size_t> cell_values_per_face{connectivity};
-      {
-        size_t value = 0;
-        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-          for (size_t i_cell = 0; i_cell < cell_values_per_face.numberOfSubValues(face_id); ++i_cell) {
-            cell_values_per_face(face_id, i_cell) = value++;
-          }
-        }
-      }
-      {
-        bool is_same = true;
-        for (size_t i = 0; i < cell_values_per_face.numberOfValues(); ++i) {
-          is_same &= (cell_values_per_face[i] == i);
-        }
-        REQUIRE(is_same);
-      }
-      for (size_t i = 0; i < cell_values_per_face.numberOfValues(); ++i) {
-        cell_values_per_face[i] = 3 * i + 1;
-      }
-      {
-        bool is_same = true;
-        size_t i     = 0;
-        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-          for (size_t i_cell = 0; i_cell < cell_values_per_face.numberOfSubValues(face_id); ++i_cell, ++i) {
-            is_same &= (cell_values_per_face(face_id, i_cell) == 3 * i + 1);
+          {
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
+                node_value_per_cell.itemValues(cell_id)[i_node] = i_node;
+              }
+            }
           }
-        }
-        REQUIRE(is_same);
-      }
-    }
 
-    SECTION("3D")
-    {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+          {
+            bool is_same = true;
+            for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
+              is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
+            }
 
-      FaceValuePerNode<size_t> face_values_per_node{connectivity};
-      {
-        size_t value = 0;
-        for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-          for (size_t i_face = 0; i_face < face_values_per_node.numberOfSubValues(node_id); ++i_face) {
-            face_values_per_node.itemValues(node_id)[i_face] = value++;
+            REQUIRE(not is_same);
           }
         }
       }
-      {
-        bool is_same = true;
-        for (size_t i = 0; i < face_values_per_node.numberOfValues(); ++i) {
-          is_same &= (face_values_per_node[i] == i);
-        }
-        REQUIRE(is_same);
-      }
-
-      for (size_t i = 0; i < face_values_per_node.numberOfValues(); ++i) {
-        face_values_per_node[i] = 3 + i * i;
-      }
-      {
-        bool is_same = true;
-        size_t i     = 0;
-        for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-          for (size_t i_face = 0; i_face < face_values_per_node.numberOfSubValues(node_id); ++i_face, ++i) {
-            is_same &= (face_values_per_node.itemValues(node_id)[i_face] == 3 + i * i);
-          }
-        }
-        REQUIRE(is_same);
-      }
     }
   }
 
-  SECTION("copy")
+  SECTION("WeakSubItemValuePerItem")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
-
-    SECTION("classic")
-    {
-      NodeValuePerCell<size_t> node_value_per_cell{connectivity};
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
       {
-        size_t value = 0;
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
-            node_value_per_cell.itemValues(cell_id)[i_node] = value++;
-          }
-        }
-      }
-
-      NodeValuePerCell<size_t> copy_node_value_per_cell = copy(node_value_per_cell);
+        auto mesh_2d = named_mesh.mesh();
 
-      {
-        bool is_same = true;
-        for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
-          is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
-        }
-
-        REQUIRE(is_same);
-      }
+        const Connectivity<2>& connectivity = mesh_2d->connectivity();
 
-      {
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
-            node_value_per_cell.itemValues(cell_id)[i_node] = i_node;
-          }
-        }
-      }
+        WeakFaceValuePerCell<int> weak_face_value_per_cell{connectivity};
 
-      {
-        bool is_same = true;
-        for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
-          is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
+        for (size_t i = 0; i < weak_face_value_per_cell.numberOfValues(); ++i) {
+          weak_face_value_per_cell[i] = i;
         }
 
-        REQUIRE(not is_same);
-      }
-    }
-
-    SECTION("from weak")
-    {
-      WeakNodeValuePerCell<size_t> node_value_per_cell{connectivity};
-
-      {
-        size_t value = 0;
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
-            node_value_per_cell.itemValues(cell_id)[i_node] = value++;
-          }
-        }
-      }
+        FaceValuePerCell<const int> face_value_per_cell{weak_face_value_per_cell};
 
-      NodeValuePerCell<size_t> copy_node_value_per_cell = copy(node_value_per_cell);
+        REQUIRE(face_value_per_cell.connectivity_ptr() == weak_face_value_per_cell.connectivity_ptr());
 
-      {
         bool is_same = true;
-        for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
-          is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
+        for (size_t i = 0; i < weak_face_value_per_cell.numberOfValues(); ++i) {
+          is_same &= (face_value_per_cell[i] == weak_face_value_per_cell[i]);
         }
-
         REQUIRE(is_same);
       }
-
-      {
-        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
-            node_value_per_cell.itemValues(cell_id)[i_node] = i_node;
-          }
-        }
-      }
-
-      {
-        bool is_same = true;
-        for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
-          is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
-        }
-
-        REQUIRE(not is_same);
-      }
     }
   }
 
-  SECTION("WeakSubItemValuePerItem")
-  {
-    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
-    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
-
-    WeakFaceValuePerCell<int> weak_face_value_per_cell{connectivity};
-
-    for (size_t i = 0; i < weak_face_value_per_cell.numberOfValues(); ++i) {
-      weak_face_value_per_cell[i] = i;
-    }
-
-    FaceValuePerCell<const int> face_value_per_cell{weak_face_value_per_cell};
-
-    REQUIRE(face_value_per_cell.connectivity_ptr() == weak_face_value_per_cell.connectivity_ptr());
-
-    bool is_same = true;
-    for (size_t i = 0; i < weak_face_value_per_cell.numberOfValues(); ++i) {
-      is_same &= (face_value_per_cell[i] == weak_face_value_per_cell[i]);
-    }
-    REQUIRE(is_same);
-  }
-
 #ifndef NDEBUG
   SECTION("error")
   {
@@ -758,59 +822,67 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
     SECTION("checking for bounds violation")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
-      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      CellValuePerFace<int> cell_value_per_face{connectivity};
-      {
-        FaceId invalid_face_id = connectivity.numberOfFaces();
-        REQUIRE_THROWS_AS(cell_value_per_face(invalid_face_id, 0), AssertError);
-      }
-      if (connectivity.numberOfFaces() > 0) {
-        FaceId face_id          = 0;
-        const auto& cell_values = cell_value_per_face.itemValues(face_id);
-        REQUIRE_THROWS_AS(cell_value_per_face(face_id, cell_values.size()), AssertError);
-        REQUIRE_THROWS_AS(cell_values[cell_values.size()], AssertError);
-        REQUIRE_THROWS_AS(cell_value_per_face.itemValues(face_id)[cell_values.size()] = 2, AssertError);
-      }
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
 
-      FaceValuePerNode<int> face_value_per_node{connectivity};
-      {
-        NodeId invalid_node_id = connectivity.numberOfNodes();
-        REQUIRE_THROWS_AS(face_value_per_node(invalid_node_id, 0), AssertError);
-      }
-      if (connectivity.numberOfNodes() > 0) {
-        NodeId node_id          = 0;
-        const auto& face_values = face_value_per_node.itemValues(node_id);
-        REQUIRE_THROWS_AS(face_value_per_node(node_id, face_values.size()), AssertError);
-        REQUIRE_THROWS_AS(face_values[face_values.size()], AssertError);
-        REQUIRE_THROWS_AS(face_value_per_node.itemValues(node_id)[face_values.size()] = 2, AssertError);
-      }
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
 
-      EdgeValuePerCell<int> edge_value_per_cell{connectivity};
-      {
-        CellId invalid_cell_id = connectivity.numberOfCells();
-        REQUIRE_THROWS_AS(edge_value_per_cell(invalid_cell_id, 0), AssertError);
-      }
-      if (connectivity.numberOfCells() > 0) {
-        CellId cell_id          = 0;
-        const auto& edge_values = edge_value_per_cell.itemValues(cell_id);
-        REQUIRE_THROWS_AS(edge_value_per_cell(cell_id, edge_values.size()), AssertError);
-        REQUIRE_THROWS_AS(edge_values[edge_values.size()], AssertError);
-        REQUIRE_THROWS_AS(edge_value_per_cell.itemValues(cell_id)[edge_values.size()] = 2, AssertError);
-      }
+          CellValuePerFace<int> cell_value_per_face{connectivity};
+          {
+            FaceId invalid_face_id = connectivity.numberOfFaces();
+            REQUIRE_THROWS_AS(cell_value_per_face(invalid_face_id, 0), AssertError);
+          }
+          if (connectivity.numberOfFaces() > 0) {
+            FaceId face_id          = 0;
+            const auto& cell_values = cell_value_per_face.itemValues(face_id);
+            REQUIRE_THROWS_AS(cell_value_per_face(face_id, cell_values.size()), AssertError);
+            REQUIRE_THROWS_AS(cell_values[cell_values.size()], AssertError);
+            REQUIRE_THROWS_AS(cell_value_per_face.itemValues(face_id)[cell_values.size()] = 2, AssertError);
+          }
 
-      NodeValuePerEdge<int> node_value_per_edge{connectivity};
-      {
-        EdgeId invalid_edge_id = connectivity.numberOfEdges();
-        REQUIRE_THROWS_AS(node_value_per_edge(invalid_edge_id, 0), AssertError);
-      }
-      if (connectivity.numberOfEdges() > 0) {
-        EdgeId edge_id          = 0;
-        const auto& node_values = node_value_per_edge.itemValues(edge_id);
-        REQUIRE_THROWS_AS(node_value_per_edge(edge_id, node_values.size()), AssertError);
-        REQUIRE_THROWS_AS(node_values[node_values.size()], AssertError);
-        REQUIRE_THROWS_AS(node_value_per_edge.itemValues(edge_id)[node_values.size()] = 2, AssertError);
+          FaceValuePerNode<int> face_value_per_node{connectivity};
+          {
+            NodeId invalid_node_id = connectivity.numberOfNodes();
+            REQUIRE_THROWS_AS(face_value_per_node(invalid_node_id, 0), AssertError);
+          }
+          if (connectivity.numberOfNodes() > 0) {
+            NodeId node_id          = 0;
+            const auto& face_values = face_value_per_node.itemValues(node_id);
+            REQUIRE_THROWS_AS(face_value_per_node(node_id, face_values.size()), AssertError);
+            REQUIRE_THROWS_AS(face_values[face_values.size()], AssertError);
+            REQUIRE_THROWS_AS(face_value_per_node.itemValues(node_id)[face_values.size()] = 2, AssertError);
+          }
+
+          EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+          {
+            CellId invalid_cell_id = connectivity.numberOfCells();
+            REQUIRE_THROWS_AS(edge_value_per_cell(invalid_cell_id, 0), AssertError);
+          }
+          if (connectivity.numberOfCells() > 0) {
+            CellId cell_id          = 0;
+            const auto& edge_values = edge_value_per_cell.itemValues(cell_id);
+            REQUIRE_THROWS_AS(edge_value_per_cell(cell_id, edge_values.size()), AssertError);
+            REQUIRE_THROWS_AS(edge_values[edge_values.size()], AssertError);
+            REQUIRE_THROWS_AS(edge_value_per_cell.itemValues(cell_id)[edge_values.size()] = 2, AssertError);
+          }
+
+          NodeValuePerEdge<int> node_value_per_edge{connectivity};
+          {
+            EdgeId invalid_edge_id = connectivity.numberOfEdges();
+            REQUIRE_THROWS_AS(node_value_per_edge(invalid_edge_id, 0), AssertError);
+          }
+          if (connectivity.numberOfEdges() > 0) {
+            EdgeId edge_id          = 0;
+            const auto& node_values = node_value_per_edge.itemValues(edge_id);
+            REQUIRE_THROWS_AS(node_value_per_edge(edge_id, node_values.size()), AssertError);
+            REQUIRE_THROWS_AS(node_values[node_values.size()], AssertError);
+            REQUIRE_THROWS_AS(node_value_per_edge.itemValues(edge_id)[node_values.size()] = 2, AssertError);
+          }
+        }
       }
     }
   }
diff --git a/tests/test_TensorialGaussLegendreQuadrature.cpp b/tests/test_TensorialGaussLegendreQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b2ac6c80c57009ea16ac8911e0609e59f60cb178
--- /dev/null
+++ b/tests/test_TensorialGaussLegendreQuadrature.cpp
@@ -0,0 +1,628 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <analysis/TensorialGaussLegendreQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("TensorialGaussLegendreQuadrature", "[analysis]")
+{
+  SECTION("1D")
+  {
+    auto is_symmetric_formula = [](auto quadrature_formula) {
+      auto point_list  = quadrature_formula.pointList();
+      auto weight_list = quadrature_formula.weightList();
+
+      bool is_symmetric = true;
+
+      const size_t middle_index = point_list.size() / 2;
+      for (size_t i = 0; i <= middle_index; ++i) {
+        if (point_list[i] != -point_list[point_list.size() - 1 - i]) {
+          return false;
+        }
+      }
+      for (size_t i = 0; i <= middle_index; ++i) {
+        if (weight_list[i] != weight_list[point_list.size() - 1 - i]) {
+          return false;
+        }
+      }
+
+      return is_symmetric;
+    };
+
+    auto integrate = [](auto f, auto quadrature_formula, const double a, const double b) {
+      auto point_list  = quadrature_formula.pointList();
+      auto weight_list = quadrature_formula.weightList();
+
+      double alpha = 0.5 * (b - a);
+      double beta  = 0.5 * (a + b);
+
+      auto x = [&alpha, &beta](auto x_hat) { return alpha * x_hat + beta; };
+
+      auto value = weight_list[0] * f(x(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * f(x(point_list[i]));
+      }
+
+      return alpha * value;
+    };
+
+    auto get_order = [&integrate](auto f, auto quadrature_formula, const double a, const double b,
+                                  const double exact_value) {
+      double int_ab          = integrate(f, quadrature_formula, a, b);
+      double int_first_half  = integrate(f, quadrature_formula, a, 0.5 * (a + b));
+      double int_second_half = integrate(f, quadrature_formula, 0.5 * (a + b), b);
+
+      return -std::log((int_first_half + int_second_half - exact_value) / (int_ab - exact_value)) / std::log(2);
+    };
+
+    auto p0 = [](const TinyVector<1>&) { return 1; };
+    auto p1 = [&p0](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 2 * x + p0(X);
+    };
+    auto p2 = [&p1](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 3 * x * x + p1(X);
+    };
+    auto p3 = [&p2](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 4 * std::pow(x, 3) + p2(X);
+    };
+    auto p4 = [&p3](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 5 * std::pow(x, 4) + p3(X);
+    };
+    auto p5 = [&p4](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 6 * std::pow(x, 5) + p4(X);
+    };
+    auto p6 = [&p5](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 7 * std::pow(x, 6) + p5(X);
+    };
+    auto p7 = [&p6](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 8 * std::pow(x, 7) + p6(X);
+    };
+    auto p8 = [&p7](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 9 * std::pow(x, 8) + p7(X);
+    };
+    auto p9 = [&p8](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 10 * std::pow(x, 9) + p8(X);
+    };
+    auto p10 = [&p9](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 11 * std::pow(x, 10) + p9(X);
+    };
+    auto p11 = [&p10](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 12 * std::pow(x, 11) + p10(X);
+    };
+    auto p12 = [&p11](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 13 * std::pow(x, 12) + p11(X);
+    };
+    auto p13 = [&p12](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 14 * std::pow(x, 13) + p12(X);
+    };
+    auto p14 = [&p13](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 15 * std::pow(x, 14) + p13(X);
+    };
+    auto p15 = [&p14](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 16 * std::pow(x, 15) + p14(X);
+    };
+    auto p16 = [&p15](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 17 * std::pow(x, 16) + p15(X);
+    };
+    auto p17 = [&p16](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 18 * std::pow(x, 17) + p16(X);
+    };
+    auto p18 = [&p17](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 19 * std::pow(x, 18) + p17(X);
+    };
+    auto p19 = [&p18](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 20 * std::pow(x, 19) + p18(X);
+    };
+    auto p20 = [&p19](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 21 * std::pow(x, 20) + p19(X);
+    };
+    auto p21 = [&p20](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 22 * std::pow(x, 21) + p20(X);
+    };
+    auto p22 = [&p21](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 23 * std::pow(x, 22) + p21(X);
+    };
+    auto p23 = [&p22](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 24 * std::pow(x, 23) + p22(X);
+    };
+    auto p24 = [&p23](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 25 * std::pow(x, 24) + p23(X);
+    };
+
+    SECTION("degree 1")
+    {
+      const QuadratureFormula<1>& l1 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(1));
+
+      REQUIRE(is_symmetric_formula(l1));
+
+      REQUIRE(l1.numberOfPoints() == 1);
+
+      REQUIRE(integrate(p0, l1, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l1, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l1, 0, 1) != Catch::Approx(3));
+
+      REQUIRE(get_order(p2, l1, -1, 1, 4) == Catch::Approx(2));
+    }
+
+    SECTION("degree 2 and 3")
+    {
+      const QuadratureFormula<1>& l2 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(2));
+      const QuadratureFormula<1>& l3 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(3));
+
+      REQUIRE(&l2 == &l3);
+      REQUIRE(is_symmetric_formula(l3));
+
+      REQUIRE(l3.numberOfPoints() == 2);
+
+      REQUIRE(integrate(p0, l3, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l3, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l3, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l3, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l3, 0, 1) != Catch::Approx(5));
+
+      REQUIRE(get_order(p4, l3, -1, 1, 6) == Catch::Approx(4));
+    }
+
+    SECTION("degree 4 and 5")
+    {
+      const QuadratureFormula<1>& l4 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(4));
+      const QuadratureFormula<1>& l5 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(5));
+
+      REQUIRE(&l4 == &l5);
+      REQUIRE(is_symmetric_formula(l5));
+
+      REQUIRE(l5.numberOfPoints() == 3);
+
+      REQUIRE(integrate(p0, l5, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l5, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l5, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l5, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l5, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l5, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l5, 0, 1) != Catch::Approx(7));
+
+      REQUIRE(get_order(p6, l5, -1, 1, 8) == Catch::Approx(6));
+    }
+
+    SECTION("degree 6 and 7")
+    {
+      const QuadratureFormula<1>& l6 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(6));
+      const QuadratureFormula<1>& l7 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(7));
+
+      REQUIRE(&l6 == &l7);
+      REQUIRE(is_symmetric_formula(l7));
+
+      REQUIRE(l7.numberOfPoints() == 4);
+
+      REQUIRE(integrate(p0, l7, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l7, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l7, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l7, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l7, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l7, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l7, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l7, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l7, 0, 1) != Catch::Approx(9));
+
+      REQUIRE(get_order(p8, l7, -1, 1, 10) == Catch::Approx(8));
+    }
+
+    SECTION("degree 8 and 9")
+    {
+      const QuadratureFormula<1>& l8 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(8));
+      const QuadratureFormula<1>& l9 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(9));
+
+      REQUIRE(&l8 == &l9);
+      REQUIRE(is_symmetric_formula(l9));
+
+      REQUIRE(l9.numberOfPoints() == 5);
+
+      REQUIRE(integrate(p0, l9, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l9, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l9, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l9, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l9, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l9, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l9, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l9, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l9, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l9, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l9, 0, 1) != Catch::Approx(11).epsilon(1E-13));
+
+      REQUIRE(get_order(p10, l9, -1, 1, 12) == Catch::Approx(10));
+    }
+
+    SECTION("degree 10 and 11")
+    {
+      const QuadratureFormula<1>& l10 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(10));
+      const QuadratureFormula<1>& l11 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(11));
+
+      REQUIRE(&l10 == &l11);
+      REQUIRE(is_symmetric_formula(l11));
+
+      REQUIRE(l11.numberOfPoints() == 6);
+
+      REQUIRE(integrate(p0, l11, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l11, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l11, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l11, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l11, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l11, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l11, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l11, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l11, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l11, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l11, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l11, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l11, 0, 1) != Catch::Approx(13).epsilon(1E-13));
+
+      REQUIRE(get_order(p12, l11, -1, 1, 14) == Catch::Approx(12));
+    }
+
+    SECTION("degree 12 and 13")
+    {
+      const QuadratureFormula<1>& l12 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(12));
+      const QuadratureFormula<1>& l13 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(13));
+
+      REQUIRE(&l12 == &l13);
+      REQUIRE(is_symmetric_formula(l13));
+
+      REQUIRE(l13.numberOfPoints() == 7);
+
+      REQUIRE(integrate(p0, l13, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l13, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l13, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l13, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l13, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l13, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l13, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l13, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l13, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l13, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l13, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l13, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l13, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l13, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l13, 0, 1) != Catch::Approx(15).epsilon(1E-13));
+
+      REQUIRE(get_order(p14, l13, -1, 1, 16) == Catch::Approx(14));
+    }
+
+    SECTION("degree 14 and 15")
+    {
+      const QuadratureFormula<1>& l14 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(14));
+      const QuadratureFormula<1>& l15 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(15));
+
+      REQUIRE(&l14 == &l15);
+      REQUIRE(is_symmetric_formula(l15));
+
+      REQUIRE(l15.numberOfPoints() == 8);
+
+      REQUIRE(integrate(p0, l15, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l15, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l15, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l15, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l15, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l15, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l15, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l15, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l15, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l15, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l15, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l15, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l15, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l15, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l15, 0, 1) == Catch::Approx(15));
+      REQUIRE(integrate(p15, l15, 0, 1) == Catch::Approx(16));
+      REQUIRE(integrate(p16, l15, 0, 1) != Catch::Approx(17).epsilon(1E-13));
+
+      REQUIRE(get_order(p16, l15, -1, 1, 18) == Catch::Approx(16));
+    }
+
+    SECTION("degree 16 and 17")
+    {
+      const QuadratureFormula<1>& l16 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(16));
+      const QuadratureFormula<1>& l17 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(17));
+
+      REQUIRE(&l16 == &l17);
+      REQUIRE(is_symmetric_formula(l17));
+
+      REQUIRE(l17.numberOfPoints() == 9);
+
+      REQUIRE(integrate(p0, l17, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l17, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l17, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l17, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l17, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l17, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l17, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l17, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l17, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l17, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l17, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l17, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l17, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l17, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l17, 0, 1) == Catch::Approx(15));
+      REQUIRE(integrate(p15, l17, 0, 1) == Catch::Approx(16));
+      REQUIRE(integrate(p16, l17, 0, 1) == Catch::Approx(17));
+      REQUIRE(integrate(p17, l17, 0, 1) == Catch::Approx(18));
+      REQUIRE(integrate(p18, l17, 0, 1) != Catch::Approx(19).epsilon(1E-13));
+
+      REQUIRE(get_order(p18, l17, -1, 1, 20) == Catch::Approx(18));
+    }
+
+    SECTION("degree 18 and 19")
+    {
+      const QuadratureFormula<1>& l18 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(18));
+      const QuadratureFormula<1>& l19 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(19));
+
+      REQUIRE(&l18 == &l19);
+      REQUIRE(is_symmetric_formula(l19));
+
+      REQUIRE(l19.numberOfPoints() == 10);
+
+      REQUIRE(integrate(p0, l19, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l19, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l19, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l19, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l19, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l19, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l19, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l19, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l19, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l19, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l19, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l19, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l19, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l19, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l19, 0, 1) == Catch::Approx(15));
+      REQUIRE(integrate(p15, l19, 0, 1) == Catch::Approx(16));
+      REQUIRE(integrate(p16, l19, 0, 1) == Catch::Approx(17));
+      REQUIRE(integrate(p17, l19, 0, 1) == Catch::Approx(18));
+      REQUIRE(integrate(p18, l19, 0, 1) == Catch::Approx(19));
+      REQUIRE(integrate(p19, l19, 0, 1) == Catch::Approx(20));
+      REQUIRE(integrate(p20, l19, 0, 1) != Catch::Approx(21).epsilon(1E-13));
+
+      REQUIRE(get_order(p20, l19, -1, 1, 22) == Catch::Approx(20));
+    }
+
+    SECTION("degree 20 and 21")
+    {
+      const QuadratureFormula<1>& l20 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(20));
+      const QuadratureFormula<1>& l21 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(21));
+
+      REQUIRE(&l20 == &l21);
+      REQUIRE(is_symmetric_formula(l21));
+
+      REQUIRE(l21.numberOfPoints() == 11);
+
+      REQUIRE(integrate(p0, l21, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l21, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l21, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l21, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l21, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l21, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l21, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l21, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l21, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l21, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l21, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l21, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l21, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l21, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l21, 0, 1) == Catch::Approx(15));
+      REQUIRE(integrate(p15, l21, 0, 1) == Catch::Approx(16));
+      REQUIRE(integrate(p16, l21, 0, 1) == Catch::Approx(17));
+      REQUIRE(integrate(p17, l21, 0, 1) == Catch::Approx(18));
+      REQUIRE(integrate(p18, l21, 0, 1) == Catch::Approx(19));
+      REQUIRE(integrate(p19, l21, 0, 1) == Catch::Approx(20));
+      REQUIRE(integrate(p20, l21, 0, 1) == Catch::Approx(21));
+      REQUIRE(integrate(p21, l21, 0, 1) == Catch::Approx(22));
+      REQUIRE(integrate(p22, l21, 0, 1) != Catch::Approx(23).epsilon(1E-14));
+
+      REQUIRE(get_order(p22, l21, -1, 1, 24) == Catch::Approx(22).margin(0.02));
+    }
+
+    SECTION("degree 22 and 23")
+    {
+      const QuadratureFormula<1>& l22 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(22));
+      const QuadratureFormula<1>& l23 =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(23));
+
+      REQUIRE(&l22 == &l23);
+      REQUIRE(is_symmetric_formula(l23));
+
+      REQUIRE(l23.numberOfPoints() == 12);
+
+      REQUIRE(integrate(p0, l23, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l23, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l23, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l23, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l23, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l23, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l23, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l23, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l23, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l23, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l23, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l23, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l23, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l23, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l23, 0, 1) == Catch::Approx(15));
+      REQUIRE(integrate(p15, l23, 0, 1) == Catch::Approx(16));
+      REQUIRE(integrate(p16, l23, 0, 1) == Catch::Approx(17));
+      REQUIRE(integrate(p17, l23, 0, 1) == Catch::Approx(18));
+      REQUIRE(integrate(p18, l23, 0, 1) == Catch::Approx(19));
+      REQUIRE(integrate(p19, l23, 0, 1) == Catch::Approx(20));
+      REQUIRE(integrate(p20, l23, 0, 1) == Catch::Approx(21));
+      REQUIRE(integrate(p21, l23, 0, 1) == Catch::Approx(22));
+      REQUIRE(integrate(p22, l23, 0, 1) == Catch::Approx(23));
+      REQUIRE(integrate(p23, l23, 0, 1) == Catch::Approx(24));
+      REQUIRE(integrate(p24, l23, 0, 1) != Catch::Approx(25).epsilon(1E-15));
+
+      REQUIRE(get_order(p24, l23, -1, 1, 26) == Catch::Approx(24).margin(0.05));
+    }
+
+    SECTION("max implemented degree")
+    {
+      REQUIRE(QuadratureManager::instance().maxLineDegree(QuadratureType::GaussLegendre) ==
+              TensorialGaussLegendreQuadrature<1>::max_degree);
+    }
+  }
+
+  SECTION("2D")
+  {
+    auto integrate = [](auto f, auto quadrature_formula, const double xa, const double xb, const double ya,
+                        const double yb) {
+      auto point_list  = quadrature_formula.pointList();
+      auto weight_list = quadrature_formula.weightList();
+
+      const double alphax = 0.5 * (xb - xa);
+      const double betax  = 0.5 * (xa + xb);
+
+      const double alphay = 0.5 * (yb - ya);
+      const double betay  = 0.5 * (ya + yb);
+
+      auto x = [&alphax, &betax, &alphay, &betay](auto x_hat) {
+        return TinyVector<2>{alphax * x_hat[0] + betax, alphay * x_hat[1] + betay};
+      };
+
+      auto value = weight_list[0] * f(x(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * f(x(point_list[i]));
+      }
+
+      return alphax * alphay * value;
+    };
+
+    auto p6 = [](const double x) { return 7 * std::pow(x, 6); };
+    auto p7 = [](const double x) { return 8 * std::pow(x, 7); };
+
+    auto px7y6 = [&p6, &p7](const TinyVector<2>& X) {
+      const double x = X[0];
+      const double y = X[1];
+      return p7(x) * p6(y);
+    };
+
+    SECTION("degree 6 and 7")
+    {
+      const QuadratureFormula<2>& l6 =
+        QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(6));
+      const QuadratureFormula<2>& l7 =
+        QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(7));
+
+      REQUIRE(&l6 == &l7);
+
+      REQUIRE(l7.numberOfPoints() == 4 * 4);
+
+      REQUIRE(integrate(px7y6, l7, 0, 1, 0.2, 0.8) == Catch::Approx(std::pow(0.8, 7) - std::pow(0.2, 7)));
+    }
+  }
+
+  SECTION("3D")
+  {
+    auto integrate = [](auto f, auto quadrature_formula, const double xa, const double xb, const double ya,
+                        const double yb, const double za, const double zb) {
+      auto point_list  = quadrature_formula.pointList();
+      auto weight_list = quadrature_formula.weightList();
+
+      const double alphax = 0.5 * (xb - xa);
+      const double betax  = 0.5 * (xa + xb);
+
+      const double alphay = 0.5 * (yb - ya);
+      const double betay  = 0.5 * (ya + yb);
+
+      const double alphaz = 0.5 * (zb - za);
+      const double betaz  = 0.5 * (za + zb);
+
+      auto x = [&alphax, &betax, &alphay, &betay, &alphaz, &betaz](auto x_hat) {
+        return TinyVector<3>{alphax * x_hat[0] + betax, alphay * x_hat[1] + betay, alphaz * x_hat[2] + betaz};
+      };
+
+      auto value = weight_list[0] * f(x(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * f(x(point_list[i]));
+      }
+
+      return alphax * alphay * alphaz * value;
+    };
+
+    auto p6 = [](const double x) { return 7 * std::pow(x, 6); };
+    auto p7 = [](const double x) { return 8 * std::pow(x, 7); };
+
+    auto px7y6 = [&p6, &p7](const TinyVector<3>& X) {
+      const double x = X[0];
+      const double y = X[1];
+      const double z = X[2];
+      return p7(x) * p6(y) + p7(z);
+    };
+
+    SECTION("degree 6 and 7")
+    {
+      const QuadratureFormula<3>& l6 =
+        QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(6));
+      const QuadratureFormula<3>& l7 =
+        QuadratureManager::instance().getCubeFormula(GaussLegendreQuadratureDescriptor(7));
+
+      REQUIRE(&l6 == &l7);
+
+      REQUIRE(l7.numberOfPoints() == 4 * 4 * 4);
+
+      REQUIRE(integrate(px7y6, l7, 0, 1, 0.2, 0.8, -0.1, 0.7) ==
+              Catch::Approx((std::pow(0.8, 7) - std::pow(0.2, 7)) * (0.7 - -0.1) +
+                            (0.8 - 0.2) * (std::pow(0.7, 8) - std::pow(-0.1, 8))));
+    }
+  }
+}
diff --git a/tests/test_TensorialGaussLobattoQuadrature.cpp b/tests/test_TensorialGaussLobattoQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1c08404ecaaf98fedb4dfa841a5ed8d845cfca09
--- /dev/null
+++ b/tests/test_TensorialGaussLobattoQuadrature.cpp
@@ -0,0 +1,420 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <analysis/GaussLobattoQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <analysis/TensorialGaussLobattoQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("TensorialGaussLobattoQuadrature", "[analysis]")
+{
+  SECTION("1D")
+  {
+    auto is_symmetric_formula = [](auto quadrature_formula) {
+      auto point_list  = quadrature_formula.pointList();
+      auto weight_list = quadrature_formula.weightList();
+
+      bool is_symmetric = true;
+
+      const size_t middle_index = point_list.size() / 2;
+      for (size_t i = 0; i <= middle_index; ++i) {
+        if (point_list[i] != -point_list[point_list.size() - 1 - i]) {
+          return false;
+        }
+      }
+      for (size_t i = 0; i <= middle_index; ++i) {
+        if (weight_list[i] != weight_list[point_list.size() - 1 - i]) {
+          return false;
+        }
+      }
+
+      return is_symmetric;
+    };
+
+    auto integrate = [](auto f, auto quadrature_formula, const double a, const double b) {
+      auto point_list  = quadrature_formula.pointList();
+      auto weight_list = quadrature_formula.weightList();
+
+      double alpha = 0.5 * (b - a);
+      double beta  = 0.5 * (a + b);
+
+      auto x = [&alpha, &beta](auto x_hat) { return alpha * x_hat + beta; };
+
+      auto value = weight_list[0] * f(x(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * f(x(point_list[i]));
+      }
+
+      return alpha * value;
+    };
+
+    auto get_order = [&integrate](auto f, auto quadrature_formula, const double a, const double b,
+                                  const double exact_value) {
+      double int_ab          = integrate(f, quadrature_formula, a, b);
+      double int_first_half  = integrate(f, quadrature_formula, a, 0.5 * (a + b));
+      double int_second_half = integrate(f, quadrature_formula, 0.5 * (a + b), b);
+
+      return -std::log((int_first_half + int_second_half - exact_value) / (int_ab - exact_value)) / std::log(2);
+    };
+
+    auto p0 = [](const TinyVector<1>&) { return 1; };
+    auto p1 = [&p0](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 2 * x + p0(X);
+    };
+    auto p2 = [&p1](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 3 * x * x + p1(X);
+    };
+    auto p3 = [&p2](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 4 * std::pow(x, 3) + p2(X);
+    };
+    auto p4 = [&p3](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 5 * std::pow(x, 4) + p3(X);
+    };
+    auto p5 = [&p4](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 6 * std::pow(x, 5) + p4(X);
+    };
+    auto p6 = [&p5](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 7 * std::pow(x, 6) + p5(X);
+    };
+    auto p7 = [&p6](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 8 * std::pow(x, 7) + p6(X);
+    };
+    auto p8 = [&p7](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 9 * std::pow(x, 8) + p7(X);
+    };
+    auto p9 = [&p8](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 10 * std::pow(x, 9) + p8(X);
+    };
+    auto p10 = [&p9](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 11 * std::pow(x, 10) + p9(X);
+    };
+    auto p11 = [&p10](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 12 * std::pow(x, 11) + p10(X);
+    };
+    auto p12 = [&p11](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 13 * std::pow(x, 12) + p11(X);
+    };
+    auto p13 = [&p12](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 14 * std::pow(x, 13) + p12(X);
+    };
+    auto p14 = [&p13](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 15 * std::pow(x, 14) + p13(X);
+    };
+
+    SECTION("degree 1")
+    {
+      const QuadratureFormula<1>& l1 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(1));
+
+      REQUIRE(is_symmetric_formula(l1));
+
+      REQUIRE(l1.numberOfPoints() == 2);
+
+      REQUIRE(integrate(p0, l1, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l1, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l1, 0, 1) != Catch::Approx(3));
+
+      REQUIRE(get_order(p2, l1, -1, 1, 4) == Catch::Approx(2));
+    }
+
+    SECTION("degree 2 and 3")
+    {
+      const QuadratureFormula<1>& l2 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(2));
+      const QuadratureFormula<1>& l3 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(3));
+
+      REQUIRE(&l2 == &l3);
+      REQUIRE(is_symmetric_formula(l3));
+
+      REQUIRE(l3.numberOfPoints() == 3);
+
+      REQUIRE(integrate(p0, l3, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l3, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l3, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l3, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l3, 0, 1) != Catch::Approx(5));
+
+      REQUIRE(get_order(p4, l3, -1, 1, 6) == Catch::Approx(4));
+    }
+
+    SECTION("degree 4 and 5")
+    {
+      const QuadratureFormula<1>& l4 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(4));
+      const QuadratureFormula<1>& l5 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(5));
+
+      REQUIRE(&l4 == &l5);
+      REQUIRE(is_symmetric_formula(l5));
+
+      REQUIRE(l5.numberOfPoints() == 4);
+
+      REQUIRE(integrate(p0, l5, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l5, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l5, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l5, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l5, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l5, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l5, 0, 1) != Catch::Approx(7));
+
+      REQUIRE(get_order(p6, l5, -1, 1, 8) == Catch::Approx(6));
+    }
+
+    SECTION("degree 6 and 7")
+    {
+      const QuadratureFormula<1>& l6 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(6));
+      const QuadratureFormula<1>& l7 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(7));
+
+      REQUIRE(&l6 == &l7);
+      REQUIRE(is_symmetric_formula(l7));
+
+      REQUIRE(l7.numberOfPoints() == 5);
+
+      REQUIRE(integrate(p0, l7, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l7, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l7, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l7, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l7, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l7, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l7, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l7, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l7, 0, 1) != Catch::Approx(9));
+
+      REQUIRE(get_order(p8, l7, -1, 1, 10) == Catch::Approx(8));
+    }
+
+    SECTION("degree 8 and 9")
+    {
+      const QuadratureFormula<1>& l8 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(8));
+      const QuadratureFormula<1>& l9 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(9));
+
+      REQUIRE(&l8 == &l9);
+      REQUIRE(is_symmetric_formula(l9));
+
+      REQUIRE(l9.numberOfPoints() == 6);
+
+      REQUIRE(integrate(p0, l9, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l9, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l9, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l9, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l9, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l9, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l9, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l9, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l9, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l9, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l9, 0, 1) != Catch::Approx(11).epsilon(1E-13));
+
+      REQUIRE(get_order(p10, l9, -1, 1, 12) == Catch::Approx(10));
+    }
+
+    SECTION("degree 10 and 11")
+    {
+      const QuadratureFormula<1>& l10 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(10));
+      const QuadratureFormula<1>& l11 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(11));
+
+      REQUIRE(&l10 == &l11);
+      REQUIRE(is_symmetric_formula(l11));
+
+      REQUIRE(l11.numberOfPoints() == 7);
+
+      REQUIRE(integrate(p0, l11, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l11, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l11, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l11, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l11, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l11, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l11, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l11, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l11, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l11, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l11, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l11, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l11, 0, 1) != Catch::Approx(13).epsilon(1E-13));
+
+      REQUIRE(get_order(p12, l11, -1, 1, 14) == Catch::Approx(12));
+    }
+
+    SECTION("degree 12 and 13")
+    {
+      const QuadratureFormula<1>& l12 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(12));
+      const QuadratureFormula<1>& l13 =
+        QuadratureManager::instance().getLineFormula(GaussLobattoQuadratureDescriptor(13));
+
+      REQUIRE(&l12 == &l13);
+      REQUIRE(is_symmetric_formula(l13));
+
+      REQUIRE(l13.numberOfPoints() == 8);
+
+      REQUIRE(integrate(p0, l13, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l13, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l13, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l13, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l13, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l13, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l13, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l13, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l13, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l13, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l13, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l13, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l13, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l13, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l13, 0, 1) != Catch::Approx(15).epsilon(1E-13));
+
+      REQUIRE(get_order(p14, l13, -1, 1, 16) == Catch::Approx(14));
+    }
+
+    SECTION("max implemented degree")
+    {
+      REQUIRE(QuadratureManager::instance().maxLineDegree(QuadratureType::GaussLobatto) ==
+              TensorialGaussLobattoQuadrature<1>::max_degree);
+    }
+  }
+
+  SECTION("2D")
+  {
+    auto integrate = [](auto f, auto quadrature_formula, const double xa, const double xb, const double ya,
+                        const double yb) {
+      auto point_list  = quadrature_formula.pointList();
+      auto weight_list = quadrature_formula.weightList();
+
+      const double alphax = 0.5 * (xb - xa);
+      const double betax  = 0.5 * (xa + xb);
+
+      const double alphay = 0.5 * (yb - ya);
+      const double betay  = 0.5 * (ya + yb);
+
+      auto x = [&alphax, &betax, &alphay, &betay](auto x_hat) {
+        return TinyVector<2>{alphax * x_hat[0] + betax, alphay * x_hat[1] + betay};
+      };
+
+      auto value = weight_list[0] * f(x(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * f(x(point_list[i]));
+      }
+
+      return alphax * alphay * value;
+    };
+
+    auto p6 = [](const double x) { return 7 * std::pow(x, 6); };
+    auto p7 = [](const double x) { return 8 * std::pow(x, 7); };
+
+    auto px7y6 = [&p6, &p7](const TinyVector<2>& X) {
+      const double x = X[0];
+      const double y = X[1];
+      return p7(x) * p6(y);
+    };
+
+    SECTION("degree 6 and 7")
+    {
+      const QuadratureFormula<2>& l6 =
+        QuadratureManager::instance().getSquareFormula(GaussLobattoQuadratureDescriptor(6));
+      const QuadratureFormula<2>& l7 =
+        QuadratureManager::instance().getSquareFormula(GaussLobattoQuadratureDescriptor(7));
+
+      REQUIRE(&l6 == &l7);
+
+      REQUIRE(l7.numberOfPoints() == 5 * 5);
+
+      REQUIRE(integrate(px7y6, l7, 0, 1, 0.2, 0.8) == Catch::Approx(std::pow(0.8, 7) - std::pow(0.2, 7)));
+    }
+  }
+
+  SECTION("3D")
+  {
+    auto integrate = [](auto f, auto quadrature_formula, const double xa, const double xb, const double ya,
+                        const double yb, const double za, const double zb) {
+      auto point_list  = quadrature_formula.pointList();
+      auto weight_list = quadrature_formula.weightList();
+
+      const double alphax = 0.5 * (xb - xa);
+      const double betax  = 0.5 * (xa + xb);
+
+      const double alphay = 0.5 * (yb - ya);
+      const double betay  = 0.5 * (ya + yb);
+
+      const double alphaz = 0.5 * (zb - za);
+      const double betaz  = 0.5 * (za + zb);
+
+      auto x = [&alphax, &betax, &alphay, &betay, &alphaz, &betaz](auto x_hat) {
+        return TinyVector<3>{alphax * x_hat[0] + betax, alphay * x_hat[1] + betay, alphaz * x_hat[2] + betaz};
+      };
+
+      auto value = weight_list[0] * f(x(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * f(x(point_list[i]));
+      }
+
+      return alphax * alphay * alphaz * value;
+    };
+
+    auto p6 = [](const double x) { return 7 * std::pow(x, 6); };
+    auto p7 = [](const double x) { return 8 * std::pow(x, 7); };
+
+    auto px7y6 = [&p6, &p7](const TinyVector<3>& X) {
+      const double x = X[0];
+      const double y = X[1];
+      const double z = X[2];
+      return p7(x) * p6(y) + p7(z);
+    };
+
+    SECTION("degree 6 and 7")
+    {
+      const QuadratureFormula<3>& l6 =
+        QuadratureManager::instance().getCubeFormula(GaussLobattoQuadratureDescriptor(6));
+      const QuadratureFormula<3>& l7 =
+        QuadratureManager::instance().getCubeFormula(GaussLobattoQuadratureDescriptor(7));
+
+      REQUIRE(&l6 == &l7);
+
+      REQUIRE(l7.numberOfPoints() == 5 * 5 * 5);
+
+      REQUIRE(integrate(px7y6, l7, 0, 1, 0.2, 0.8, -0.1, 0.7) ==
+              Catch::Approx((std::pow(0.8, 7) - std::pow(0.2, 7)) * (0.7 - -0.1) +
+                            (0.8 - 0.2) * (std::pow(0.7, 8) - std::pow(-0.1, 8))));
+    }
+  }
+
+  SECTION("Access functions")
+  {
+    const QuadratureFormula<3>& quadrature_formula =
+      QuadratureManager::instance().getCubeFormula(GaussLobattoQuadratureDescriptor(7));
+
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    REQUIRE(point_list.size() == quadrature_formula.numberOfPoints());
+    REQUIRE(weight_list.size() == quadrature_formula.numberOfPoints());
+
+    for (size_t i = 0; i < quadrature_formula.numberOfPoints(); ++i) {
+      REQUIRE(&point_list[i] == &quadrature_formula.point(i));
+      REQUIRE(&weight_list[i] == &quadrature_formula.weight(i));
+    }
+  }
+}
diff --git a/tests/test_TetrahedronGaussQuadrature.cpp b/tests/test_TetrahedronGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..605d1dc1fa7476719fe16ac595ff55491f5bd8e8
--- /dev/null
+++ b/tests/test_TetrahedronGaussQuadrature.cpp
@@ -0,0 +1,683 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <analysis/TetrahedronGaussQuadrature.hpp>
+#include <geometry/TetrahedronTransformation.hpp>
+#include <utils/Exceptions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("TetrahedronGaussQuadrature", "[analysis]")
+{
+  auto integrate = [](auto f, auto quadrature_formula) {
+    using R3 = TinyVector<3>;
+
+    const R3 A{-1, -1, -1};
+    const R3 B{+1, -1, -1};
+    const R3 C{-1, +1, -1};
+    const R3 D{-1, -1, +1};
+
+    TetrahedronTransformation t{A, B, C, D};
+
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    auto value = weight_list[0] * f(t(point_list[0]));
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(t(point_list[i]));
+    }
+
+    return t.jacobianDeterminant() * value;
+  };
+
+  auto integrate_on_tetra = [](auto f, auto quadrature_formula, const TinyVector<3>& a, const TinyVector<3>& b,
+                               const TinyVector<3>& c, const TinyVector<3>& d) {
+    TetrahedronTransformation t{a, b, c, d};
+
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    auto value = weight_list[0] * f(t(point_list[0]));
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(t(point_list[i]));
+    }
+
+    return t.jacobianDeterminant() * value;
+  };
+
+  auto get_order = [&integrate, &integrate_on_tetra](auto f, auto quadrature_formula, const double exact_value) {
+    using R3 = TinyVector<3>;
+
+    const R3 A{-1, -1, -1};
+    const R3 B{+1, -1, -1};
+    const R3 C{-1, +1, -1};
+    const R3 D{-1, -1, +1};
+
+    const R3 M_AB = 0.5 * (A + B);
+    const R3 M_AC = 0.5 * (A + C);
+    const R3 M_AD = 0.5 * (A + D);
+    const R3 M_BC = 0.5 * (B + C);
+    const R3 M_BD = 0.5 * (B + D);
+    const R3 M_CD = 0.5 * (C + D);
+
+    const double int_T_hat = integrate(f, quadrature_formula);
+    const double int_refined   //
+      = integrate_on_tetra(f, quadrature_formula, A, M_AB, M_AC, M_AD) +
+        integrate_on_tetra(f, quadrature_formula, B, M_AB, M_BD, M_BC) +
+        integrate_on_tetra(f, quadrature_formula, C, M_AC, M_BC, M_CD) +
+        integrate_on_tetra(f, quadrature_formula, D, M_AD, M_CD, M_BD) +
+        integrate_on_tetra(f, quadrature_formula, M_BD, M_AC, M_AD, M_CD) +
+        integrate_on_tetra(f, quadrature_formula, M_AB, M_AC, M_AD, M_BD) +
+        integrate_on_tetra(f, quadrature_formula, M_BC, M_AB, M_BD, M_AC) +
+        integrate_on_tetra(f, quadrature_formula, M_BC, M_AC, M_BD, M_CD);
+
+    return -std::log(std::abs(int_refined - exact_value) / std::abs(int_T_hat - exact_value)) / std::log(2);
+  };
+
+  auto p0 = [](const TinyVector<3>&) { return 4; };
+  auto p1 = [](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return 2 * x + 3 * y + z - 1;
+  };
+  auto p2 = [&p1](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p1(X) * (2.5 * x - 3 * y + z + 3);
+  };
+  auto p3 = [&p2](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p2(X) * (3 * x + 2 * y - 3 * z - 1);
+  };
+  auto p4 = [&p3](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p3(X) * (2 * x - 0.5 * y - 1.3 * z + 1);
+  };
+  auto p5 = [&p4](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p4(X) * (-0.1 * x + 1.3 * y - 3 * z + 1);
+  };
+  auto p6 = [&p5](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p5(X) * 7875. / 143443 * (2 * x - y + 4 * z + 1);
+  };
+  auto p7 = [&p6](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p6(X) * (0.7 * x - 2.7 * y + 1.3 * z - 2);
+  };
+  auto p8 = [&p7](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p7(X) * (0.3 * x + 1.2 * y - 0.7 * z + 0.2);
+  };
+  auto p9 = [&p8](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p8(X) * (-0.2 * x + 1.1 * y - 0.5 * z + 0.6);
+  };
+  auto p10 = [&p9](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p9(X) * (0.7 * x - 0.6 * y - 0.7 * z - 0.2);
+  };
+  auto p11 = [&p10](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p10(X) * (-1.3 * x + 0.6 * y - 1.3 * z + 0.7);
+  };
+  auto p12 = [&p11](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p11(X) * (0.3 * x - 0.7 * y + 0.3 * z + 0.7);
+  };
+  auto p13 = [&p12](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p12(X) * (0.9 * x + 0.2 * y - 0.4 * z + 0.5);
+  };
+  auto p14 = [&p13](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p13(X) * (0.6 * x - 1.2 * y + 0.7 * z - 0.4);
+  };
+  auto p15 = [&p14](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p14(X) * (-0.2 * x - 0.7 * y + 0.9 * z + 0.8);
+  };
+  auto p16 = [&p15](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p15(X) * (0.7 * x + 0.2 * y - 0.6 * z + 0.4);
+  };
+  auto p17 = [&p16](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p16(X) * (-0.1 * x + 0.8 * y + 0.3 * z - 0.2);
+  };
+  auto p18 = [&p17](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p17(X) * (0.7 * x - 0.2 * y - 0.3 * z + 0.8);
+  };
+  auto p19 = [&p18](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p18(X) * (-0.7 * x + 1.2 * y + 1.3 * z + 0.8);
+  };
+  auto p20 = [&p19](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p19(X) * (0.7 * x - 1.2 * y + 0.3 * z - 0.6);
+  };
+  auto p21 = [&p20](const TinyVector<3>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    const double z = X[2];
+    return p20(X) * (0.7 * x - 1.2 * y + 0.3 * z - 0.6);
+  };
+
+  SECTION("degree 1")
+  {
+    const QuadratureFormula<3>& l1 = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(1));
+
+    REQUIRE(l1.numberOfPoints() == 1);
+
+    REQUIRE(integrate(p0, l1) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l1) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l1) != Catch::Approx(-47. / 3));
+
+    REQUIRE(get_order(p2, l1, -47. / 3) >= Catch::Approx(2));
+  }
+
+  SECTION("degree 2")
+  {
+    const QuadratureFormula<3>& l2 = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(2));
+
+    REQUIRE(l2.numberOfPoints() == 4);
+
+    REQUIRE(integrate(p0, l2) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l2) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l2) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l2) != Catch::Approx(557. / 15));
+
+    REQUIRE(get_order(p3, l2, 557. / 15) >= Catch::Approx(3));
+  }
+
+  SECTION("degree 3")
+  {
+    const QuadratureFormula<3>& l3 = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(3));
+
+    REQUIRE(l3.numberOfPoints() == 8);
+
+    REQUIRE(integrate(p0, l3) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l3) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l3) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l3) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l3) != Catch::Approx(4499. / 1575));
+  }
+
+  SECTION("degree 4")
+  {
+    const QuadratureFormula<3>& l4 = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(4));
+
+    REQUIRE(l4.numberOfPoints() == 14);
+
+    REQUIRE(integrate(p0, l4) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l4) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l4) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l4) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l4) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l4) != Catch::Approx(143443. / 7875));
+
+    REQUIRE(get_order(p5, l4, 143443. / 7875) >= Catch::Approx(5));
+  }
+
+  SECTION("degree 5")
+  {
+    const QuadratureFormula<3>& l5 = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(5));
+
+    REQUIRE(l5.numberOfPoints() == 14);
+
+    REQUIRE(integrate(p0, l5) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l5) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l5) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l5) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l5) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l5) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l5) != Catch::Approx(-75773. / 2581974));
+  }
+
+  SECTION("degree 6")
+  {
+    const QuadratureFormula<3>& l6 = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(6));
+
+    REQUIRE(l6.numberOfPoints() == 24);
+
+    REQUIRE(integrate(p0, l6) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l6) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l6) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l6) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l6) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l6) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l6) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l6) != Catch::Approx(86951548. / 32274675));
+
+    REQUIRE(get_order(p7, l6, 86951548. / 32274675) >= Catch::Approx(7));
+  }
+
+  SECTION("degree 7")
+  {
+    const QuadratureFormula<3>& l7 = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(7));
+
+    REQUIRE(l7.numberOfPoints() == 35);
+
+    REQUIRE(integrate(p0, l7) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l7) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l7) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l7) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l7) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l7) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l7) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l7) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l7) != Catch::Approx(-863556317. / 322746750));
+
+    REQUIRE(get_order(p8, l7, -863556317. / 322746750) == Catch::Approx(8).margin(0.5));
+  }
+
+  SECTION("degree 8")
+  {
+    const QuadratureFormula<3>& l8 = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(8));
+
+    REQUIRE(l8.numberOfPoints() == 46);
+
+    REQUIRE(integrate(p0, l8) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l8) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l8) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l8) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l8) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l8) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l8) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l8) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l8) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l8) != Catch::Approx(1168568393. / 3227467500));
+
+    // In a weird way, one gets almost 10th order on this test
+    REQUIRE(get_order(p9, l8, 1168568393. / 3227467500) == Catch::Approx(10));
+  }
+
+  SECTION("degree 9")
+  {
+    const QuadratureFormula<3>& l9 = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(9));
+
+    REQUIRE(l9.numberOfPoints() == 59);
+
+    REQUIRE(integrate(p0, l9) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l9) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l9) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l9) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l9) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l9) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l9) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l9) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l9) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l9) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l9) != Catch::Approx(-473611706567. / 461527852500));
+
+    // No order test. Too bad ~4.5 when one expects 10 asymptotically
+  }
+
+  SECTION("degree 10")
+  {
+    const QuadratureFormula<3>& l10 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(10));
+
+    REQUIRE(l10.numberOfPoints() == 81);
+
+    REQUIRE(integrate(p0, l10) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l10) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l10) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l10) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l10) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l10) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l10) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l10) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l10) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l10) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l10) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l10) != Catch::Approx(-340332578501887. / 323069496750000));
+
+    // In a weird way, one gets almost 12th order on this test
+    REQUIRE(get_order(p11, l10, -340332578501887. / 323069496750000) == Catch::Approx(12));
+  }
+
+  SECTION("degree 11")
+  {
+    const QuadratureFormula<3>& l11 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(11));
+
+    REQUIRE(l11.numberOfPoints() == 110);
+
+    REQUIRE(integrate(p0, l11) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l11) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l11) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l11) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l11) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l11) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l11) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l11) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l11) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l11) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l11) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l11) == Catch::Approx(-340332578501887. / 323069496750000));
+    REQUIRE(integrate(p12, l11) != Catch::Approx(-9990191769716047. / 16153474837500000));
+
+    // No order test. Too bad ~11 when one expects 12 asymptotically
+  }
+
+  SECTION("degree 12")
+  {
+    const QuadratureFormula<3>& l12 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(12));
+
+    REQUIRE(l12.numberOfPoints() == 168);
+
+    REQUIRE(integrate(p0, l12) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l12) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l12) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l12) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l12) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l12) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l12) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l12) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l12) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l12) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l12) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l12) == Catch::Approx(-340332578501887. / 323069496750000));
+    REQUIRE(integrate(p12, l12) == Catch::Approx(-9990191769716047. / 16153474837500000));
+    REQUIRE(integrate(p13, l12) != Catch::Approx(-31229729533861. / 5384491612500000));
+
+    // In a weird way, one gets almost 14th order on this test
+    REQUIRE(get_order(p13, l12, -31229729533861. / 5384491612500000) == Catch::Approx(14).margin(0.01));
+  }
+
+  SECTION("degree 13")
+  {
+    const QuadratureFormula<3>& l13 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(13));
+
+    REQUIRE(l13.numberOfPoints() == 172);
+
+    REQUIRE(integrate(p0, l13) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l13) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l13) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l13) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l13) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l13) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l13) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l13) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l13) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l13) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l13) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l13) == Catch::Approx(-340332578501887. / 323069496750000));
+    REQUIRE(integrate(p12, l13) == Catch::Approx(-9990191769716047. / 16153474837500000));
+    REQUIRE(integrate(p13, l13) == Catch::Approx(-31229729533861. / 5384491612500000));
+    REQUIRE(integrate(p14, l13) != Catch::Approx(3758162710897404343. / 13730453611875000000.));
+
+    REQUIRE(get_order(p14, l13, 3758162710897404343. / 13730453611875000000.) == Catch::Approx(14).margin(0.5));
+  }
+
+  SECTION("degree 14")
+  {
+    const QuadratureFormula<3>& l14 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(14));
+
+    REQUIRE(l14.numberOfPoints() == 204);
+
+    REQUIRE(integrate(p0, l14) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l14) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l14) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l14) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l14) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l14) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l14) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l14) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l14) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l14) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l14) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l14) == Catch::Approx(-340332578501887. / 323069496750000));
+    REQUIRE(integrate(p12, l14) == Catch::Approx(-9990191769716047. / 16153474837500000));
+    REQUIRE(integrate(p13, l14) == Catch::Approx(-31229729533861. / 5384491612500000));
+    REQUIRE(integrate(p14, l14) == Catch::Approx(3758162710897404343. / 13730453611875000000.));
+    REQUIRE(integrate(p15, l14) != Catch::Approx(49733943385709654587. / 137304536118750000000.));
+
+    // In a weird way, one gets almost 16th order on this test
+    REQUIRE(get_order(p15, l14, 49733943385709654587. / 137304536118750000000.) == Catch::Approx(16).margin(0.01));
+  }
+
+  SECTION("degree 15")
+  {
+    const QuadratureFormula<3>& l15 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(15));
+
+    REQUIRE(l15.numberOfPoints() == 264);
+
+    REQUIRE(integrate(p0, l15) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l15) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l15) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l15) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l15) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l15) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l15) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l15) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l15) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l15) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l15) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l15) == Catch::Approx(-340332578501887. / 323069496750000));
+    REQUIRE(integrate(p12, l15) == Catch::Approx(-9990191769716047. / 16153474837500000));
+    REQUIRE(integrate(p13, l15) == Catch::Approx(-31229729533861. / 5384491612500000));
+    REQUIRE(integrate(p14, l15) == Catch::Approx(3758162710897404343. / 13730453611875000000.));
+    REQUIRE(integrate(p15, l15) == Catch::Approx(49733943385709654587. / 137304536118750000000.));
+    REQUIRE(integrate(p16, l15) != Catch::Approx(-7270030713465096260897. / 26087861862562500000000.));
+
+    REQUIRE(get_order(p16, l15, -7270030713465096260897. / 26087861862562500000000.) ==
+            Catch::Approx(16.5).margin(0.2));
+  }
+
+  SECTION("degree 16")
+  {
+    const QuadratureFormula<3>& l16 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(16));
+
+    REQUIRE(l16.numberOfPoints() == 304);
+
+    REQUIRE(integrate(p0, l16) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l16) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l16) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l16) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l16) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l16) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l16) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l16) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l16) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l16) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l16) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l16) == Catch::Approx(-340332578501887. / 323069496750000));
+    REQUIRE(integrate(p12, l16) == Catch::Approx(-9990191769716047. / 16153474837500000));
+    REQUIRE(integrate(p13, l16) == Catch::Approx(-31229729533861. / 5384491612500000));
+    REQUIRE(integrate(p14, l16) == Catch::Approx(3758162710897404343. / 13730453611875000000.));
+    REQUIRE(integrate(p15, l16) == Catch::Approx(49733943385709654587. / 137304536118750000000.));
+    REQUIRE(integrate(p16, l16) == Catch::Approx(-7270030713465096260897. / 26087861862562500000000.));
+    REQUIRE(integrate(p17, l16) != Catch::Approx(34859214238288098805889. / 195658963969218750000000.));
+
+    // In a weird way, one gets almost 18th order on this test
+    REQUIRE(get_order(p17, l16, 34859214238288098805889. / 195658963969218750000000.) == Catch::Approx(18).margin(0.1));
+  }
+
+  SECTION("degree 17")
+  {
+    const QuadratureFormula<3>& l17 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(17));
+
+    REQUIRE(l17.numberOfPoints() == 364);
+
+    REQUIRE(integrate(p0, l17) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l17) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l17) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l17) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l17) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l17) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l17) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l17) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l17) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l17) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l17) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l17) == Catch::Approx(-340332578501887. / 323069496750000));
+    REQUIRE(integrate(p12, l17) == Catch::Approx(-9990191769716047. / 16153474837500000));
+    REQUIRE(integrate(p13, l17) == Catch::Approx(-31229729533861. / 5384491612500000));
+    REQUIRE(integrate(p14, l17) == Catch::Approx(3758162710897404343. / 13730453611875000000.));
+    REQUIRE(integrate(p15, l17) == Catch::Approx(49733943385709654587. / 137304536118750000000.));
+    REQUIRE(integrate(p16, l17) == Catch::Approx(-7270030713465096260897. / 26087861862562500000000.));
+    REQUIRE(integrate(p17, l17) == Catch::Approx(34859214238288098805889. / 195658963969218750000000.));
+    REQUIRE(integrate(p18, l17) != Catch::Approx(115510477527288033058991. / 13696127477845312500000000.));
+
+    REQUIRE(get_order(p18, l17, 115510477527288033058991. / 13696127477845312500000000.) ==
+            Catch::Approx(18).margin(0.2));
+  }
+
+  SECTION("degree 18")
+  {
+    const QuadratureFormula<3>& l18 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(18));
+
+    REQUIRE(l18.numberOfPoints() == 436);
+
+    REQUIRE(integrate(p0, l18) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l18) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l18) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l18) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l18) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l18) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l18) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l18) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l18) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l18) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l18) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l18) == Catch::Approx(-340332578501887. / 323069496750000));
+    REQUIRE(integrate(p12, l18) == Catch::Approx(-9990191769716047. / 16153474837500000));
+    REQUIRE(integrate(p13, l18) == Catch::Approx(-31229729533861. / 5384491612500000));
+    REQUIRE(integrate(p14, l18) == Catch::Approx(3758162710897404343. / 13730453611875000000.));
+    REQUIRE(integrate(p15, l18) == Catch::Approx(49733943385709654587. / 137304536118750000000.));
+    REQUIRE(integrate(p16, l18) == Catch::Approx(-7270030713465096260897. / 26087861862562500000000.));
+    REQUIRE(integrate(p17, l18) == Catch::Approx(34859214238288098805889. / 195658963969218750000000.));
+    REQUIRE(integrate(p18, l18) == Catch::Approx(115510477527288033058991. / 13696127477845312500000000.));
+    REQUIRE(integrate(p19, l18) !=
+            Catch::Approx(2328326230028547427138903. / 57945154713960937500000000.).epsilon(1E-10));
+
+    // In a weird way, one gets almost 20th order on this test
+    REQUIRE(get_order(p19, l18, 2328326230028547427138903. / 57945154713960937500000000.) ==
+            Catch::Approx(20).margin(0.01));
+  }
+
+  SECTION("degree 19")
+  {
+    const QuadratureFormula<3>& l19 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(19));
+
+    REQUIRE(l19.numberOfPoints() == 487);
+
+    REQUIRE(integrate(p0, l19) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l19) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l19) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l19) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l19) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l19) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l19) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l19) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l19) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l19) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l19) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l19) == Catch::Approx(-340332578501887. / 323069496750000));
+    REQUIRE(integrate(p12, l19) == Catch::Approx(-9990191769716047. / 16153474837500000));
+    REQUIRE(integrate(p13, l19) == Catch::Approx(-31229729533861. / 5384491612500000));
+    REQUIRE(integrate(p14, l19) == Catch::Approx(3758162710897404343. / 13730453611875000000.));
+    REQUIRE(integrate(p15, l19) == Catch::Approx(49733943385709654587. / 137304536118750000000.));
+    REQUIRE(integrate(p16, l19) == Catch::Approx(-7270030713465096260897. / 26087861862562500000000.));
+    REQUIRE(integrate(p17, l19) == Catch::Approx(34859214238288098805889. / 195658963969218750000000.));
+    REQUIRE(integrate(p18, l19) == Catch::Approx(115510477527288033058991. / 13696127477845312500000000.));
+    REQUIRE(integrate(p19, l19) == Catch::Approx(2328326230028547427138903. / 57945154713960937500000000.));
+    REQUIRE(integrate(p20, l19) !=
+            Catch::Approx(-20113634155270986416106151. / 19250668066082578125000000000.).epsilon(1E-10));
+
+    // No order test. Too bad ~16 when one expects 20 asymptotically
+  }
+
+  SECTION("degree 20")
+  {
+    const QuadratureFormula<3>& l20 =
+      QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(20));
+
+    REQUIRE(l20.numberOfPoints() == 552);
+
+    REQUIRE(integrate(p0, l20) == Catch::Approx(16. / 3));
+    REQUIRE(integrate(p1, l20) == Catch::Approx(-16. / 3));
+    REQUIRE(integrate(p2, l20) == Catch::Approx(-47. / 3));
+    REQUIRE(integrate(p3, l20) == Catch::Approx(557. / 15));
+    REQUIRE(integrate(p4, l20) == Catch::Approx(4499. / 1575));
+    REQUIRE(integrate(p5, l20) == Catch::Approx(143443. / 7875));
+    REQUIRE(integrate(p6, l20) == Catch::Approx(-75773. / 2581974));
+    REQUIRE(integrate(p7, l20) == Catch::Approx(86951548. / 32274675));
+    REQUIRE(integrate(p8, l20) == Catch::Approx(-863556317. / 322746750));
+    REQUIRE(integrate(p9, l20) == Catch::Approx(1168568393. / 3227467500));
+    REQUIRE(integrate(p10, l20) == Catch::Approx(-473611706567. / 461527852500));
+    REQUIRE(integrate(p11, l20) == Catch::Approx(-340332578501887. / 323069496750000));
+    REQUIRE(integrate(p12, l20) == Catch::Approx(-9990191769716047. / 16153474837500000));
+    REQUIRE(integrate(p13, l20) == Catch::Approx(-31229729533861. / 5384491612500000));
+    REQUIRE(integrate(p14, l20) == Catch::Approx(3758162710897404343. / 13730453611875000000.));
+    REQUIRE(integrate(p15, l20) == Catch::Approx(49733943385709654587. / 137304536118750000000.));
+    REQUIRE(integrate(p16, l20) == Catch::Approx(-7270030713465096260897. / 26087861862562500000000.));
+    REQUIRE(integrate(p17, l20) == Catch::Approx(34859214238288098805889. / 195658963969218750000000.));
+    REQUIRE(integrate(p18, l20) == Catch::Approx(115510477527288033058991. / 13696127477845312500000000.));
+    REQUIRE(integrate(p19, l20) == Catch::Approx(2328326230028547427138903. / 57945154713960937500000000.));
+    REQUIRE(integrate(p20, l20) == Catch::Approx(-20113634155270986416106151. / 19250668066082578125000000000.));
+    REQUIRE(integrate(p21, l20) != Catch::Approx(16760093568330570728566871. / 7598947920822070312500000000.));
+
+    // In a weird way, one gets almost 22th order on this test
+    REQUIRE(get_order(p21, l20, 16760093568330570728566871. / 7598947920822070312500000000.) ==
+            Catch::Approx(22).margin(0.01));
+  }
+
+  SECTION("max implemented degree")
+  {
+    REQUIRE(QuadratureManager::instance().maxTetrahedronDegree(QuadratureType::Gauss) ==
+            TetrahedronGaussQuadrature::max_degree);
+  }
+}
diff --git a/tests/test_TetrahedronTransformation.cpp b/tests/test_TetrahedronTransformation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..91739b99e7030e1b8490869fb770f1978cce9334
--- /dev/null
+++ b/tests/test_TetrahedronTransformation.cpp
@@ -0,0 +1,61 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/TetrahedronTransformation.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("TetrahedronTransformation", "[geometry]")
+{
+  using R3 = TinyVector<3>;
+
+  const R3 a = {1, 2, 1};
+  const R3 b = {3, 1, 3};
+  const R3 c = {2, 5, 2};
+  const R3 d = {2, 3, 4};
+
+  const TetrahedronTransformation t(a, b, c, d);
+
+  REQUIRE(t({0, 0, 0})[0] == Catch::Approx(1));
+  REQUIRE(t({0, 0, 0})[1] == Catch::Approx(2));
+  REQUIRE(t({0, 0, 0})[2] == Catch::Approx(1));
+
+  REQUIRE(t({1, 0, 0})[0] == Catch::Approx(3));
+  REQUIRE(t({1, 0, 0})[1] == Catch::Approx(1));
+  REQUIRE(t({1, 0, 0})[2] == Catch::Approx(3));
+
+  REQUIRE(t({0, 1, 0})[0] == Catch::Approx(2));
+  REQUIRE(t({0, 1, 0})[1] == Catch::Approx(5));
+  REQUIRE(t({0, 1, 0})[2] == Catch::Approx(2));
+
+  REQUIRE(t({0, 0, 1})[0] == Catch::Approx(2));
+  REQUIRE(t({0, 0, 1})[1] == Catch::Approx(3));
+  REQUIRE(t({0, 0, 1})[2] == Catch::Approx(4));
+
+  REQUIRE(t({0.25, 0.25, 0.25})[0] == Catch::Approx(2));
+  REQUIRE(t({0.25, 0.25, 0.25})[1] == Catch::Approx(11. / 4));
+  REQUIRE(t({0.25, 0.25, 0.25})[2] == Catch::Approx(2.5));
+
+  REQUIRE(t.jacobianDeterminant() == Catch::Approx(14));
+
+  SECTION("Polynomial integral")
+  {
+    auto p = [](const R3& X) {
+      const double x = X[0];
+      const double y = X[1];
+      const double z = X[2];
+      return 2 * x * x + 3 * x * y + y * y + 3 * y - z * z + 2 * x * z + 2 * z;
+    };
+
+    QuadratureFormula<3> qf = QuadratureManager::instance().getTetrahedronFormula(GaussQuadratureDescriptor(2));
+
+    double sum = 0;
+    for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+      sum += qf.weight(i) * t.jacobianDeterminant() * p(t(qf.point(i)));
+    }
+
+    REQUIRE(sum == Catch::Approx(231. / 2));
+  }
+}
diff --git a/tests/test_TriangleGaussQuadrature.cpp b/tests/test_TriangleGaussQuadrature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3d7b5dffaf2f0290d5b15dcb6796188512f62cf8
--- /dev/null
+++ b/tests/test_TriangleGaussQuadrature.cpp
@@ -0,0 +1,628 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <analysis/TriangleGaussQuadrature.hpp>
+#include <geometry/TriangleTransformation.hpp>
+#include <utils/Exceptions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("TriangleGaussQuadrature", "[analysis]")
+{
+  auto integrate = [](auto f, auto quadrature_formula) {
+    using R2 = TinyVector<2>;
+
+    const R2 A{-1, -1};
+    const R2 B{+1, -1};
+    const R2 C{-1, +1};
+
+    TriangleTransformation<2> t{A, B, C};
+
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    auto value = weight_list[0] * f(t(point_list[0]));
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(t(point_list[i]));
+    }
+
+    return t.jacobianDeterminant() * value;
+  };
+
+  auto integrate_on_triangle = [](auto f, auto quadrature_formula, const TinyVector<2>& a, const TinyVector<2>& b,
+                                  const TinyVector<2>& c) {
+    TriangleTransformation<2> t(a, b, c);
+
+    auto point_list  = quadrature_formula.pointList();
+    auto weight_list = quadrature_formula.weightList();
+
+    auto value = weight_list[0] * f(t(point_list[0]));
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * f(t(point_list[i]));
+    }
+
+    return t.jacobianDeterminant() * value;
+  };
+
+  auto get_order = [&integrate, &integrate_on_triangle](auto f, auto quadrature_formula, const double exact_value) {
+    using R2               = TinyVector<2>;
+    const double int_T_hat = integrate(f, quadrature_formula);
+    const double int_refined   //
+      = integrate_on_triangle(f, quadrature_formula, R2{-1, -1}, R2{0, -1}, R2{-1, 0}) +
+        integrate_on_triangle(f, quadrature_formula, R2{0, -1}, R2{0, 0}, R2{-1, 0}) +
+        integrate_on_triangle(f, quadrature_formula, R2{0, -1}, R2{1, -1}, R2{0, 0}) +
+        integrate_on_triangle(f, quadrature_formula, R2{-1, 0}, R2{0, 0}, R2{-1, 1});
+
+    return -std::log(std::abs(int_refined - exact_value) / std::abs(int_T_hat - exact_value)) / std::log(2);
+  };
+
+  auto p0 = [](const TinyVector<2>&) { return 2; };
+  auto p1 = [](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return 2 * x + 3 * y + 1;
+  };
+  auto p2 = [&p1](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p1(X) * (2.5 * x - 3 * y + 3);
+  };
+  auto p3 = [&p2](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p2(X) * (-1.5 * x + 3 * y - 3);
+  };
+  auto p4 = [&p3](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p3(X) * (x + y + 1);
+  };
+  auto p5 = [&p4](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p4(X) * (-0.2 * x - 1.3 * y - 0.7);
+  };
+  auto p6 = [&p5](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p5(X) * (3 * x - 2 * y + 3);
+  };
+  auto p7 = [&p6](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p6(X) * (-2 * x + 4 * y - 7);
+  };
+  auto p8 = [&p7](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p7(X) * (0.2 * x - 0.3 * y - 0.3);
+  };
+  auto p9 = [&p8](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p8(X) * (0.9 * x + 0.6 * y - 0.4);
+  };
+  auto p10 = [&p9](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p9(X) * (-0.1 * x + 0.3 * y + 0.6);
+  };
+  auto p11 = [&p10](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p10(X) * (0.7 * x - 0.6 * y + 0.2);
+  };
+  auto p12 = [&p11](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p11(X) * (0.2 * x + 0.3 * y - 0.4);
+  };
+  auto p13 = [&p12](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p12(X) * (-0.9 * x + 0.4 * y + 0.3);
+  };
+  auto p14 = [&p13](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p13(X) * (0.7 * x - 0.8 * y - 0.9);
+  };
+  auto p15 = [&p14](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p14(X) * (-0.9 * x + 0.4 * y + 0.2);
+  };
+  auto p16 = [&p15](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p15(X) * (-1.3 * x - 1.2 * y - 0.9);
+  };
+  auto p17 = [&p16](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p16(X) * (0.7 * x + 0.6 * y - 0.2);
+  };
+  auto p18 = [&p17](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p17(X) * (-0.2 * x + 0.5 * y + 0.7);
+  };
+  auto p19 = [&p18](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p18(X) * (0.4 * x - 0.7 * y - 0.3);
+  };
+  auto p20 = [&p19](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p19(X) * (-0.1 * x + 0.8 * y - 0.7);
+  };
+  auto p21 = [&p20](const TinyVector<2>& X) {
+    const double x = X[0];
+    const double y = X[1];
+    return p20(X) * (0.5 * x - 0.2 * y + 0.3);
+  };
+
+  SECTION("degree 1")
+  {
+    const QuadratureFormula<2>& l1 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(1));
+
+    REQUIRE(l1.numberOfPoints() == 1);
+
+    REQUIRE(integrate(p0, l1) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l1) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l1) != Catch::Approx(-19. / 3));
+
+    REQUIRE(get_order(p2, l1, -19. / 3) == Catch::Approx(2));
+  }
+
+  SECTION("degree 2")
+  {
+    const QuadratureFormula<2>& l2 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(2));
+
+    REQUIRE(l2.numberOfPoints() == 3);
+
+    REQUIRE(integrate(p0, l2) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l2) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l2) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l2) != Catch::Approx(146. / 5));
+
+    // In a weird way, the formula achieves 4th order on this test
+    REQUIRE(get_order(p3, l2, 146. / 5) == Catch::Approx(4));
+  }
+
+  SECTION("degree 3 and 4")
+  {
+    const QuadratureFormula<2>& l3 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(3));
+    const QuadratureFormula<2>& l4 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(4));
+
+    bool is_same = true;
+    is_same &= (l3.numberOfPoints() == l4.numberOfPoints());
+    for (size_t i = 0; i < l3.pointList().size(); ++i) {
+      is_same &= (l3.point(i) == l3.point(i));
+      is_same &= (l3.weight(i) == l3.weight(i));
+    }
+
+    REQUIRE(is_same);
+
+    REQUIRE(l4.numberOfPoints() == 6);
+
+    REQUIRE(integrate(p0, l4) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l4) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l4) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l4) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l4) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l4) != Catch::Approx(-17. / 10));
+
+    // In a weird way, the formula achieves 6th order on this test
+    REQUIRE(get_order(p5, l4, -17. / 10) == Catch::Approx(6));
+  }
+
+  SECTION("degree 5")
+  {
+    const QuadratureFormula<2>& l5 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(5));
+
+    REQUIRE(l5.numberOfPoints() == 7);
+
+    REQUIRE(integrate(p0, l5) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l5) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l5) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l5) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l5) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l5) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l5) != Catch::Approx(-197. / 175));
+
+    REQUIRE(get_order(p6, l5, -197. / 175) == Catch::Approx(6));
+  }
+
+  SECTION("degree 6")
+  {
+    const QuadratureFormula<2>& l6 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(6));
+
+    REQUIRE(l6.numberOfPoints() == 12);
+
+    REQUIRE(integrate(p0, l6) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l6) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l6) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l6) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l6) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l6) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l6) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l6) != Catch::Approx(-4507. / 1575));
+
+    REQUIRE(get_order(p7, l6, -4507. / 1575) == Catch::Approx(8));
+  }
+
+  SECTION("degree 7")
+  {
+    const QuadratureFormula<2>& l7 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(7));
+
+    REQUIRE(l7.numberOfPoints() == 15);
+
+    REQUIRE(integrate(p0, l7) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l7) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l7) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l7) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l7) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l7) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l7) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l7) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l7) != Catch::Approx(-23867. / 1575));
+
+    REQUIRE(get_order(p8, l7, -23867. / 1575) == Catch::Approx(8));
+  }
+
+  SECTION("degree 8")
+  {
+    const QuadratureFormula<2>& l8 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(8));
+
+    REQUIRE(l8.numberOfPoints() == 16);
+
+    REQUIRE(integrate(p0, l8) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l8) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l8) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l8) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l8) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l8) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l8) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l8) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l8) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l8) != Catch::Approx(7782251. / 346500));
+
+    // In a weird way, the formula achieves 10th order on this test
+    REQUIRE(get_order(p9, l8, 7782251. / 346500) == Catch::Approx(10));
+  }
+
+  SECTION("degree 9")
+  {
+    const QuadratureFormula<2>& l9 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(9));
+
+    REQUIRE(l9.numberOfPoints() == 19);
+
+    REQUIRE(integrate(p0, l9) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l9) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l9) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l9) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l9) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l9) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l9) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l9) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l9) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l9) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l9) != Catch::Approx(126885809. / 14437500));
+
+    REQUIRE(get_order(p10, l9, 126885809. / 14437500) == Catch::Approx(10));
+  }
+
+  SECTION("degree 10")
+  {
+    const QuadratureFormula<2>& l10 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(10));
+
+    REQUIRE(l10.numberOfPoints() == 25);
+
+    REQUIRE(integrate(p0, l10) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l10) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l10) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l10) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l10) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l10) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l10) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l10) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l10) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l10) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l10) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l10) != Catch::Approx(22133453663. / 11261250000));
+
+    // In a weird way, the formula achieves 12th order on this test
+    REQUIRE(get_order(p11, l10, 22133453663. / 11261250000) == Catch::Approx(12));
+  }
+
+  SECTION("degree 11")
+  {
+    const QuadratureFormula<2>& l11 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(11));
+
+    REQUIRE(l11.numberOfPoints() == 28);
+
+    REQUIRE(integrate(p0, l11) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l11) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l11) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l11) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l11) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l11) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l11) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l11) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l11) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l11) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l11) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l11) == Catch::Approx(22133453663. / 11261250000));
+    REQUIRE(integrate(p12, l11) != Catch::Approx(-419453736959. / 262762500000));
+
+    REQUIRE(get_order(p12, l11, -419453736959. / 262762500000) == Catch::Approx(12));
+  }
+
+  SECTION("degree 12")
+  {
+    const QuadratureFormula<2>& l12 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(12));
+
+    REQUIRE(l12.numberOfPoints() == 33);
+
+    REQUIRE(integrate(p0, l12) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l12) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l12) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l12) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l12) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l12) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l12) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l12) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l12) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l12) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l12) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l12) == Catch::Approx(22133453663. / 11261250000));
+    REQUIRE(integrate(p12, l12) == Catch::Approx(-419453736959. / 262762500000));
+    REQUIRE(integrate(p13, l12) != Catch::Approx(-3625092349117. / 7882875000000));
+
+    // In a weird way, the formula achieves 14th order on this test
+    REQUIRE(get_order(p13, l12, -3625092349117. / 7882875000000) == Catch::Approx(14));
+  }
+
+  SECTION("degree 13")
+  {
+    const QuadratureFormula<2>& l13 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(13));
+
+    REQUIRE(l13.numberOfPoints() == 37);
+
+    REQUIRE(integrate(p0, l13) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l13) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l13) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l13) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l13) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l13) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l13) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l13) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l13) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l13) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l13) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l13) == Catch::Approx(22133453663. / 11261250000));
+    REQUIRE(integrate(p12, l13) == Catch::Approx(-419453736959. / 262762500000));
+    REQUIRE(integrate(p13, l13) == Catch::Approx(-3625092349117. / 7882875000000));
+    REQUIRE(integrate(p14, l13) != Catch::Approx(541632196607. / 1642265625000));
+
+    REQUIRE(get_order(p14, l13, 541632196607. / 1642265625000) == Catch::Approx(14));
+  }
+
+  SECTION("degree 14")
+  {
+    const QuadratureFormula<2>& l14 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(14));
+
+    REQUIRE(l14.numberOfPoints() == 42);
+
+    REQUIRE(integrate(p0, l14) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l14) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l14) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l14) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l14) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l14) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l14) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l14) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l14) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l14) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l14) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l14) == Catch::Approx(22133453663. / 11261250000));
+    REQUIRE(integrate(p12, l14) == Catch::Approx(-419453736959. / 262762500000));
+    REQUIRE(integrate(p13, l14) == Catch::Approx(-3625092349117. / 7882875000000));
+    REQUIRE(integrate(p14, l14) == Catch::Approx(541632196607. / 1642265625000));
+    REQUIRE(integrate(p15, l14) != Catch::Approx(-287809833862769. / 3350221875000000));
+
+    // In a weird way, the formula achieves 16th order on this test
+    REQUIRE(get_order(p15, l14, -287809833862769. / 3350221875000000) == Catch::Approx(16));
+  }
+
+  SECTION("degree 15")
+  {
+    const QuadratureFormula<2>& l15 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(15));
+
+    REQUIRE(l15.numberOfPoints() == 49);
+
+    REQUIRE(integrate(p0, l15) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l15) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l15) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l15) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l15) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l15) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l15) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l15) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l15) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l15) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l15) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l15) == Catch::Approx(22133453663. / 11261250000));
+    REQUIRE(integrate(p12, l15) == Catch::Approx(-419453736959. / 262762500000));
+    REQUIRE(integrate(p13, l15) == Catch::Approx(-3625092349117. / 7882875000000));
+    REQUIRE(integrate(p14, l15) == Catch::Approx(541632196607. / 1642265625000));
+    REQUIRE(integrate(p15, l15) == Catch::Approx(-287809833862769. / 3350221875000000));
+    REQUIRE(integrate(p16, l15) != Catch::Approx(3340311405172793. / 6700443750000000).epsilon(1E-10));
+
+    REQUIRE(get_order(p16, l15, 3340311405172793. / 6700443750000000) == Catch::Approx(16));
+  }
+
+  SECTION("degree 16")
+  {
+    const QuadratureFormula<2>& l16 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(16));
+
+    REQUIRE(l16.numberOfPoints() == 55);
+
+    REQUIRE(integrate(p0, l16) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l16) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l16) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l16) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l16) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l16) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l16) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l16) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l16) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l16) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l16) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l16) == Catch::Approx(22133453663. / 11261250000));
+    REQUIRE(integrate(p12, l16) == Catch::Approx(-419453736959. / 262762500000));
+    REQUIRE(integrate(p13, l16) == Catch::Approx(-3625092349117. / 7882875000000));
+    REQUIRE(integrate(p14, l16) == Catch::Approx(541632196607. / 1642265625000));
+    REQUIRE(integrate(p15, l16) == Catch::Approx(-287809833862769. / 3350221875000000));
+    REQUIRE(integrate(p16, l16) == Catch::Approx(3340311405172793. / 6700443750000000));
+    REQUIRE(integrate(p17, l16) != Catch::Approx(-8386984372282772827. / 19096264687500000000.).epsilon(1E-10));
+
+    // In a weird way, the formula achieves ~18th order on this test
+    REQUIRE(get_order(p17, l16, -8386984372282772827. / 19096264687500000000.) == Catch::Approx(18).margin(1E-2));
+  }
+
+  SECTION("degree 17")
+  {
+    const QuadratureFormula<2>& l17 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(17));
+
+    REQUIRE(l17.numberOfPoints() == 60);
+
+    REQUIRE(integrate(p0, l17) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l17) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l17) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l17) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l17) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l17) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l17) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l17) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l17) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l17) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l17) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l17) == Catch::Approx(22133453663. / 11261250000));
+    REQUIRE(integrate(p12, l17) == Catch::Approx(-419453736959. / 262762500000));
+    REQUIRE(integrate(p13, l17) == Catch::Approx(-3625092349117. / 7882875000000));
+    REQUIRE(integrate(p14, l17) == Catch::Approx(541632196607. / 1642265625000));
+    REQUIRE(integrate(p15, l17) == Catch::Approx(-287809833862769. / 3350221875000000));
+    REQUIRE(integrate(p16, l17) == Catch::Approx(3340311405172793. / 6700443750000000));
+    REQUIRE(integrate(p17, l17) == Catch::Approx(-8386984372282772827. / 19096264687500000000.));
+    REQUIRE(integrate(p18, l17) != Catch::Approx(-1599289493784003137. / 6820094531250000000.).epsilon(1E-10));
+
+    REQUIRE(get_order(p18, l17, -1599289493784003137. / 6820094531250000000.) == Catch::Approx(18).margin(1E-2));
+  }
+
+  SECTION("degree 18")
+  {
+    const QuadratureFormula<2>& l18 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(18));
+
+    REQUIRE(l18.numberOfPoints() == 67);
+
+    REQUIRE(integrate(p0, l18) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l18) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l18) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l18) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l18) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l18) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l18) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l18) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l18) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l18) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l18) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l18) == Catch::Approx(22133453663. / 11261250000));
+    REQUIRE(integrate(p12, l18) == Catch::Approx(-419453736959. / 262762500000));
+    REQUIRE(integrate(p13, l18) == Catch::Approx(-3625092349117. / 7882875000000));
+    REQUIRE(integrate(p14, l18) == Catch::Approx(541632196607. / 1642265625000));
+    REQUIRE(integrate(p15, l18) == Catch::Approx(-287809833862769. / 3350221875000000));
+    REQUIRE(integrate(p16, l18) == Catch::Approx(3340311405172793. / 6700443750000000));
+    REQUIRE(integrate(p17, l18) == Catch::Approx(-8386984372282772827. / 19096264687500000000.));
+    REQUIRE(integrate(p18, l18) == Catch::Approx(-1599289493784003137. / 6820094531250000000.));
+    REQUIRE(integrate(p19, l18) != Catch::Approx(5280879341958226453. / 47740661718750000000.).epsilon(1E-10));
+
+    // In a weird way, the formula achieves ~20th order on this test
+    REQUIRE(get_order(p19, l18, 5280879341958226453. / 47740661718750000000.) == Catch::Approx(20).margin(0.1));
+  }
+
+  SECTION("degree 19")
+  {
+    const QuadratureFormula<2>& l19 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(19));
+
+    REQUIRE(l19.numberOfPoints() == 73);
+
+    REQUIRE(integrate(p0, l19) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l19) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l19) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l19) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l19) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l19) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l19) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l19) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l19) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l19) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l19) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l19) == Catch::Approx(22133453663. / 11261250000));
+    REQUIRE(integrate(p12, l19) == Catch::Approx(-419453736959. / 262762500000));
+    REQUIRE(integrate(p13, l19) == Catch::Approx(-3625092349117. / 7882875000000));
+    REQUIRE(integrate(p14, l19) == Catch::Approx(541632196607. / 1642265625000));
+    REQUIRE(integrate(p15, l19) == Catch::Approx(-287809833862769. / 3350221875000000));
+    REQUIRE(integrate(p16, l19) == Catch::Approx(3340311405172793. / 6700443750000000));
+    REQUIRE(integrate(p17, l19) == Catch::Approx(-8386984372282772827. / 19096264687500000000.));
+    REQUIRE(integrate(p18, l19) == Catch::Approx(-1599289493784003137. / 6820094531250000000.));
+    REQUIRE(integrate(p19, l19) == Catch::Approx(5280879341958226453. / 47740661718750000000.));
+    REQUIRE(integrate(p20, l19) != Catch::Approx(1390615013183923183. / 85251181640625000000.).epsilon(1E-10));
+
+    REQUIRE(get_order(p20, l19, 1390615013183923183. / 85251181640625000000.) == Catch::Approx(20).margin(0.1));
+  }
+
+  SECTION("degree 20")
+  {
+    const QuadratureFormula<2>& l20 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(20));
+
+    REQUIRE(l20.numberOfPoints() == 79);
+
+    REQUIRE(integrate(p0, l20) == Catch::Approx(4));
+    REQUIRE(integrate(p1, l20) == Catch::Approx(-4. / 3));
+    REQUIRE(integrate(p2, l20) == Catch::Approx(-19. / 3));
+    REQUIRE(integrate(p3, l20) == Catch::Approx(146. / 5));
+    REQUIRE(integrate(p4, l20) == Catch::Approx(-25. / 6));
+    REQUIRE(integrate(p5, l20) == Catch::Approx(-17. / 10));
+    REQUIRE(integrate(p6, l20) == Catch::Approx(-197. / 175));
+    REQUIRE(integrate(p7, l20) == Catch::Approx(-4507. / 1575));
+    REQUIRE(integrate(p8, l20) == Catch::Approx(-23867. / 1575));
+    REQUIRE(integrate(p9, l20) == Catch::Approx(7782251. / 346500));
+    REQUIRE(integrate(p10, l20) == Catch::Approx(126885809. / 14437500));
+    REQUIRE(integrate(p11, l20) == Catch::Approx(22133453663. / 11261250000));
+    REQUIRE(integrate(p12, l20) == Catch::Approx(-419453736959. / 262762500000));
+    REQUIRE(integrate(p13, l20) == Catch::Approx(-3625092349117. / 7882875000000));
+    REQUIRE(integrate(p14, l20) == Catch::Approx(541632196607. / 1642265625000));
+    REQUIRE(integrate(p15, l20) == Catch::Approx(-287809833862769. / 3350221875000000));
+    REQUIRE(integrate(p16, l20) == Catch::Approx(3340311405172793. / 6700443750000000));
+    REQUIRE(integrate(p17, l20) == Catch::Approx(-8386984372282772827. / 19096264687500000000.));
+    REQUIRE(integrate(p18, l20) == Catch::Approx(-1599289493784003137. / 6820094531250000000.));
+    REQUIRE(integrate(p19, l20) == Catch::Approx(5280879341958226453. / 47740661718750000000.));
+    REQUIRE(integrate(p20, l20) == Catch::Approx(1390615013183923183. / 85251181640625000000.));
+    REQUIRE(integrate(p21, l20) != Catch::Approx(-520205676970121316953. / 215685489550781250000000.).epsilon(1E-10));
+
+    // In a weird way, the formula achieves ~22th order on this test
+    REQUIRE(get_order(p21, l20, -520205676970121316953. / 215685489550781250000000.) == Catch::Approx(22).margin(0.5));
+  }
+
+  SECTION("max implemented degree")
+  {
+    REQUIRE(QuadratureManager::instance().maxTriangleDegree(QuadratureType::Gauss) ==
+            TriangleGaussQuadrature::max_degree);
+  }
+}
diff --git a/tests/test_TriangleTransformation.cpp b/tests/test_TriangleTransformation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e9561e859d727ea6c4019eaa78de125fbab8ba00
--- /dev/null
+++ b/tests/test_TriangleTransformation.cpp
@@ -0,0 +1,141 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/TriangleTransformation.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("TriangleTransformation", "[geometry]")
+{
+  SECTION("2D")
+  {
+    using R2 = TinyVector<2>;
+
+    const R2 a = {1, 2};
+    const R2 b = {3, 1};
+    const R2 c = {2, 5};
+
+    const TriangleTransformation<2> t(a, b, c);
+
+    REQUIRE(t({0, 0})[0] == Catch::Approx(1));
+    REQUIRE(t({0, 0})[1] == Catch::Approx(2));
+
+    REQUIRE(t({1, 0})[0] == Catch::Approx(3));
+    REQUIRE(t({1, 0})[1] == Catch::Approx(1));
+
+    REQUIRE(t({0, 1})[0] == Catch::Approx(2));
+    REQUIRE(t({0, 1})[1] == Catch::Approx(5));
+
+    REQUIRE(t({1. / 3, 1. / 3})[0] == Catch::Approx(2));
+    REQUIRE(t({1. / 3, 1. / 3})[1] == Catch::Approx(8. / 3));
+
+    REQUIRE(t.jacobianDeterminant() == Catch::Approx(7));
+
+    SECTION("Polynomial integral")
+    {
+      auto p = [](const R2& X) {
+        const double x = X[0];
+        const double y = X[1];
+        return 2 * x * x + 3 * x * y + y * y + 3 * y + 1;
+      };
+
+      QuadratureFormula<2> qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(2));
+
+      double sum = 0;
+      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+        sum += qf.weight(i) * t.jacobianDeterminant() * p(t(qf.point(i)));
+      }
+
+      REQUIRE(sum == Catch::Approx(3437. / 24));
+    }
+  }
+
+  SECTION("3D")
+  {
+    SECTION("Data")
+    {
+      using R3 = TinyVector<3>;
+
+      const R3 a = {1, 2, 2};
+      const R3 b = {4, 1, 3};
+      const R3 c = {2, 5, 1};
+
+      const TriangleTransformation<3> t(a, b, c);
+
+      REQUIRE(t({0, 0})[0] == Catch::Approx(1));
+      REQUIRE(t({0, 0})[1] == Catch::Approx(2));
+      REQUIRE(t({0, 0})[2] == Catch::Approx(2));
+
+      REQUIRE(t({1, 0})[0] == Catch::Approx(4));
+      REQUIRE(t({1, 0})[1] == Catch::Approx(1));
+      REQUIRE(t({1, 0})[2] == Catch::Approx(3));
+
+      REQUIRE(t({0, 1})[0] == Catch::Approx(2));
+      REQUIRE(t({0, 1})[1] == Catch::Approx(5));
+      REQUIRE(t({0, 1})[2] == Catch::Approx(1));
+
+      REQUIRE(t({1. / 3, 1. / 3})[0] == Catch::Approx(7. / 3));
+      REQUIRE(t({1. / 3, 1. / 3})[1] == Catch::Approx(8. / 3));
+      REQUIRE(t({1. / 3, 1. / 3})[2] == Catch::Approx(2));
+
+      REQUIRE(t.areaVariationNorm() == Catch::Approx(2 * std::sqrt(30)));
+    }
+
+    SECTION("Area modulus")
+    {
+      using R3 = TinyVector<3>;
+
+      const R3 a_hat = {0, 0, 0};
+      const R3 b_hat = {1, 0, 0};
+      const R3 c_hat = {0, 1, 0};
+
+      const double theta = 2.3;
+      TinyMatrix<3> r_theta(1, 0, 0,                               //
+                            0, std::cos(theta), std::sin(theta),   //
+                            0, -std::sin(theta), std::cos(theta));
+
+      const double phi = 0.7;
+      TinyMatrix<3> r_phi(std::cos(phi), std::sin(phi), 0,    //
+                          -std::sin(phi), std::cos(phi), 0,   //
+                          0, 0, 1);
+
+      const R3 a = r_phi * r_theta * a_hat;
+      const R3 b = r_phi * r_theta * b_hat;
+      const R3 c = r_phi * r_theta * c_hat;
+
+      const TriangleTransformation<3> t(a, b, c);
+
+      // The triangle (a,b,c) is just a rotation of the initial one
+      REQUIRE(t.areaVariationNorm() == Catch::Approx(1));
+    }
+
+    SECTION("Polynomial integral")
+    {
+      using R3 = TinyVector<3>;
+
+      const R3 a = {1, 2, 2};
+      const R3 b = {4, 1, 3};
+      const R3 c = {2, 5, 1};
+
+      const TriangleTransformation<3> t(a, b, c);
+
+      auto p = [](const R3& X) {
+        const double x = X[0];
+        const double y = X[1];
+        const double z = X[2];
+        return 2 * x * x + 3 * x - 3 * y * y + y + 2 * z * z - 0.5 * z + 2;
+      };
+
+      QuadratureFormula<2> qf = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(2));
+
+      double sum = 0;
+      for (size_t i = 0; i < qf.numberOfPoints(); ++i) {
+        sum += qf.weight(i) * t.areaVariationNorm() * p(t(qf.point(i)));
+      }
+
+      REQUIRE(sum == Catch::Approx(39.2534499545369));
+    }
+  }
+}
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 9c03ba9213ece8947688036ca7d5554c8e38a279..fa207c91b9b743ffa1986c20a3cdc6e1622c0a1a 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -2,6 +2,7 @@
 
 #include <Kokkos_Core.hpp>
 
+#include <analysis/QuadratureManager.hpp>
 #include <language/utils/OperatorRepository.hpp>
 #include <mesh/DiamondDualConnectivityManager.hpp>
 #include <mesh/DiamondDualMeshManager.hpp>
@@ -36,6 +37,7 @@ main(int argc, char* argv[])
 
       SynchronizerManager::create();
       RandomEngine::create();
+      QuadratureManager::create();
       MeshDataManager::create();
       DiamondDualConnectivityManager::create();
       DiamondDualMeshManager::create();
@@ -53,6 +55,7 @@ main(int argc, char* argv[])
       DiamondDualMeshManager::destroy();
       DiamondDualConnectivityManager::destroy();
       MeshDataManager::destroy();
+      QuadratureManager::destroy();
       RandomEngine::destroy();
       SynchronizerManager::destroy();
     }