diff --git a/src/scheme/FaceIntegrator.hpp b/src/scheme/FaceIntegrator.hpp
index 30ee39077ea12d898b4bdee00f12970be79e8753..657ca7a88a3a959f2856bc34ae8938ab91561785 100644
--- a/src/scheme/FaceIntegrator.hpp
+++ b/src/scheme/FaceIntegrator.hpp
@@ -144,9 +144,11 @@ class FaceIntegrator
           }
           break;
         }
+          // LCOV_EXCL_START
         default: {
           invalid_face = std::make_pair(true, face_id);
         }
+          // LCOV_EXCL_STOP
         }
       });
 
@@ -159,7 +161,9 @@ class FaceIntegrator
       // LCOV_EXCL_STOP
 
     } else {
+      // LCOV_EXCL_START
       throw UnexpectedError("invalid dimension for face integration");
+      // LCOV_EXCL_STOP
     }
   }
 
diff --git a/tests/test_FaceIntegrator.cpp b/tests/test_FaceIntegrator.cpp
index 184f1a8f97db137f99f638f5feef048f4d78ec04..945d765a6d0a530c7c7babb5d8b5800d5af033d3 100644
--- a/tests/test_FaceIntegrator.cpp
+++ b/tests/test_FaceIntegrator.cpp
@@ -15,487 +15,1477 @@
 
 TEST_CASE("FaceIntegrator", "[scheme]")
 {
-  SECTION("2D")
+  SECTION("scalar")
   {
-    using R2 = TinyVector<2>;
+    SECTION("2D")
+    {
+      using R2 = TinyVector<2>;
 
-    const auto mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+      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;
-    };
+      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();
+      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));
+        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));
-          }
+            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;
-        });
+            int_f[face_id] = integral;
+          });
 
-      return int_f;
-    }();
+        return int_f;
+      }();
 
-    SECTION("direct formula")
-    {
-      SECTION("all faces")
+      SECTION("direct formula")
       {
-        SECTION("FaceValue")
+        SECTION("all faces")
         {
-          FaceValue<double> values(mesh->connectivity());
-          FaceIntegrator::integrateTo([=](const R2 x) { return f(x); }, GaussQuadratureDescriptor(2), *mesh, values);
+          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]);
+            }
 
-          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));
           }
 
-          REQUIRE(error == Catch::Approx(0).margin(1E-10));
-        }
+          SECTION("Array")
+          {
+            Array<double> values(mesh->numberOfFaces());
 
-        SECTION("Array")
-        {
-          Array<double> values(mesh->numberOfFaces());
+            FaceIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values);
 
-          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]);
+            }
 
-          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));
           }
 
-          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("SmallArray")
+        SECTION("face list")
         {
-          SmallArray<double> values(mesh->numberOfFaces());
-          FaceIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values);
+          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 (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
-            error += std::abs(int_f_per_face[face_id] - values[face_id]);
+            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));
           }
 
-          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("face list")
+      SECTION("tensorial formula")
       {
-        SECTION("Array")
+        SECTION("all faces")
         {
-          Array<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
-
+          SECTION("FaceValue")
           {
-            size_t k = 0;
-            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
-              face_list[k] = face_id;
+            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(k == face_list.size());
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
           }
 
-          Array<double> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, face_list);
+          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]);
+            }
 
-          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));
           }
 
-          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("SmallArray")
+        SECTION("face list")
         {
-          SmallArray<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+          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")
           {
-            size_t k = 0;
-            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
-              face_list[k] = face_id;
+            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(k == face_list.size());
+            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", DualMeshManager::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);
 
-          SmallArray<double> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, face_list);
+                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]);
+                }
 
-          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("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));
+              }
+            }
           }
 
-          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));
+              }
+            }
+          }
         }
       }
     }
+  }
 
