From 2039cd5a0172c8b1be1de9240887ff0631b61982 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Fri, 8 Oct 2021 18:31:04 +0200
Subject: [PATCH] Add Gauss Legendre formulae up to order 7 on reference
 triangle

---
 .../SimplicialGaussLegendreQuadrature.cpp     | 175 ++++++++++++-
 ...test_SimplicialGaussLegendreQuadrature.cpp | 240 ++++++++++++++++--
 2 files changed, 396 insertions(+), 19 deletions(-)

diff --git a/src/analysis/SimplicialGaussLegendreQuadrature.cpp b/src/analysis/SimplicialGaussLegendreQuadrature.cpp
index d09e9a020..c6143bdec 100644
--- a/src/analysis/SimplicialGaussLegendreQuadrature.cpp
+++ b/src/analysis/SimplicialGaussLegendreQuadrature.cpp
@@ -16,7 +16,180 @@ template <>
 void
 SimplicialGaussLegendreQuadrature<2>::_buildPointAndWeightLists()
 {
-  throw NotImplementedError("gauss legendre quadrature on 2d simplex");
+  switch (m_order) {
+  case 0:
+  case 1: {
+    constexpr size_t nb_points = 1;
+    SmallArray<TinyVector<2>> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    point_list[0]  = {-0.333333333333333, -0.333333333333333};
+    weight_list[0] = 2;
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 2: {
+    constexpr size_t nb_points = 3;
+    SmallArray<TinyVector<2>> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    point_list[0] = {-0.666666666666667, -0.666666666666667};
+    point_list[1] = {-0.666666666666667, +0.333333333333333};
+    point_list[2] = {+0.333333333333333, -0.666666666666667};
+
+    weight_list[0] = 0.666666666666667;
+    weight_list[1] = 0.666666666666667;
+    weight_list[2] = 0.666666666666667;
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 3: {
+    constexpr size_t nb_points = 4;
+    SmallArray<TinyVector<2>> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    point_list[0] = {-0.333333333333333, -0.333333333333333};
+    point_list[1] = {-0.600000000000000, -0.600000000000000};
+    point_list[2] = {-0.600000000000000, +0.200000000000000};
+    point_list[3] = {+0.200000000000000, -0.600000000000000};
+
+    weight_list[0] = -1.125;
+    weight_list[1] = 1.041666666666667;
+    weight_list[2] = 1.041666666666667;
+    weight_list[3] = 1.041666666666667;
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 4: {
+    constexpr size_t nb_points = 6;
+    SmallArray<TinyVector<2>> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    point_list[0] = {-0.108103018168070, -0.108103018168070};
+    point_list[1] = {-0.108103018168070, -0.783793963663860};
+    point_list[2] = {-0.783793963663860, -0.108103018168070};
+    point_list[3] = {-0.816847572980458, -0.816847572980458};
+    point_list[4] = {-0.816847572980458, +0.633695145960918};
+    point_list[5] = {+0.633695145960918, -0.816847572980458};
+
+    weight_list[0] = 0.446763179356022;
+    weight_list[1] = 0.446763179356022;
+    weight_list[2] = 0.446763179356022;
+    weight_list[3] = 0.219903487310644;
+    weight_list[4] = 0.219903487310644;
+    weight_list[5] = 0.219903487310644;
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 5: {
+    constexpr size_t nb_points = 7;
+    SmallArray<TinyVector<2>> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    point_list[0] = {-0.333333333333333, -0.333333333333333};
+    point_list[1] = {-0.059715871789770, -0.059715871789770};
+    point_list[2] = {-0.059715871789770, -0.880568256420460};
+    point_list[3] = {-0.880568256420460, -0.059715871789770};
+    point_list[4] = {-0.797426985353088, -0.797426985353088};
+    point_list[5] = {-0.797426985353088, +0.594853970706174};
+    point_list[6] = {+0.594853970706174, -0.797426985353088};
+
+    weight_list[0] = 0.45;
+    weight_list[1] = 0.264788305577012;
+    weight_list[2] = 0.264788305577012;
+    weight_list[3] = 0.264788305577012;
+    weight_list[4] = 0.251878361089654;
+    weight_list[5] = 0.251878361089654;
+    weight_list[6] = 0.251878361089654;
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 6: {
+    constexpr size_t nb_points = 12;
+    SmallArray<TinyVector<2>> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    point_list[0]  = {-0.501426509658180, -0.501426509658180};
+    point_list[1]  = {-0.501426509658180, +0.002853019316358};
+    point_list[2]  = {+0.002853019316358, -0.501426509658180};
+    point_list[3]  = {-0.873821971016996, -0.873821971016996};
+    point_list[4]  = {-0.873821971016996, +0.747643942033992};
+    point_list[5]  = {+0.747643942033992, -0.873821971016996};
+    point_list[6]  = {-0.379295097932432, +0.273004998242798};
+    point_list[7]  = {+0.273004998242798, -0.893709900310366};
+    point_list[8]  = {-0.893709900310366, -0.379295097932432};
+    point_list[9]  = {-0.379295097932432, -0.893709900310366};
+    point_list[10] = {+0.273004998242798, -0.379295097932432};
+    point_list[11] = {-0.893709900310366, +0.273004998242798};
+
+    weight_list[0]  = 0.233572551452758;
+    weight_list[1]  = 0.233572551452758;
+    weight_list[2]  = 0.233572551452758;
+    weight_list[3]  = 0.101689812740414;
+    weight_list[4]  = 0.101689812740414;
+    weight_list[5]  = 0.101689812740414;
+    weight_list[6]  = 0.165702151236748;
+    weight_list[7]  = 0.165702151236748;
+    weight_list[8]  = 0.165702151236748;
+    weight_list[9]  = 0.165702151236748;
+    weight_list[10] = 0.165702151236748;
+    weight_list[11] = 0.165702151236748;
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  case 7: {
+    constexpr size_t nb_points = 13;
+    SmallArray<TinyVector<2>> point_list(nb_points);
+    SmallArray<double> weight_list(nb_points);
+
+    point_list[0]  = {-0.333333333333333, -0.333333333333333};
+    point_list[1]  = {-0.479308067841920, -0.479308067841920};
+    point_list[2]  = {-0.479308067841920, -0.041383864316160};
+    point_list[3]  = {-0.041383864316160, -0.479308067841920};
+    point_list[4]  = {-0.869739794195568, -0.869739794195568};
+    point_list[5]  = {-0.869739794195568, +0.739479588391136};
+    point_list[6]  = {+0.739479588391136, -0.869739794195568};
+    point_list[7]  = {-0.374269007990252, +0.276888377139620};
+    point_list[8]  = {+0.276888377139620, -0.902619369149368};
+    point_list[9]  = {-0.902619369149368, -0.374269007990252};
+    point_list[10] = {-0.374269007990252, -0.902619369149368};
+    point_list[11] = {+0.276888377139620, -0.374269007990252};
+    point_list[12] = {-0.902619369149368, +0.276888377139620};
+
+    weight_list[0]  = -0.299140088935364;
+    weight_list[1]  = +0.351230514866416;
+    weight_list[2]  = +0.351230514866416;
+    weight_list[3]  = +0.351230514866416;
+    weight_list[4]  = +0.106694471217676;
+    weight_list[5]  = +0.106694471217676;
+    weight_list[6]  = +0.106694471217676;
+    weight_list[7]  = +0.154227521780514;
+    weight_list[8]  = +0.154227521780514;
+    weight_list[9]  = +0.154227521780514;
+    weight_list[10] = +0.154227521780514;
+    weight_list[11] = +0.154227521780514;
+    weight_list[12] = +0.154227521780514;
+
+    m_point_list  = point_list;
+    m_weight_list = weight_list;
+    break;
+  }
+  default: {
+    throw NormalError("Gauss-Legendre quadrature formulae handle orders up to 7 on triangles");
+  }
+  }
 }
 
 template <>
diff --git a/tests/test_SimplicialGaussLegendreQuadrature.cpp b/tests/test_SimplicialGaussLegendreQuadrature.cpp
index 80fd1c7e0..9517a9f1f 100644
--- a/tests/test_SimplicialGaussLegendreQuadrature.cpp
+++ b/tests/test_SimplicialGaussLegendreQuadrature.cpp
@@ -3,37 +3,38 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <analysis/SimplicialGaussLegendreQuadrature.hpp>
+#include <geometry/AffineTransformation.hpp>
 #include <utils/Exceptions.hpp>
 
 // clazy:excludeall=non-pod-global-static
 
 TEST_CASE("SimplicialGaussLegendreQuadrature", "[analysis]")
 {
-  auto same_formulas = [](auto quadrature_formula0, auto quadrature_formula1) {
-    auto point_list0  = quadrature_formula0.pointList();
-    auto weight_list0 = quadrature_formula0.weightList();
+  SECTION("1D")
+  {
+    auto same_formulas = [](auto quadrature_formula0, auto quadrature_formula1) {
+      auto point_list0  = quadrature_formula0.pointList();
+      auto weight_list0 = quadrature_formula0.weightList();
 
-    auto point_list1  = quadrature_formula1.pointList();
-    auto weight_list1 = quadrature_formula1.weightList();
+      auto point_list1  = quadrature_formula1.pointList();
+      auto weight_list1 = quadrature_formula1.weightList();
 
-    bool is_same = true;
+      bool is_same = true;
 
-    for (size_t i = 0; i < point_list0.size(); ++i) {
-      if (point_list0[i] != point_list1[i]) {
-        return false;
+      for (size_t i = 0; i < point_list0.size(); ++i) {
+        if (point_list0[i] != point_list1[i]) {
+          return false;
+        }
       }
-    }
-    for (size_t i = 0; i < weight_list0.size(); ++i) {
-      if (weight_list0[i] != weight_list1[i]) {
-        return false;
+      for (size_t i = 0; i < weight_list0.size(); ++i) {
+        if (weight_list0[i] != weight_list1[i]) {
+          return false;
+        }
       }
-    }
 
-    return is_same;
-  };
+      return is_same;
+    };
 
-  SECTION("1D")
-  {
     auto is_symmetric_formula = [](auto quadrature_formula) {
       auto point_list  = quadrature_formula.pointList();
       auto weight_list = quadrature_formula.weightList();
@@ -521,4 +522,207 @@ TEST_CASE("SimplicialGaussLegendreQuadrature", "[analysis]")
                           "error: Gauss-Legendre quadrature formulae handle orders up to 23");
     }
   }
+
+  SECTION("2D")
+  {
+    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_triangle = [](auto f, auto quadrature_formula, const std::array<TinyVector<2>, 3>& triangle) {
+      AffineTransformation<2> t{triangle};
+
+      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((int_refined - exact_value) / (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) * (2 * x - 3 * y - 3);
+    };
+
+    SECTION("order 1")
+    {
+      SimplicialGaussLegendreQuadrature<2> l1(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("order 2")
+    {
+      SimplicialGaussLegendreQuadrature<2> l2(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("order 3")
+    {
+      SimplicialGaussLegendreQuadrature<2> l3(3);
+
+      REQUIRE(l3.numberOfPoints() == 4);
+
+      REQUIRE(integrate(p0, l3) == Catch::Approx(4));
+      REQUIRE(integrate(p1, l3) == Catch::Approx(-4. / 3));
+      REQUIRE(integrate(p2, l3) == Catch::Approx(-19. / 3));
+      REQUIRE(integrate(p3, l3) == Catch::Approx(146. / 5));
+      REQUIRE(integrate(p4, l3) != Catch::Approx(-25. / 6));
+
+      REQUIRE(get_order(p4, l3, -25. / 6) == Catch::Approx(4));
+    }
+
+    SECTION("order 4")
+    {
+      SimplicialGaussLegendreQuadrature<2> l4(4);
+
+      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("order 5")
+    {
+      SimplicialGaussLegendreQuadrature<2> l5(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("order 6")
+    {
+      SimplicialGaussLegendreQuadrature<2> l6(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("order 7")
+    {
+      SimplicialGaussLegendreQuadrature<2> l7(7);
+
+      REQUIRE(l7.numberOfPoints() == 13);
+
+      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(-47734. / 315));
+
+      REQUIRE(get_order(p8, l7, -47734. / 315) == Catch::Approx(8));
+    }
+
+    SECTION("not implemented formulae")
+    {
+      REQUIRE_THROWS_WITH(SimplicialGaussLegendreQuadrature<2>(8),
+                          "error: Gauss-Legendre quadrature formulae handle orders up to 7 on triangles");
+    }
+  }
 }
-- 
GitLab