From a0ef66438537149d9794f5a370d8e74e770af10f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Tue, 9 Nov 2021 19:10:37 +0100
Subject: [PATCH] Improve testing of Q1Transformation

These additional tests exhibited an issue wrt the Jacobian calculation
in the 3D case.
---
 src/geometry/Q1Transformation.hpp |  14 ++--
 tests/test_Q1Transformation.cpp   | 124 +++++++++++++++++++++++-------
 2 files changed, 105 insertions(+), 33 deletions(-)

diff --git a/src/geometry/Q1Transformation.hpp b/src/geometry/Q1Transformation.hpp
index bd23e5095..55e87bcbb 100644
--- a/src/geometry/Q1Transformation.hpp
+++ b/src/geometry/Q1Transformation.hpp
@@ -43,8 +43,11 @@ class Q1Transformation
       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};
+      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);
     } else {
       static_assert(Dimension == 3, "invalid dimension");
@@ -54,16 +57,17 @@ class Q1Transformation
       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 * y,
+                                                  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 * y,
+                                                  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 * y,
+                                                  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);
     }
   }
diff --git a/tests/test_Q1Transformation.cpp b/tests/test_Q1Transformation.cpp
index f42a60b4b..680b53e22 100644
--- a/tests/test_Q1Transformation.cpp
+++ b/tests/test_Q1Transformation.cpp
@@ -32,34 +32,60 @@ TEST_CASE("Q1Transformation", "[geometry]")
   {
     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 std::array points = {a, b, c, d};
     const Q1Transformation<2> t(points);
 
     SECTION("values")
     {
-      REQUIRE(t({-1, -1})[0] == Catch::Approx(a[0]));
-      REQUIRE(t({-1, -1})[1] == Catch::Approx(a[1]));
+      REQUIRE(t(a_hat)[0] == Catch::Approx(a[0]));
+      REQUIRE(t(a_hat)[1] == Catch::Approx(a[1]));
 
-      REQUIRE(t({+1, -1})[0] == Catch::Approx(b[0]));
-      REQUIRE(t({+1, -1})[1] == Catch::Approx(b[1]));
+      REQUIRE(t(b_hat)[0] == Catch::Approx(b[0]));
+      REQUIRE(t(b_hat)[1] == Catch::Approx(b[1]));
 
-      REQUIRE(t({+1, +1})[0] == Catch::Approx(c[0]));
-      REQUIRE(t({+1, +1})[1] == Catch::Approx(c[1]));
+      REQUIRE(t(c_hat)[0] == Catch::Approx(c[0]));
+      REQUIRE(t(c_hat)[1] == Catch::Approx(c[1]));
 
-      REQUIRE(t({-1, +1})[0] == Catch::Approx(d[0]));
-      REQUIRE(t({-1, +1})[1] == Catch::Approx(d[1]));
+      REQUIRE(t(d_hat)[0] == Catch::Approx(d[0]));
+      REQUIRE(t(d_hat)[1] == Catch::Approx(d[1]));
 
-      REQUIRE(t({0, 0})[0] == Catch::Approx(0.25 * (a + b + c + d)[0]));
-      REQUIRE(t({0, 0})[1] == Catch::Approx(0.25 * (a + b + c + 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
@@ -69,20 +95,28 @@ TEST_CASE("Q1Transformation", "[geometry]")
           surface += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i));
         }
 
-        // 71.5 is actually the volume of the hexahedron
+        // 71.5 is actually the surface of the quadrangle
         REQUIRE(surface == Catch::Approx(71.5));
       }
 
-      SECTION("Gauss order 7")
+      SECTION("Gauss order 3")
       {
-        TensorialGaussLegendreQuadrature<2> gauss(7);
-        double surface = 0;
+        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
+        TensorialGaussLegendreQuadrature<2> gauss(3);
+        double integral = 0;
         for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
-          surface += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i));
+          integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(gauss.point(i));
         }
 
-        // 71.5 is actually the volume of the hexahedron
-        REQUIRE(surface == Catch::Approx(71.5));
+        REQUIRE(integral == Catch::Approx(-479. / 2));
       }
     }
   }
@@ -102,7 +136,7 @@ TEST_CASE("Q1Transformation", "[geometry]")
 
     const R3 m_hat = zero;
 
-    const R3 a = {1, 2, 0};
+    const R3 a = {0, 0, 0};
     const R3 b = {3, 1, 3};
     const R3 c = {2, 5, 2};
     const R3 d = {0, 3, 1};
@@ -132,29 +166,63 @@ TEST_CASE("Q1Transformation", "[geometry]")
 
     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")
       {
-        // At least 2^3 points is are required in 2d
-        TensorialGaussLegendreQuadrature<3> gauss(3);
+        // The jacobian determinant is of maximal degree 2 in each variable
+        TensorialGaussLegendreQuadrature<3> gauss(2);
         double volume = 0;
         for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
           volume += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i));
         }
 
-        // 22.5 is actually the volume of the hexahedron
-        REQUIRE(volume == Catch::Approx(22.5));
+        // 353. / 12 is actually the volume of the hexahedron
+        REQUIRE(volume == Catch::Approx(353. / 12));
       }
 
-      SECTION("Gauss order 7")
+      SECTION("Gauss order 4")
       {
-        TensorialGaussLegendreQuadrature<3> gauss(7);
-        double volume = 0;
+        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;
+        };
+
+        // Jacbian determinant is a degree 2 polynomial, so the
+        // following formula is required to reach exactness
+        TensorialGaussLegendreQuadrature<3> gauss(4);
+        double integral = 0;
         for (size_t i = 0; i < gauss.numberOfPoints(); ++i) {
-          volume += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i));
+          integral += gauss.weight(i) * t.jacobianDeterminant(gauss.point(i)) * p(gauss.point(i));
         }
 
-        // 22.5 is actually the volume of the hexahedron
-        REQUIRE(volume == Catch::Approx(22.5));
+        REQUIRE(integral == Catch::Approx(8795. / 72));
       }
     }
   }
-- 
GitLab