-    SECTION("tensorial formula")
+  SECTION("R^d")
+  {
+    SECTION("2D")
     {
-      SECTION("all faces")
+      using R2 = TinyVector<2>;
+
+      const auto mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+
+      auto f = [](const R2& X) -> R2 {
+        const double x = X[0];
+        const double y = X[1];
+        return R2{x * x + 2 * x * y + 3 * y * y + 2, 3 * x - 2 * y};
+      };
+
+      Array<const R2> int_f_per_face = [=] {
+        Array<R2> 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();
+            R2 integral         = zero;
+            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("FaceValue")
+        SECTION("all faces")
         {
-          FaceValue<double> values(mesh->connectivity());
-          FaceIntegrator::integrateTo([=](const R2 x) { return f(x); }, GaussLobattoQuadratureDescriptor(2), *mesh,
-                                      values);
+          SECTION("FaceValue")
+          {
+            FaceValue<R2> 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]);
+            double error = 0;
+            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+              error += l2Norm(int_f_per_face[face_id] - values[face_id]);
+            }
+
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
           }
 
-          REQUIRE(error == Catch::Approx(0).margin(1E-10));
-        }
+          SECTION("Array")
+          {
+            Array<R2> values(mesh->numberOfFaces());
 
-        SECTION("Array")
-        {
-          Array<double> values(mesh->numberOfFaces());
+            FaceIntegrator::integrateTo(f, GaussQuadratureDescriptor(2), *mesh, values);
 
-          FaceIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values);
+            double error = 0;
+            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+              error += l2Norm(int_f_per_face[face_id] - values[face_id]);
+            }
 
-          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));
           }
 
-          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          SECTION("SmallArray")
+          {
+            SmallArray<R2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+            }
+
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
         }
 
-        SECTION("SmallArray")
+        SECTION("face list")
         {
-          SmallArray<double> values(mesh->numberOfFaces());
-          FaceIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values);
+          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<R2> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, face_list);
+
+            double error = 0;
+            for (size_t i = 0; i < face_list.size(); ++i) {
+              error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+            }
 
-          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));
           }
 
-          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<R2> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, face_list);
+
+            double error = 0;
+            for (size_t i = 0; i < face_list.size(); ++i) {
+              error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+            }
+
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
         }
       }
 
-      SECTION("face list")
+      SECTION("tensorial formula")
       {
-        SECTION("Array")
+        SECTION("all faces")
         {
-          Array<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
-
+          SECTION("FaceValue")
           {
-            size_t k = 0;
-            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
-              face_list[k] = face_id;
+            FaceValue<R2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
             }
 
-            REQUIRE(k == face_list.size());
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
           }
 
-          Array<double> values = FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, face_list);
+          SECTION("Array")
+          {
+            Array<R2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+            }
 
-          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));
           }
 
-          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          SECTION("SmallArray")
+          {
+            SmallArray<R2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+            }
+
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
         }
 
-        SECTION("SmallArray")
+        SECTION("face list")
         {
-          SmallArray<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+          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<R2> values = FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, face_list);
+
+            double error = 0;
+            for (size_t i = 0; i < face_list.size(); ++i) {
+              error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+            }
+
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
+
+          SECTION("SmallArray")
           {
-            size_t k = 0;
-            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
-              face_list[k] = face_id;
+            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<R2> values = FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, face_list);
+
+            double error = 0;
+            for (size_t i = 0; i < face_list.size(); ++i) {
+              error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
             }
 
-            REQUIRE(k == face_list.size());
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
           }
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      using R2 = TinyVector<2>;
+      using R3 = TinyVector<3>;
+
+      auto hybrid_mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+
+      auto f = [](const R3& X) -> R2 {
+        const double x = X[0];
+        const double y = X[1];
+        const double z = X[2];
+        return R2{x * x + 2 * x * y + 3 * y * y + 2 * z * z - z + 1, 3 * x - 2 * y + z};
+      };
+
+      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", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
+
+      for (auto mesh_info : mesh_list) {
+        auto mesh_name = mesh_info.first;
+        auto mesh      = mesh_info.second;
+
+        Array<const R2> int_f_per_face = [=] {
+          Array<R2> 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();
+              R2 integral         = zero;
+
+              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;
+            });
 
