From 31b1cfd46f0bd31348872b93676940db86fa73b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Tue, 30 Nov 2021 14:46:19 +0100
Subject: [PATCH] Allow TriangleTransformation in dimension 3

In the tests one checks that integrals are computed correctly on such surfaces.
---
 src/geometry/TriangleTransformation.hpp |  48 +++++++++-
 src/language/utils/IntegrateOnCells.hpp |   2 +-
 tests/test_TriangleGaussQuadrature.cpp  |   4 +-
 tests/test_TriangleTransformation.cpp   | 120 +++++++++++++++++++++---
 4 files changed, 156 insertions(+), 18 deletions(-)

diff --git a/src/geometry/TriangleTransformation.hpp b/src/geometry/TriangleTransformation.hpp
index 9053912b8..9210e0bda 100644
--- a/src/geometry/TriangleTransformation.hpp
+++ b/src/geometry/TriangleTransformation.hpp
@@ -4,7 +4,11 @@
 #include <algebra/TinyMatrix.hpp>
 #include <algebra/TinyVector.hpp>
 
-class TriangleTransformation
+template <size_t GivenDimension>
+class TriangleTransformation;
+
+template <>
+class TriangleTransformation<2>
 {
  public:
   constexpr static size_t Dimension = 2;
@@ -44,4 +48,46 @@ class TriangleTransformation
   ~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/utils/IntegrateOnCells.hpp b/src/language/utils/IntegrateOnCells.hpp
index e833fd5fb..216483dc4 100644
--- a/src/language/utils/IntegrateOnCells.hpp
+++ b/src/language/utils/IntegrateOnCells.hpp
@@ -399,7 +399,7 @@ class IntegrateOnCells<OutputType(InputType)> : public PugsFunctionAdapter<Outpu
       } else if constexpr (Dimension == 2) {
         switch (cell_type[cell_id]) {
         case CellType::Triangle: {
-          const TriangleTransformation T(xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]]);
+          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) {
diff --git a/tests/test_TriangleGaussQuadrature.cpp b/tests/test_TriangleGaussQuadrature.cpp
index 0bb7f34ec..3d7b5dffa 100644
--- a/tests/test_TriangleGaussQuadrature.cpp
+++ b/tests/test_TriangleGaussQuadrature.cpp
@@ -19,7 +19,7 @@ TEST_CASE("TriangleGaussQuadrature", "[analysis]")
     const R2 B{+1, -1};
     const R2 C{-1, +1};
 
-    TriangleTransformation t{A, B, C};
+    TriangleTransformation<2> t{A, B, C};
 
     auto point_list  = quadrature_formula.pointList();
     auto weight_list = quadrature_formula.weightList();
@@ -34,7 +34,7 @@ TEST_CASE("TriangleGaussQuadrature", "[analysis]")
 
   auto integrate_on_triangle = [](auto f, auto quadrature_formula, const TinyVector<2>& a, const TinyVector<2>& b,
                                   const TinyVector<2>& c) {
-    TriangleTransformation t(a, b, c);
+    TriangleTransformation<2> t(a, b, c);
 
     auto point_list  = quadrature_formula.pointList();
     auto weight_list = quadrature_formula.weightList();
diff --git a/tests/test_TriangleTransformation.cpp b/tests/test_TriangleTransformation.cpp
index a1c05aabc..99330e41d 100644
--- a/tests/test_TriangleTransformation.cpp
+++ b/tests/test_TriangleTransformation.cpp
@@ -1,31 +1,123 @@
 #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]")
 {
-  using R2 = TinyVector<2>;
+  SECTION("2D")
+  {
+    using R2 = TinyVector<2>;
 
-  const R2 a = {1, 2};
-  const R2 b = {3, 1};
-  const R2 c = {2, 5};
+    const R2 a = {1, 2};
+    const R2 b = {3, 1};
+    const R2 c = {2, 5};
 
-  const TriangleTransformation t(a, b, c);
+    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({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({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({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({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));
+    REQUIRE(t.jacobianDeterminant() == Catch::Approx(7));
+  }
+
+  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));
+    }
+  }
 }
-- 
GitLab