-          SmallArray<double> values =
-            FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, face_list);
+          return int_f;
+        }();
+
+        SECTION(mesh_name)
+        {
+          SECTION("direct formula")
+          {
+            SECTION("all faces")
+            {
+              SECTION("FaceValue")
+              {
+                FaceValue<R2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              }
+
+              SECTION("Array")
+              {
+                Array<R2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              }
 
-          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]);
+              SECTION("SmallArray")
+              {
+                SmallArray<R2> 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 += l2Norm(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<R2> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, face_list);
+
+                double error = 0;
+                for (size_t i = 0; i < face_list.size(); ++i) {
+                  error += l2Norm(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<R2> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, face_list);
+
+                double error = 0;
+                for (size_t i = 0; i < face_list.size(); ++i) {
+                  error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              }
+            }
           }
 
-          REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          SECTION("tensorial formula")
+          {
+            SECTION("all faces")
+            {
+              SECTION("FaceValue")
+              {
+                FaceValue<R2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              }
+
+              SECTION("Array")
+              {
+                Array<R2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              }
+
+              SECTION("SmallArray")
+              {
+                SmallArray<R2> 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 += l2Norm(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<R2> values = FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, face_list);
+
+                double error = 0;
+                for (size_t i = 0; i < face_list.size(); ++i) {
+                  error += l2Norm(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<R2> values =
+                  FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, face_list);
+
+                double error = 0;
+                for (size_t i = 0; i < face_list.size(); ++i) {
+                  error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              }
+            }
+          }
         }
       }
     }
   }
 
-  SECTION("3D")
+  SECTION("R^dxd")
   {
-    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;
+    using R2x2  = TinyMatrix<2>;
+    auto l2Norm = [](const R2x2& A) -> double {
+      return std::sqrt(A(0, 0) * A(0, 0) + A(1, 0) * A(1, 0) + A(0, 1) * A(0, 1) + A(1, 1) * A(1, 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", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
+    SECTION("2D")
+    {
+      using R2 = TinyVector<2>;
+
+      const auto mesh = MeshDataBaseForTests::get().hybrid2DMesh();
 
-    for (auto mesh_info : mesh_list) {
-      auto mesh_name = mesh_info.first;
-      auto mesh      = mesh_info.second;
+      auto f = [](const R2& X) -> R2x2 {
+        const double x = X[0];
+        const double y = X[1];
+        return R2x2{x * x + 2 * x * y + 3 * y * y + 2, 3 * x - 2 * y, 2 * x * x - 2 * y, 2 + x * y};
+      };
 
-      Array<const double> int_f_per_face = [=] {
-        Array<double> int_f(mesh->numberOfFaces());
+      Array<const R2x2> int_f_per_face = [=] {
+        Array<R2x2> 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;
+            R2x2 integral       = zero;
+            LineTransformation<2> T(xr[face_node_list[0]], xr[face_node_list[1]]);
+            auto qf = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor(2));
 
-            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)");
-            }
+            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(mesh_name)
+      SECTION("direct formula")
       {
-        SECTION("direct formula")
+        SECTION("all faces")
         {
-          SECTION("all faces")
+          SECTION("FaceValue")
           {
-            SECTION("FaceValue")
-            {
-              FaceValue<double> values(mesh->connectivity());
-              FaceIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussQuadratureDescriptor(4), *mesh,
-                                          values);
+            FaceValue<R2x2> 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]);
-              }
+            double error = 0;
+            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+              error += l2Norm(int_f_per_face[face_id] - values[face_id]);
+            }
+
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
 
-              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          SECTION("Array")
+          {
+            Array<R2x2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
             }
 
-            SECTION("Array")
-            {
-              Array<double> values(mesh->numberOfFaces());
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
+
+          SECTION("SmallArray")
+          {
+            SmallArray<R2x2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+            }
+
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
+        }
 
-              FaceIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values);
+        SECTION("face list")
+        {
+          SECTION("Array")
+          {
+            Array<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
 
-              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]);
+            {
+              size_t k = 0;
+              for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
+                face_list[k] = face_id;
               }
 
-              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              REQUIRE(k == face_list.size());
             }
 
-            SECTION("SmallArray")
-            {
-              SmallArray<double> values(mesh->numberOfFaces());
-              FaceIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values);
+            Array<R2x2> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, face_list);
+
+            double error = 0;
+            for (size_t i = 0; i < face_list.size(); ++i) {
+              error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+            }
+
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
 
-              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]);
+          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(error == Catch::Approx(0).margin(1E-10));
+              REQUIRE(k == face_list.size());
+            }
+
+            SmallArray<R2x2> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(2), *mesh, face_list);
+
+            double error = 0;
+            for (size_t i = 0; i < face_list.size(); ++i) {
+              error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
             }
+
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
           }
+        }
+      }
 
-          SECTION("face list")
+      SECTION("tensorial formula")
+      {
+        SECTION("all faces")
+        {
+          SECTION("FaceValue")
           {
-            SECTION("Array")
-            {
-              Array<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+            FaceValue<R2x2> values(mesh->connectivity());
+            FaceIntegrator::integrateTo([=](const R2 x) { return f(x); }, GaussLobattoQuadratureDescriptor(2), *mesh,
+                                        values);
 
-              {
-                size_t k = 0;
-                for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
-                  face_list[k] = face_id;
-                }
+            double error = 0;
+            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+              error += l2Norm(int_f_per_face[face_id] - values[face_id]);
+            }
 
-                REQUIRE(k == face_list.size());
-              }
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
 
-              Array<double> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, face_list);
+          SECTION("Array")
+          {
+            Array<R2x2> values(mesh->numberOfFaces());
 
-              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]);
-              }
+            FaceIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(2), *mesh, values);
 
-              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+            double error = 0;
+            for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+              error += l2Norm(int_f_per_face[face_id] - values[face_id]);
             }
 
-            SECTION("SmallArray")
-            {
-              SmallArray<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
 
-              {
-                size_t k = 0;
-                for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
-                  face_list[k] = face_id;
-                }
+          SECTION("SmallArray")
+          {
+            SmallArray<R2x2> 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 += l2Norm(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};
 
-                REQUIRE(k == face_list.size());
+            {
+              size_t k = 0;
+              for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
+                face_list[k] = face_id;
               }
 
-              SmallArray<double> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, face_list);
+              REQUIRE(k == face_list.size());
+            }
+
+            Array<R2x2> values = FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, face_list);
+
+            double error = 0;
+            for (size_t i = 0; i < face_list.size(); ++i) {
+              error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+            }
+
+            REQUIRE(error == Catch::Approx(0).margin(1E-10));
+          }
 
-              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]);
+          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(error == Catch::Approx(0).margin(1E-10));
+              REQUIRE(k == face_list.size());
             }
+
+            SmallArray<R2x2> values =
+              FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(2), *mesh, face_list);
+
+            double error = 0;
+            for (size_t i = 0; i < face_list.size(); ++i) {
+              error += l2Norm(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) -> R2x2 {
+        const double x = X[0];
+        const double y = X[1];
+        const double z = X[2];
+        return R2x2{x * x + 2 * x * y + 3 * y * y + 2 * z * z - z + 1, 3 * x - 2 * y + z, 2 * x - y * z, 3 * z - x * x};
+      };
+
+      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", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
+
+      for (auto mesh_info : mesh_list) {
+        auto mesh_name = mesh_info.first;
+        auto mesh      = mesh_info.second;
+
+        Array<const R2x2> int_f_per_face = [=] {
+          Array<R2x2> 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();
+              R2x2 integral       = zero;
+
+              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("tensorial formula")
+        SECTION(mesh_name)
         {
-          SECTION("all faces")
+          SECTION("direct formula")
           {
-            SECTION("FaceValue")
+            SECTION("all faces")
             {
-              FaceValue<double> values(mesh->connectivity());
-              FaceIntegrator::integrateTo([=](const R3 x) { return f(x); }, GaussLegendreQuadratureDescriptor(10),
-                                          *mesh, values);
+              SECTION("FaceValue")
+              {
+                FaceValue<R2x2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+                }
 
-              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));
               }
 
-              REQUIRE(error == Catch::Approx(0).margin(1E-10));
-            }
+              SECTION("Array")
+              {
+                Array<R2x2> values(mesh->numberOfFaces());
 
-            SECTION("Array")
-            {
-              Array<double> values(mesh->numberOfFaces());
+                FaceIntegrator::integrateTo(f, GaussQuadratureDescriptor(4), *mesh, values);
 
-              FaceIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values);
+                double error = 0;
+                for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+                  error += l2Norm(int_f_per_face[face_id] - values[face_id]);
+                }
 
-              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));
               }
 
-              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              SECTION("SmallArray")
+              {
+                SmallArray<R2x2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              }
             }
 
-            SECTION("SmallArray")
+            SECTION("face list")
             {
-              SmallArray<double> values(mesh->numberOfFaces());
-              FaceIntegrator::integrateTo(f, GaussLobattoQuadratureDescriptor(4), *mesh, values);
+              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;
+                  }
 
-              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(k == face_list.size());
+                }
+
+                Array<R2x2> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, face_list);
+
+                double error = 0;
+                for (size_t i = 0; i < face_list.size(); ++i) {
+                  error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
               }
 
-              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<R2x2> values = FaceIntegrator::integrate(f, GaussQuadratureDescriptor(4), *mesh, face_list);
+
+                double error = 0;
+                for (size_t i = 0; i < face_list.size(); ++i) {
+                  error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              }
             }
           }
 
-          SECTION("face list")
+          SECTION("tensorial formula")
           {
-            SECTION("Array")
+            SECTION("all faces")
             {
-              Array<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
-
+              SECTION("FaceValue")
               {
-                size_t k = 0;
-                for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
-                  face_list[k] = face_id;
+                FaceValue<R2x2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
                 }
 
-                REQUIRE(k == face_list.size());
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
               }
 
-              Array<double> values =
-                FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, face_list);
+              SECTION("Array")
+              {
+                Array<R2x2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+                }
 
-              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));
               }
 
-              REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              SECTION("SmallArray")
+              {
+                SmallArray<R2x2> 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 += l2Norm(int_f_per_face[face_id] - values[face_id]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              }
             }
 
-            SECTION("SmallArray")
+            SECTION("face list")
             {
-              SmallArray<FaceId> face_list{mesh->numberOfFaces() / 2 + mesh->numberOfFaces() % 2};
-
+              SECTION("Array")
               {
-                size_t k = 0;
-                for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++(++face_id), ++k) {
-                  face_list[k] = face_id;
+                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());
                 }
 
-                REQUIRE(k == face_list.size());
-              }
+                Array<R2x2> values =
+                  FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, face_list);
 
-              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 += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+                }
 
-              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));
               }
 
-              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<R2x2> values =
+                  FaceIntegrator::integrate(f, GaussLobattoQuadratureDescriptor(4), *mesh, face_list);
+
+                double error = 0;
+                for (size_t i = 0; i < face_list.size(); ++i) {
+                  error += l2Norm(int_f_per_face[face_list[i]] - values[i]);
+                }
+
+                REQUIRE(error == Catch::Approx(0).margin(1E-10));
+              }
             }
           }
         }