From a5b213029ba3bf19656c9c44adf334f9c183e87d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Mon, 4 Oct 2021 15:27:57 +0200
Subject: [PATCH] Displace Gauss Legendre quadrature formula in its own file

All orders are tested in 1d.
It is quite tricky to test correctly these formulae since starting
from order 10 formula give a very (very actually) precise evaluation
of common functions.
---
 CMakeLists.txt                           |   3 +-
 src/CMakeLists.txt                       |   3 +
 src/algebra/IntegrationTools.hpp         | 354 +---------------
 src/analysis/CMakeLists.txt              |   5 +
 src/analysis/GaussLegendreQuadrature.cpp | 291 +++++++++++++
 src/analysis/GaussLegendreQuadrature.hpp |  25 ++
 src/analysis/QuadratureFormula.hpp       |  52 +++
 tests/CMakeLists.txt                     |   3 +
 tests/test_FunctionEvaluation.cpp        | 105 -----
 tests/test_GaussLegendreQuadrature.cpp   | 503 +++++++++++++++++++++++
 10 files changed, 887 insertions(+), 457 deletions(-)
 create mode 100644 src/analysis/CMakeLists.txt
 create mode 100644 src/analysis/GaussLegendreQuadrature.cpp
 create mode 100644 src/analysis/GaussLegendreQuadrature.hpp
 create mode 100644 src/analysis/QuadratureFormula.hpp
 create mode 100644 tests/test_GaussLegendreQuadrature.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e9255bff6..4b302fe37 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -573,6 +573,7 @@ target_link_libraries(
   pugs
   PugsMesh
   PugsAlgebra
+  PugsAnalysis
   PugsUtils
   PugsLanguage
   PugsLanguageAST
@@ -606,7 +607,7 @@ if(${CMAKE_VERSION} VERSION_LESS "3.13.0")
     LIBRARY DESTINATION lib
     ARCHIVE DESTINATION lib)
 else()
-  install(TARGETS pugs  PugsAlgebra PugsUtils PugsLanguage PugsLanguageAST PugsLanguageModules PugsLanguageAlgorithms  PugsLanguageUtils PugsMesh PugsScheme PugsOutput
+  install(TARGETS pugs PugsAlgebra PugsAnalysis PugsUtils PugsLanguage PugsLanguageAST PugsLanguageModules PugsLanguageAlgorithms  PugsLanguageUtils PugsMesh PugsScheme PugsOutput
 
     RUNTIME DESTINATION bin
     LIBRARY DESTINATION lib
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 16904a7be..54cae625b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -12,6 +12,9 @@ add_subdirectory(language)
 # Pugs algebra
 add_subdirectory(algebra)
 
+# Pugs analysis
+add_subdirectory(analysis)
+
 # Pugs mesh
 add_subdirectory(mesh)
 
diff --git a/src/algebra/IntegrationTools.hpp b/src/algebra/IntegrationTools.hpp
index 756fe09b9..08642b794 100644
--- a/src/algebra/IntegrationTools.hpp
+++ b/src/algebra/IntegrationTools.hpp
@@ -7,6 +7,8 @@
 #include <utils/PugsMacros.hpp>
 #include <utils/Types.hpp>
 
+#include <analysis/GaussLegendreQuadrature.hpp>
+
 #include <cmath>
 
 enum class QuadratureType : int8_t
@@ -19,356 +21,6 @@ enum class QuadratureType : int8_t
   QT__end
 };
 
-template <size_t Dimension>
-class QuadratureBase
-{
- protected:
-  size_t m_order;
-  SmallArray<const TinyVector<Dimension>> m_point_list;
-  SmallArray<const double> m_weight_list;
-
-  virtual void _buildPointAndWeightLists() = 0;
-
- public:
-  size_t
-  numberOfPoints() const
-  {
-    Assert(m_point_list.size() == m_weight_list.size());
-    return m_point_list.size();
-  }
-
-  const SmallArray<const TinyVector<Dimension>>&
-  pointList() const
-  {
-    return m_point_list;
-  }
-
-  const SmallArray<const double>&
-  weightList() const
-  {
-    return m_weight_list;
-  }
-
-  QuadratureBase(QuadratureBase&&)      = default;
-  QuadratureBase(const QuadratureBase&) = default;
-
- protected:
-  explicit QuadratureBase(const size_t order) : m_order{order} {}
-
- public:
-  QuadratureBase()          = delete;
-  virtual ~QuadratureBase() = default;
-};
-
-template <size_t Dimension>
-class TensorialGaussLegendre final : public QuadratureBase<Dimension>
-{
- private:
-  void _buildPointAndWeightLists();
-
- public:
-  TensorialGaussLegendre(TensorialGaussLegendre&&)      = default;
-  TensorialGaussLegendre(const TensorialGaussLegendre&) = default;
-
-  explicit TensorialGaussLegendre(const size_t order) : QuadratureBase<Dimension>(order)
-  {
-    this->_buildPointAndWeightLists();
-  }
-
-  TensorialGaussLegendre()  = delete;
-  ~TensorialGaussLegendre() = default;
-};
-
-template <>
-void
-TensorialGaussLegendre<1>::_buildPointAndWeightLists()
-{
-  const size_t nb_points = m_order / 2 + 1;
-
-  SmallArray<TinyVector<1>> point_list(nb_points);
-  SmallArray<double> weight_list(nb_points);
-
-  switch (m_order) {
-  case 0:
-  case 1: {
-    point_list[0] = 0;
-
-    weight_list[0] = 2;
-    break;
-  }
-  case 2:
-  case 3: {
-    point_list[0] = -0.5773502691896257645091488;
-    point_list[1] = +0.5773502691896257645091488;
-
-    weight_list[0] = 1;
-    weight_list[1] = 1;
-    break;
-  }
-  case 4:
-  case 5: {
-    point_list[0] = -0.7745966692414833770358531;
-    point_list[1] = 0;
-    point_list[2] = +0.7745966692414833770358531;
-
-    weight_list[0] = 0.5555555555555555555555556;
-    weight_list[1] = 0.8888888888888888888888889;
-    weight_list[2] = 0.5555555555555555555555556;
-    break;
-  }
-  case 6:
-  case 7: {
-    point_list[0] = -0.8611363115940525752239465;
-    point_list[1] = -0.3399810435848562648026658;
-    point_list[2] = +0.3399810435848562648026658;
-    point_list[3] = +0.8611363115940525752239465;
-
-    weight_list[0] = 0.3478548451374538573730639;
-    weight_list[1] = 0.6521451548625461426269361;
-    weight_list[2] = 0.6521451548625461426269361;
-    weight_list[3] = 0.3478548451374538573730639;
-    break;
-  }
-  case 8:
-  case 9: {
-    point_list[0] = -0.9061798459386639927976269;
-    point_list[1] = -0.5384693101056830910363144;
-    point_list[2] = 0;
-    point_list[3] = +0.5384693101056830910363144;
-    point_list[4] = +0.9061798459386639927976269;
-
-    weight_list[0] = 0.2369268850561890875142640;
-    weight_list[1] = 0.4786286704993664680412915;
-    weight_list[2] = 0.5688888888888888888888889;
-    weight_list[3] = 0.4786286704993664680412915;
-    weight_list[4] = 0.2369268850561890875142640;
-    break;
-  }
-  case 10:
-  case 11: {
-    point_list[0] = -0.9324695142031520278123016;
-    point_list[1] = -0.6612093864662645136613996;
-    point_list[2] = -0.2386191860831969086305017;
-    point_list[3] = +0.2386191860831969086305017;
-    point_list[4] = +0.6612093864662645136613996;
-    point_list[5] = +0.9324695142031520278123016;
-
-    weight_list[0] = 0.1713244923791703450402961;
-    weight_list[1] = 0.3607615730481386075698335;
-    weight_list[2] = 0.4679139345726910473898703;
-    weight_list[3] = 0.4679139345726910473898703;
-    weight_list[4] = 0.3607615730481386075698335;
-    weight_list[5] = 0.1713244923791703450402961;
-    break;
-  }
-  case 12:
-  case 13: {
-    point_list[0] = -0.9491079123427585245261897;
-    point_list[1] = -0.7415311855993944398638648;
-    point_list[2] = -0.4058451513773971669066064;
-    point_list[3] = 0;
-    point_list[4] = +0.4058451513773971669066064;
-    point_list[5] = +0.7415311855993944398638648;
-    point_list[6] = +0.9491079123427585245261897;
-
-    weight_list[0] = 0.1294849661688696932706114;
-    weight_list[1] = 0.2797053914892766679014678;
-    weight_list[2] = 0.3818300505051189449503698;
-    weight_list[3] = 0.4179591836734693877551020;
-    weight_list[4] = 0.3818300505051189449503698;
-    weight_list[5] = 0.2797053914892766679014678;
-    weight_list[6] = 0.1294849661688696932706114;
-    break;
-  }
-  case 14:
-  case 15: {
-    point_list[0] = -0.9602898564975362316835609;
-    point_list[1] = -0.7966664774136267395915539;
-    point_list[2] = -0.5255324099163289858177390;
-    point_list[3] = -0.1834346424956498049394761;
-    point_list[4] = +0.1834346424956498049394761;
-    point_list[5] = +0.5255324099163289858177390;
-    point_list[6] = +0.7966664774136267395915539;
-    point_list[7] = +0.9602898564975362316835609;
-
-    weight_list[0] = 0.1012285362903762591525314;
-    weight_list[1] = 0.2223810344533744705443560;
-    weight_list[2] = 0.3137066458778872873379622;
-    weight_list[3] = 0.3626837833783619829651504;
-    weight_list[4] = 0.3626837833783619829651504;
-    weight_list[5] = 0.3137066458778872873379622;
-    weight_list[6] = 0.2223810344533744705443560;
-    weight_list[7] = 0.1012285362903762591525314;
-    break;
-  }
-  case 16:
-  case 17: {
-    point_list[0] = -0.9681602395076260898355762;
-    point_list[1] = -0.8360311073266357942994298;
-    point_list[2] = -0.6133714327005903973087020;
-    point_list[3] = -0.3242534234038089290385380;
-    point_list[4] = 0;
-    point_list[5] = +0.3242534234038089290385380;
-    point_list[6] = +0.6133714327005903973087020;
-    point_list[7] = +0.8360311073266357942994298;
-    point_list[8] = +0.9681602395076260898355762;
-
-    weight_list[0] = 0.0812743883615744119718922;
-    weight_list[1] = 0.1806481606948574040584720;
-    weight_list[2] = 0.2606106964029354623187429;
-    weight_list[3] = 0.3123470770400028400686304;
-    weight_list[4] = 0.3302393550012597631645251;
-    weight_list[5] = 0.3123470770400028400686304;
-    weight_list[6] = 0.2606106964029354623187429;
-    weight_list[7] = 0.1806481606948574040584720;
-    weight_list[8] = 0.0812743883615744119718922;
-    break;
-  }
-  case 18:
-  case 19: {
-    point_list[0] = -0.9739065285171717200779640;
-    point_list[1] = -0.8650633666889845107320967;
-    point_list[2] = -0.6794095682990244062343274;
-    point_list[3] = -0.4333953941292471907992659;
-    point_list[4] = -0.1488743389816312108848260;
-    point_list[5] = +0.1488743389816312108848260;
-    point_list[6] = +0.4333953941292471907992659;
-    point_list[7] = +0.6794095682990244062343274;
-    point_list[8] = +0.8650633666889845107320967;
-    point_list[9] = +0.9739065285171717200779640;
-
-    weight_list[0] = 0.0666713443086881375935688;
-    weight_list[1] = 0.1494513491505805931457763;
-    weight_list[2] = 0.2190863625159820439955349;
-    weight_list[3] = 0.2692667193099963550912269;
-    weight_list[4] = 0.2955242247147528701738930;
-    weight_list[5] = 0.2955242247147528701738930;
-    weight_list[6] = 0.2692667193099963550912269;
-    weight_list[7] = 0.2190863625159820439955349;
-    weight_list[8] = 0.1494513491505805931457763;
-    weight_list[9] = 0.0666713443086881375935688;
-    break;
-  }
-  case 20:
-  case 21: {
-    point_list[0]  = -0.9782286581460569928039380;
-    point_list[1]  = -0.8870625997680952990751578;
-    point_list[2]  = -0.7301520055740493240934163;
-    point_list[3]  = -0.5190961292068118159257257;
-    point_list[4]  = -0.2695431559523449723315320;
-    point_list[5]  = 0;
-    point_list[6]  = +0.2695431559523449723315320;
-    point_list[7]  = +0.5190961292068118159257257;
-    point_list[8]  = +0.7301520055740493240934163;
-    point_list[9]  = +0.8870625997680952990751578;
-    point_list[10] = +0.9782286581460569928039380;
-
-    weight_list[0]  = 0.0556685671161736664827537;
-    weight_list[1]  = 0.1255803694649046246346940;
-    weight_list[2]  = 0.1862902109277342514260980;
-    weight_list[3]  = 0.2331937645919904799185237;
-    weight_list[4]  = 0.2628045445102466621806889;
-    weight_list[5]  = 0.2729250867779006307144835;
-    weight_list[6]  = 0.2628045445102466621806889;
-    weight_list[7]  = 0.2331937645919904799185237;
-    weight_list[8]  = 0.1862902109277342514260980;
-    weight_list[9]  = 0.1255803694649046246346940;
-    weight_list[10] = 0.0556685671161736664827537;
-    break;
-  }
-  case 22:
-  case 23: {
-    point_list[0]  = -0.9815606342467192506905491;
-    point_list[1]  = -0.9041172563704748566784659;
-    point_list[2]  = -0.7699026741943046870368938;
-    point_list[3]  = -0.5873179542866174472967024;
-    point_list[4]  = -0.3678314989981801937526915;
-    point_list[5]  = -0.1252334085114689154724414;
-    point_list[6]  = +0.1252334085114689154724414;
-    point_list[7]  = +0.3678314989981801937526915;
-    point_list[8]  = +0.5873179542866174472967024;
-    point_list[9]  = +0.7699026741943046870368938;
-    point_list[10] = +0.9041172563704748566784659;
-    point_list[11] = +0.9815606342467192506905491;
-
-    weight_list[0]  = 0.0471753363865118271946160;
-    weight_list[1]  = 0.1069393259953184309602547;
-    weight_list[2]  = 0.1600783285433462263346525;
-    weight_list[3]  = 0.2031674267230659217490645;
-    weight_list[4]  = 0.2334925365383548087608499;
-    weight_list[5]  = 0.2491470458134027850005624;
-    weight_list[6]  = 0.2491470458134027850005624;
-    weight_list[7]  = 0.2334925365383548087608499;
-    weight_list[8]  = 0.2031674267230659217490645;
-    weight_list[9]  = 0.1600783285433462263346525;
-    weight_list[10] = 0.1069393259953184309602547;
-    weight_list[11] = 0.0471753363865118271946160;
-    break;
-  }
-  default: {
-    throw UnexpectedError("Gauss-Legendre quadratures handle orders up to 23");
-    break;
-  }
-  }
-
-  m_point_list  = point_list;
-  m_weight_list = weight_list;
-}
-
-template <>
-void
-TensorialGaussLegendre<2>::_buildPointAndWeightLists()
-{
-  const size_t nb_points_1d = this->m_order / 2 + 1;
-  const size_t nb_points    = nb_points_1d * nb_points_1d;
-
-  SmallArray<TinyVector<2>> point_list(nb_points);
-  SmallArray<double> weight_list(nb_points);
-
-  TensorialGaussLegendre<1> gauss_legendre_1d(m_order);
-  const auto& point_list_1d  = gauss_legendre_1d.pointList();
-  const auto& weight_list_1d = gauss_legendre_1d.weightList();
-
-  size_t l = 0;
-  for (size_t i = 0; i < nb_points_1d; ++i) {
-    for (size_t j = 0; j < nb_points_1d; ++j, ++l) {
-      point_list[l]  = TinyVector<2>{point_list_1d[i][0], point_list_1d[j][0]};
-      weight_list[l] = weight_list_1d[i] * weight_list_1d[j];
-    }
-  }
-
-  this->m_point_list  = point_list;
-  this->m_weight_list = weight_list;
-}
-
-template <>
-void
-TensorialGaussLegendre<3>::_buildPointAndWeightLists()
-{
-  const size_t nb_points_1d = this->m_order / 2 + 1;
-  const size_t nb_points    = nb_points_1d * nb_points_1d * nb_points_1d;
-
-  SmallArray<TinyVector<3>> point_list(nb_points);
-  SmallArray<double> weight_list(nb_points);
-
-  TensorialGaussLegendre<1> gauss_legendre_1d(m_order);
-  const auto& point_list_1d  = gauss_legendre_1d.pointList();
-  const auto& weight_list_1d = gauss_legendre_1d.weightList();
-
-  size_t l = 0;
-  for (size_t i = 0; i < nb_points_1d; ++i) {
-    for (size_t j = 0; j < nb_points_1d; ++j) {
-      for (size_t k = 0; k < nb_points_1d; ++k, ++l) {
-        point_list[l]  = TinyVector<3>{point_list_1d[i][0], point_list_1d[j][0], point_list_1d[k][0]};
-        weight_list[l] = weight_list_1d[i] * weight_list_1d[j] * weight_list_1d[k];
-      }
-    }
-  }
-
-  this->m_point_list  = point_list;
-  this->m_weight_list = weight_list;
-}
-
 class IntegrationMethod
 {
  public:
@@ -576,7 +228,7 @@ class IntegrationMethodLegendre : public IntegrationMethod
   void
   fillArrayLegendre()
   {
-    TensorialGaussLegendre<1> tensorial_gauss_legendre(Order);
+    GaussLegendreQuadrature<1> tensorial_gauss_legendre(Order);
     copy_to(tensorial_gauss_legendre.pointList(), m_quadrature_positions);
     copy_to(tensorial_gauss_legendre.weightList(), m_weights);
     // switch (Order) {
diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt
new file mode 100644
index 000000000..b2e2f4c2f
--- /dev/null
+++ b/src/analysis/CMakeLists.txt
@@ -0,0 +1,5 @@
+# ------------------- Source files --------------------
+
+add_library(
+  PugsAnalysis
+  GaussLegendreQuadrature.cpp)
diff --git a/src/analysis/GaussLegendreQuadrature.cpp b/src/analysis/GaussLegendreQuadrature.cpp
new file mode 100644
index 000000000..e9cc2b963
--- /dev/null
+++ b/src/analysis/GaussLegendreQuadrature.cpp
@@ -0,0 +1,291 @@
+#include <analysis/GaussLegendreQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+template <>
+void
+GaussLegendreQuadrature<1>::_buildPointAndWeightLists()
+{
+  const size_t nb_points = m_order / 2 + 1;
+
+  SmallArray<TinyVector<1>> point_list(nb_points);
+  SmallArray<double> weight_list(nb_points);
+
+  switch (m_order) {
+  case 0:
+  case 1: {
+    point_list[0] = 0;
+
+    weight_list[0] = 2;
+    break;
+  }
+  case 2:
+  case 3: {
+    point_list[0] = -0.5773502691896257645091488;
+    point_list[1] = +0.5773502691896257645091488;
+
+    weight_list[0] = 1;
+    weight_list[1] = 1;
+    break;
+  }
+  case 4:
+  case 5: {
+    point_list[0] = -0.7745966692414833770358531;
+    point_list[1] = 0;
+    point_list[2] = +0.7745966692414833770358531;
+
+    weight_list[0] = 0.5555555555555555555555556;
+    weight_list[1] = 0.8888888888888888888888889;
+    weight_list[2] = 0.5555555555555555555555556;
+    break;
+  }
+  case 6:
+  case 7: {
+    point_list[0] = -0.8611363115940525752239465;
+    point_list[1] = -0.3399810435848562648026658;
+    point_list[2] = +0.3399810435848562648026658;
+    point_list[3] = +0.8611363115940525752239465;
+
+    weight_list[0] = 0.3478548451374538573730639;
+    weight_list[1] = 0.6521451548625461426269361;
+    weight_list[2] = 0.6521451548625461426269361;
+    weight_list[3] = 0.3478548451374538573730639;
+    break;
+  }
+  case 8:
+  case 9: {
+    point_list[0] = -0.9061798459386639927976269;
+    point_list[1] = -0.5384693101056830910363144;
+    point_list[2] = 0;
+    point_list[3] = +0.5384693101056830910363144;
+    point_list[4] = +0.9061798459386639927976269;
+
+    weight_list[0] = 0.2369268850561890875142640;
+    weight_list[1] = 0.4786286704993664680412915;
+    weight_list[2] = 0.5688888888888888888888889;
+    weight_list[3] = 0.4786286704993664680412915;
+    weight_list[4] = 0.2369268850561890875142640;
+    break;
+  }
+  case 10:
+  case 11: {
+    point_list[0] = -0.9324695142031520278123016;
+    point_list[1] = -0.6612093864662645136613996;
+    point_list[2] = -0.2386191860831969086305017;
+    point_list[3] = +0.2386191860831969086305017;
+    point_list[4] = +0.6612093864662645136613996;
+    point_list[5] = +0.9324695142031520278123016;
+
+    weight_list[0] = 0.1713244923791703450402961;
+    weight_list[1] = 0.3607615730481386075698335;
+    weight_list[2] = 0.4679139345726910473898703;
+    weight_list[3] = 0.4679139345726910473898703;
+    weight_list[4] = 0.3607615730481386075698335;
+    weight_list[5] = 0.1713244923791703450402961;
+    break;
+  }
+  case 12:
+  case 13: {
+    point_list[0] = -0.9491079123427585245261897;
+    point_list[1] = -0.7415311855993944398638648;
+    point_list[2] = -0.4058451513773971669066064;
+    point_list[3] = 0;
+    point_list[4] = +0.4058451513773971669066064;
+    point_list[5] = +0.7415311855993944398638648;
+    point_list[6] = +0.9491079123427585245261897;
+
+    weight_list[0] = 0.1294849661688696932706114;
+    weight_list[1] = 0.2797053914892766679014678;
+    weight_list[2] = 0.3818300505051189449503698;
+    weight_list[3] = 0.4179591836734693877551020;
+    weight_list[4] = 0.3818300505051189449503698;
+    weight_list[5] = 0.2797053914892766679014678;
+    weight_list[6] = 0.1294849661688696932706114;
+    break;
+  }
+  case 14:
+  case 15: {
+    point_list[0] = -0.9602898564975362316835609;
+    point_list[1] = -0.7966664774136267395915539;
+    point_list[2] = -0.5255324099163289858177390;
+    point_list[3] = -0.1834346424956498049394761;
+    point_list[4] = +0.1834346424956498049394761;
+    point_list[5] = +0.5255324099163289858177390;
+    point_list[6] = +0.7966664774136267395915539;
+    point_list[7] = +0.9602898564975362316835609;
+
+    weight_list[0] = 0.1012285362903762591525314;
+    weight_list[1] = 0.2223810344533744705443560;
+    weight_list[2] = 0.3137066458778872873379622;
+    weight_list[3] = 0.3626837833783619829651504;
+    weight_list[4] = 0.3626837833783619829651504;
+    weight_list[5] = 0.3137066458778872873379622;
+    weight_list[6] = 0.2223810344533744705443560;
+    weight_list[7] = 0.1012285362903762591525314;
+    break;
+  }
+  case 16:
+  case 17: {
+    point_list[0] = -0.9681602395076260898355762;
+    point_list[1] = -0.8360311073266357942994298;
+    point_list[2] = -0.6133714327005903973087020;
+    point_list[3] = -0.3242534234038089290385380;
+    point_list[4] = 0;
+    point_list[5] = +0.3242534234038089290385380;
+    point_list[6] = +0.6133714327005903973087020;
+    point_list[7] = +0.8360311073266357942994298;
+    point_list[8] = +0.9681602395076260898355762;
+
+    weight_list[0] = 0.0812743883615744119718922;
+    weight_list[1] = 0.1806481606948574040584720;
+    weight_list[2] = 0.2606106964029354623187429;
+    weight_list[3] = 0.3123470770400028400686304;
+    weight_list[4] = 0.3302393550012597631645251;
+    weight_list[5] = 0.3123470770400028400686304;
+    weight_list[6] = 0.2606106964029354623187429;
+    weight_list[7] = 0.1806481606948574040584720;
+    weight_list[8] = 0.0812743883615744119718922;
+    break;
+  }
+  case 18:
+  case 19: {
+    point_list[0] = -0.9739065285171717200779640;
+    point_list[1] = -0.8650633666889845107320967;
+    point_list[2] = -0.6794095682990244062343274;
+    point_list[3] = -0.4333953941292471907992659;
+    point_list[4] = -0.1488743389816312108848260;
+    point_list[5] = +0.1488743389816312108848260;
+    point_list[6] = +0.4333953941292471907992659;
+    point_list[7] = +0.6794095682990244062343274;
+    point_list[8] = +0.8650633666889845107320967;
+    point_list[9] = +0.9739065285171717200779640;
+
+    weight_list[0] = 0.0666713443086881375935688;
+    weight_list[1] = 0.1494513491505805931457763;
+    weight_list[2] = 0.2190863625159820439955349;
+    weight_list[3] = 0.2692667193099963550912269;
+    weight_list[4] = 0.2955242247147528701738930;
+    weight_list[5] = 0.2955242247147528701738930;
+    weight_list[6] = 0.2692667193099963550912269;
+    weight_list[7] = 0.2190863625159820439955349;
+    weight_list[8] = 0.1494513491505805931457763;
+    weight_list[9] = 0.0666713443086881375935688;
+    break;
+  }
+  case 20:
+  case 21: {
+    point_list[0]  = -0.9782286581460569928039380;
+    point_list[1]  = -0.8870625997680952990751578;
+    point_list[2]  = -0.7301520055740493240934163;
+    point_list[3]  = -0.5190961292068118159257257;
+    point_list[4]  = -0.2695431559523449723315320;
+    point_list[5]  = 0;
+    point_list[6]  = +0.2695431559523449723315320;
+    point_list[7]  = +0.5190961292068118159257257;
+    point_list[8]  = +0.7301520055740493240934163;
+    point_list[9]  = +0.8870625997680952990751578;
+    point_list[10] = +0.9782286581460569928039380;
+
+    weight_list[0]  = 0.0556685671161736664827537;
+    weight_list[1]  = 0.1255803694649046246346940;
+    weight_list[2]  = 0.1862902109277342514260980;
+    weight_list[3]  = 0.2331937645919904799185237;
+    weight_list[4]  = 0.2628045445102466621806889;
+    weight_list[5]  = 0.2729250867779006307144835;
+    weight_list[6]  = 0.2628045445102466621806889;
+    weight_list[7]  = 0.2331937645919904799185237;
+    weight_list[8]  = 0.1862902109277342514260980;
+    weight_list[9]  = 0.1255803694649046246346940;
+    weight_list[10] = 0.0556685671161736664827537;
+    break;
+  }
+  case 22:
+  case 23: {
+    point_list[0]  = -0.9815606342467192506905491;
+    point_list[1]  = -0.9041172563704748566784659;
+    point_list[2]  = -0.7699026741943046870368938;
+    point_list[3]  = -0.5873179542866174472967024;
+    point_list[4]  = -0.3678314989981801937526915;
+    point_list[5]  = -0.1252334085114689154724414;
+    point_list[6]  = +0.1252334085114689154724414;
+    point_list[7]  = +0.3678314989981801937526915;
+    point_list[8]  = +0.5873179542866174472967024;
+    point_list[9]  = +0.7699026741943046870368938;
+    point_list[10] = +0.9041172563704748566784659;
+    point_list[11] = +0.9815606342467192506905491;
+
+    weight_list[0]  = 0.0471753363865118271946160;
+    weight_list[1]  = 0.1069393259953184309602547;
+    weight_list[2]  = 0.1600783285433462263346525;
+    weight_list[3]  = 0.2031674267230659217490645;
+    weight_list[4]  = 0.2334925365383548087608499;
+    weight_list[5]  = 0.2491470458134027850005624;
+    weight_list[6]  = 0.2491470458134027850005624;
+    weight_list[7]  = 0.2334925365383548087608499;
+    weight_list[8]  = 0.2031674267230659217490645;
+    weight_list[9]  = 0.1600783285433462263346525;
+    weight_list[10] = 0.1069393259953184309602547;
+    weight_list[11] = 0.0471753363865118271946160;
+    break;
+  }
+  default: {
+    throw NormalError("Gauss-Legendre quadrature formulae handle orders up to 23");
+  }
+  }
+
+  m_point_list  = point_list;
+  m_weight_list = weight_list;
+}
+
+template <>
+void
+GaussLegendreQuadrature<2>::_buildPointAndWeightLists()
+{
+  const size_t nb_points_1d = this->m_order / 2 + 1;
+  const size_t nb_points    = nb_points_1d * nb_points_1d;
+
+  SmallArray<TinyVector<2>> point_list(nb_points);
+  SmallArray<double> weight_list(nb_points);
+
+  GaussLegendreQuadrature<1> gauss_legendre_1d(m_order);
+  const auto& point_list_1d  = gauss_legendre_1d.pointList();
+  const auto& weight_list_1d = gauss_legendre_1d.weightList();
+
+  size_t l = 0;
+  for (size_t i = 0; i < nb_points_1d; ++i) {
+    for (size_t j = 0; j < nb_points_1d; ++j, ++l) {
+      point_list[l]  = TinyVector<2>{point_list_1d[i][0], point_list_1d[j][0]};
+      weight_list[l] = weight_list_1d[i] * weight_list_1d[j];
+    }
+  }
+
+  this->m_point_list  = point_list;
+  this->m_weight_list = weight_list;
+}
+
+template <>
+void
+GaussLegendreQuadrature<3>::_buildPointAndWeightLists()
+{
+  const size_t nb_points_1d = this->m_order / 2 + 1;
+  const size_t nb_points    = nb_points_1d * nb_points_1d * nb_points_1d;
+
+  SmallArray<TinyVector<3>> point_list(nb_points);
+  SmallArray<double> weight_list(nb_points);
+
+  GaussLegendreQuadrature<1> gauss_legendre_1d(m_order);
+  const auto& point_list_1d  = gauss_legendre_1d.pointList();
+  const auto& weight_list_1d = gauss_legendre_1d.weightList();
+
+  size_t l = 0;
+  for (size_t i = 0; i < nb_points_1d; ++i) {
+    for (size_t j = 0; j < nb_points_1d; ++j) {
+      for (size_t k = 0; k < nb_points_1d; ++k, ++l) {
+        point_list[l]  = TinyVector<3>{point_list_1d[i][0], point_list_1d[j][0], point_list_1d[k][0]};
+        weight_list[l] = weight_list_1d[i] * weight_list_1d[j] * weight_list_1d[k];
+      }
+    }
+  }
+
+  this->m_point_list  = point_list;
+  this->m_weight_list = weight_list;
+}
diff --git a/src/analysis/GaussLegendreQuadrature.hpp b/src/analysis/GaussLegendreQuadrature.hpp
new file mode 100644
index 000000000..3e4a8b0cc
--- /dev/null
+++ b/src/analysis/GaussLegendreQuadrature.hpp
@@ -0,0 +1,25 @@
+#ifndef GAUSS_LEGENDRE_QUADRATURE_HPP
+#define GAUSS_LEGENDRE_QUADRATURE_HPP
+
+#include <analysis/QuadratureFormula.hpp>
+
+template <size_t Dimension>
+class GaussLegendreQuadrature final : public QuadratureForumla<Dimension>
+{
+ private:
+  void _buildPointAndWeightLists();
+
+ public:
+  GaussLegendreQuadrature(GaussLegendreQuadrature&&)      = default;
+  GaussLegendreQuadrature(const GaussLegendreQuadrature&) = default;
+
+  explicit GaussLegendreQuadrature(const size_t order) : QuadratureForumla<Dimension>(order)
+  {
+    this->_buildPointAndWeightLists();
+  }
+
+  GaussLegendreQuadrature()  = delete;
+  ~GaussLegendreQuadrature() = default;
+};
+
+#endif   // GAUSS_LEGENDRE_QUADRATURE_HPP
diff --git a/src/analysis/QuadratureFormula.hpp b/src/analysis/QuadratureFormula.hpp
new file mode 100644
index 000000000..c92d31872
--- /dev/null
+++ b/src/analysis/QuadratureFormula.hpp
@@ -0,0 +1,52 @@
+#ifndef QUADRATURE_FORMULA_HPP
+#define QUADRATURE_FORMULA_HPP
+
+#include <algebra/TinyVector.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/PugsMacros.hpp>
+#include <utils/SmallArray.hpp>
+
+template <size_t Dimension>
+class QuadratureForumla
+{
+ protected:
+  size_t m_order;
+  SmallArray<const TinyVector<Dimension>> m_point_list;
+  SmallArray<const double> m_weight_list;
+
+  virtual void _buildPointAndWeightLists() = 0;
+
+ public:
+  PUGS_INLINE size_t
+  numberOfPoints() const
+  {
+    Assert(m_point_list.size() == m_weight_list.size());
+    return m_point_list.size();
+  }
+
+  PUGS_INLINE
+  const SmallArray<const TinyVector<Dimension>>&
+  pointList() const
+  {
+    return m_point_list;
+  }
+
+  PUGS_INLINE
+  const SmallArray<const double>&
+  weightList() const
+  {
+    return m_weight_list;
+  }
+
+  QuadratureForumla(QuadratureForumla&&)      = default;
+  QuadratureForumla(const QuadratureForumla&) = default;
+
+ protected:
+  explicit QuadratureForumla(const size_t order) : m_order{order} {}
+
+ public:
+  QuadratureForumla()          = delete;
+  virtual ~QuadratureForumla() = default;
+};
+
+#endif   // QUADRATURE_FORMULA_HPP
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5cc4fee50..b0b2b7328 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -80,6 +80,7 @@ add_executable (unit_tests
   test_FunctionProcessor.cpp
   test_FunctionSymbolId.cpp
   test_FunctionTable.cpp
+  test_GaussLegendreQuadrature.cpp
   test_IfProcessor.cpp
   test_IncDecExpressionProcessor.cpp
   test_INodeProcessor.cpp
@@ -145,6 +146,7 @@ target_link_libraries (unit_tests
   PugsLanguage
   PugsMesh
   PugsAlgebra
+  PugsAnalysis
   PugsScheme
   PugsOutput
   PugsUtils
@@ -160,6 +162,7 @@ target_link_libraries (unit_tests
 target_link_libraries (mpi_unit_tests
   test_Pugs_MeshDataBase
   PugsAlgebra
+  PugsAnalysis
   PugsUtils
   PugsLanguage
   PugsLanguageAST
diff --git a/tests/test_FunctionEvaluation.cpp b/tests/test_FunctionEvaluation.cpp
index 7893ba07e..2e290556e 100644
--- a/tests/test_FunctionEvaluation.cpp
+++ b/tests/test_FunctionEvaluation.cpp
@@ -7,114 +7,9 @@
 #include <language/utils/FunctionSymbolId.hpp>
 #include <language/utils/SymbolTable.hpp>
 #include <utils/PugsAssert.hpp>
-// Instantiate to ensure full coverage is performed
-// template class EvaluateAtPoints<double(TinyVector<1>)>;
-template class TinyVector<1>;
-template class EvaluateAtPoints<double(TinyVector<1>)>;
-template class IntegrationTools<2>;
 
 // clazy:excludeall=non-pod-global-static
 
-TEST_CASE("EvaluateAtPoints", "[integration]")
-{
-  std::shared_ptr<SymbolTable> s = std::make_shared<SymbolTable>();
-  const FunctionSymbolId f{2, s};
-  const Array<TinyVector<1>> positions(1);
-  positions[0] = 1;
-  SECTION("Instantiation")
-  {
-    // positions[0] = 1;
-    SmallArray<double> result(1);
-    //    result = EvaluateAtPoints<double(TinyVector<1>)>::evaluate(f, positions);
-    //    REQUIRE(result[0] == 1);
-  }
-}
-
-TEST_CASE("QuadratureFormula", "[analysis]")
-{
-  SECTION("TensorialGaussLegendre")
-  {
-    auto integrate = [](auto f, auto quadrature_formula, const double a, const double b) {
-      auto point_list  = quadrature_formula.pointList();
-      auto weight_list = quadrature_formula.weightList();
-
-      double alpha = 0.5 * (b - a);
-      double beta  = 0.5 * (a + b);
-
-      auto x = [&alpha, &beta](auto x_hat) { return alpha * x_hat + beta; };
-
-      auto value = weight_list[0] * f(x(point_list[0]));
-      for (size_t i = 1; i < weight_list.size(); ++i) {
-        value += weight_list[i] * f(x(point_list[i]));
-      }
-
-      return alpha * value;
-    };
-
-    auto get_order = [&integrate](auto f, auto quadrature_formula, const double a, const double b,
-                                  const double exact_value) {
-      double int_ab          = integrate(f, quadrature_formula, a, b);
-      double int_first_half  = integrate(f, quadrature_formula, a, 0.5 * (a + b));
-      double int_second_half = integrate(f, quadrature_formula, 0.5 * (a + b), b);
-
-      return -std::log((int_first_half + int_second_half - exact_value) / (int_ab - exact_value)) / std::log(2);
-    };
-
-    auto p0 = [](const TinyVector<1>&) { return 1; };
-    auto p1 = [](const TinyVector<1>& X) {
-      const double x = X[0];
-      return 2 * x + 1;
-    };
-    auto p2 = [](const TinyVector<1>& X) {
-      const double x = X[0];
-      return 3 * x * x + 2 * x + 1;
-    };
-    auto p3 = [](const TinyVector<1>& X) {
-      const double x = X[0];
-      return 4 * std::pow(x, 3) + 3 * x * x + 2 * x + 1;
-    };
-    auto p4 = [](const TinyVector<1>& X) {
-      const double x = X[0];
-      return 5 * std::pow(x, 4) + 4 * std::pow(x, 3) + 3 * x * x + 2 * x + 1;
-    };
-
-    SECTION("exact integration")
-    {
-      SECTION("order 0 and 1")
-      {
-        TensorialGaussLegendre<1> l0(0);
-        TensorialGaussLegendre<1> l1(1);
-
-        REQUIRE(l0.numberOfPoints() == 1);
-        REQUIRE(l1.numberOfPoints() == 1);
-
-        REQUIRE(integrate(p0, l1, 0.5, 1) == Catch::Approx(0.5));
-        REQUIRE(integrate(p1, l1, 0, 1) == Catch::Approx(2));
-        REQUIRE(integrate(p2, l1, 0, 1) != Catch::Approx(3));
-
-        REQUIRE(get_order(p2, l1, 0, 1, 3) == Catch::Approx(2));
-      }
-
-      SECTION("order 2 and 3")
-      {
-        TensorialGaussLegendre<1> l2(2);
-        TensorialGaussLegendre<1> l3(3);
-
-        REQUIRE(l2.numberOfPoints() == 2);
-        REQUIRE(l3.numberOfPoints() == 2);
-
-        REQUIRE(integrate(p0, l3, 0.5, 1) == Catch::Approx(0.5));
-        REQUIRE(integrate(p1, l3, 0, 1) == Catch::Approx(2));
-        REQUIRE(integrate(p2, l3, 0, 1) == Catch::Approx(3));
-        REQUIRE(integrate(p3, l3, 0, 1) == Catch::Approx(4));
-        REQUIRE(integrate(p4, l3, 0, 1) != Catch::Approx(5));
-
-        REQUIRE(get_order(p4, l3, 0, 1, 5) == Catch::Approx(4));
-      }
-    }
-  }
-}
-
 TEST_CASE("IntegrationTools", "[integration]")
 {
   IntegrationTools<2> integrationtools(QuadratureType::gausslobatto);
diff --git a/tests/test_GaussLegendreQuadrature.cpp b/tests/test_GaussLegendreQuadrature.cpp
new file mode 100644
index 000000000..f20013560
--- /dev/null
+++ b/tests/test_GaussLegendreQuadrature.cpp
@@ -0,0 +1,503 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <analysis/GaussLegendreQuadrature.hpp>
+#include <utils/Exceptions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("GaussLegendreQuadrature", "[analysis]")
+{
+  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();
+
+    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 < weight_list0.size(); ++i) {
+      if (weight_list0[i] != weight_list1[i]) {
+        return false;
+      }
+    }
+
+    return is_same;
+  };
+
+  SECTION("1D")
+  {
+    auto integrate = [](auto f, auto quadrature_formula, const double a, const double b) {
+      auto point_list  = quadrature_formula.pointList();
+      auto weight_list = quadrature_formula.weightList();
+
+      double alpha = 0.5 * (b - a);
+      double beta  = 0.5 * (a + b);
+
+      auto x = [&alpha, &beta](auto x_hat) { return alpha * x_hat + beta; };
+
+      auto value = weight_list[0] * f(x(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * f(x(point_list[i]));
+      }
+
+      return alpha * value;
+    };
+
+    auto get_order = [&integrate](auto f, auto quadrature_formula, const double a, const double b,
+                                  const double exact_value) {
+      double int_ab          = integrate(f, quadrature_formula, a, b);
+      double int_first_half  = integrate(f, quadrature_formula, a, 0.5 * (a + b));
+      double int_second_half = integrate(f, quadrature_formula, 0.5 * (a + b), b);
+
+      return -std::log((int_first_half + int_second_half - exact_value) / (int_ab - exact_value)) / std::log(2);
+    };
+
+    auto p0 = [](const TinyVector<1>&) { return 1; };
+    auto p1 = [&p0](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 2 * x + p0(X);
+    };
+    auto p2 = [&p1](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 3 * x * x + p1(X);
+    };
+    auto p3 = [&p2](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 4 * std::pow(x, 3) + p2(X);
+    };
+    auto p4 = [&p3](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 5 * std::pow(x, 4) + p3(X);
+    };
+    auto p5 = [&p4](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 6 * std::pow(x, 5) + p4(X);
+    };
+    auto p6 = [&p5](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 7 * std::pow(x, 6) + p5(X);
+    };
+    auto p7 = [&p6](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 8 * std::pow(x, 7) + p6(X);
+    };
+    auto p8 = [&p7](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 9 * std::pow(x, 8) + p7(X);
+    };
+    auto p9 = [&p8](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 10 * std::pow(x, 9) + p8(X);
+    };
+    auto p10 = [&p9](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 11 * std::pow(x, 10) + p9(X);
+    };
+    auto p11 = [&p10](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 12 * std::pow(x, 11) + p10(X);
+    };
+    auto p12 = [&p11](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 13 * std::pow(x, 12) + p11(X);
+    };
+    auto p13 = [&p12](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 14 * std::pow(x, 13) + p12(X);
+    };
+    auto p14 = [&p13](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 15 * std::pow(x, 14) + p13(X);
+    };
+    auto p15 = [&p14](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 16 * std::pow(x, 15) + p14(X);
+    };
+    auto p16 = [&p15](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 17 * std::pow(x, 16) + p15(X);
+    };
+    auto p17 = [&p16](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 18 * std::pow(x, 17) + p16(X);
+    };
+    auto p18 = [&p17](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 19 * std::pow(x, 18) + p17(X);
+    };
+    auto p19 = [&p18](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 20 * std::pow(x, 19) + p18(X);
+    };
+    auto p20 = [&p19](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 21 * std::pow(x, 20) + p19(X);
+    };
+    auto p21 = [&p20](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 22 * std::pow(x, 21) + p20(X);
+    };
+    auto p22 = [&p21](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 23 * std::pow(x, 22) + p21(X);
+    };
+    auto p23 = [&p22](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 24 * std::pow(x, 23) + p22(X);
+    };
+    auto p24 = [&p23](const TinyVector<1>& X) {
+      const double x = X[0];
+      return 25 * std::pow(x, 24) + p23(X);
+    };
+
+    SECTION("order 0 and 1")
+    {
+      GaussLegendreQuadrature<1> l0(0);
+      GaussLegendreQuadrature<1> l1(1);
+
+      REQUIRE(same_formulas(l0, l1));
+
+      REQUIRE(l0.numberOfPoints() == 1);
+      REQUIRE(l1.numberOfPoints() == 1);
+
+      REQUIRE(integrate(p0, l1, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l1, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l1, 0, 1) != Catch::Approx(3));
+
+      REQUIRE(get_order(p2, l1, -1, 1, 4) == Catch::Approx(2));
+    }
+
+    SECTION("order 2 and 3")
+    {
+      GaussLegendreQuadrature<1> l2(2);
+      GaussLegendreQuadrature<1> l3(3);
+
+      REQUIRE(same_formulas(l2, l3));
+
+      REQUIRE(l2.numberOfPoints() == 2);
+      REQUIRE(l3.numberOfPoints() == 2);
+
+      REQUIRE(integrate(p0, l3, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l3, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l3, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l3, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l3, 0, 1) != Catch::Approx(5));
+
+      REQUIRE(get_order(p4, l3, -1, 1, 6) == Catch::Approx(4));
+    }
+
+    SECTION("order 4 and 5")
+    {
+      GaussLegendreQuadrature<1> l4(4);
+      GaussLegendreQuadrature<1> l5(5);
+
+      REQUIRE(same_formulas(l4, l5));
+
+      REQUIRE(l4.numberOfPoints() == 3);
+      REQUIRE(l5.numberOfPoints() == 3);
+
+      REQUIRE(integrate(p0, l5, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l5, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l5, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l5, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l5, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l5, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l5, 0, 1) != Catch::Approx(7));
+
+      REQUIRE(get_order(p6, l5, -1, 1, 8) == Catch::Approx(6));
+    }
+
+    SECTION("order 6 and 7")
+    {
+      GaussLegendreQuadrature<1> l6(6);
+      GaussLegendreQuadrature<1> l7(7);
+
+      REQUIRE(same_formulas(l6, l7));
+
+      REQUIRE(l6.numberOfPoints() == 4);
+      REQUIRE(l7.numberOfPoints() == 4);
+
+      REQUIRE(integrate(p0, l7, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l7, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l7, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l7, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l7, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l7, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l7, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l7, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l7, 0, 1) != Catch::Approx(9));
+
+      REQUIRE(get_order(p8, l7, -1, 1, 10) == Catch::Approx(8));
+    }
+
+    SECTION("order 8 and 9")
+    {
+      GaussLegendreQuadrature<1> l8(8);
+      GaussLegendreQuadrature<1> l9(9);
+
+      REQUIRE(same_formulas(l8, l9));
+
+      REQUIRE(l8.numberOfPoints() == 5);
+      REQUIRE(l9.numberOfPoints() == 5);
+
+      REQUIRE(integrate(p0, l9, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l9, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l9, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l9, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l9, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l9, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l9, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l9, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l9, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l9, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l9, 0, 1) != Catch::Approx(11).epsilon(1E-13));
+
+      REQUIRE(get_order(p10, l9, -1, 1, 12) == Catch::Approx(10));
+    }
+
+    SECTION("order 10 and 11")
+    {
+      GaussLegendreQuadrature<1> l10(10);
+      GaussLegendreQuadrature<1> l11(11);
+
+      REQUIRE(same_formulas(l10, l11));
+
+      REQUIRE(l10.numberOfPoints() == 6);
+      REQUIRE(l11.numberOfPoints() == 6);
+
+      REQUIRE(integrate(p0, l11, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l11, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l11, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l11, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l11, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l11, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l11, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l11, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l11, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l11, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l11, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l11, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l11, 0, 1) != Catch::Approx(13).epsilon(1E-13));
+
+      REQUIRE(get_order(p12, l11, -1, 1, 14) == Catch::Approx(12));
+    }
+
+    SECTION("order 12 and 13")
+    {
+      GaussLegendreQuadrature<1> l12(12);
+      GaussLegendreQuadrature<1> l13(13);
+
+      REQUIRE(same_formulas(l12, l13));
+
+      REQUIRE(l12.numberOfPoints() == 7);
+      REQUIRE(l13.numberOfPoints() == 7);
+
+      REQUIRE(integrate(p0, l13, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l13, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l13, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l13, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l13, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l13, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l13, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l13, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l13, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l13, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l13, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l13, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l13, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l13, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l13, 0, 1) != Catch::Approx(15).epsilon(1E-13));
+
+      REQUIRE(get_order(p14, l13, -1, 1, 16) == Catch::Approx(14));
+    }
+
+    SECTION("order 14 and 15")
+    {
+      GaussLegendreQuadrature<1> l14(14);
+      GaussLegendreQuadrature<1> l15(15);
+
+      REQUIRE(same_formulas(l14, l15));
+
+      REQUIRE(l14.numberOfPoints() == 8);
+      REQUIRE(l15.numberOfPoints() == 8);
+
+      REQUIRE(integrate(p0, l15, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l15, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l15, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l15, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l15, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l15, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l15, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l15, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l15, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l15, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l15, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l15, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l15, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l15, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l15, 0, 1) == Catch::Approx(15));
+      REQUIRE(integrate(p15, l15, 0, 1) == Catch::Approx(16));
+      REQUIRE(integrate(p16, l15, 0, 1) != Catch::Approx(17).epsilon(1E-13));
+
+      REQUIRE(get_order(p16, l15, -1, 1, 18) == Catch::Approx(16));
+    }
+
+    SECTION("order 16 and 17")
+    {
+      GaussLegendreQuadrature<1> l16(16);
+      GaussLegendreQuadrature<1> l17(17);
+
+      REQUIRE(same_formulas(l16, l17));
+
+      REQUIRE(l16.numberOfPoints() == 9);
+      REQUIRE(l17.numberOfPoints() == 9);
+
+      REQUIRE(integrate(p0, l17, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l17, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l17, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l17, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l17, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l17, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l17, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l17, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l17, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l17, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l17, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l17, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l17, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l17, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l17, 0, 1) == Catch::Approx(15));
+      REQUIRE(integrate(p15, l17, 0, 1) == Catch::Approx(16));
+      REQUIRE(integrate(p16, l17, 0, 1) == Catch::Approx(17));
+      REQUIRE(integrate(p17, l17, 0, 1) == Catch::Approx(18));
+      REQUIRE(integrate(p18, l17, 0, 1) != Catch::Approx(19).epsilon(1E-13));
+
+      REQUIRE(get_order(p18, l17, -1, 1, 20) == Catch::Approx(18));
+    }
+
+    SECTION("order 18 and 19")
+    {
+      GaussLegendreQuadrature<1> l18(18);
+      GaussLegendreQuadrature<1> l19(19);
+
+      REQUIRE(same_formulas(l18, l19));
+
+      REQUIRE(l18.numberOfPoints() == 10);
+      REQUIRE(l19.numberOfPoints() == 10);
+
+      REQUIRE(integrate(p0, l19, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l19, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l19, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l19, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l19, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l19, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l19, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l19, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l19, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l19, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l19, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l19, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l19, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l19, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l19, 0, 1) == Catch::Approx(15));
+      REQUIRE(integrate(p15, l19, 0, 1) == Catch::Approx(16));
+      REQUIRE(integrate(p16, l19, 0, 1) == Catch::Approx(17));
+      REQUIRE(integrate(p17, l19, 0, 1) == Catch::Approx(18));
+      REQUIRE(integrate(p18, l19, 0, 1) == Catch::Approx(19));
+      REQUIRE(integrate(p19, l19, 0, 1) == Catch::Approx(20));
+      REQUIRE(integrate(p20, l19, 0, 1) != Catch::Approx(21).epsilon(1E-13));
+
+      REQUIRE(get_order(p20, l19, -1, 1, 22) == Catch::Approx(20));
+    }
+
+    SECTION("order 20 and 21")
+    {
+      GaussLegendreQuadrature<1> l20(20);
+      GaussLegendreQuadrature<1> l21(21);
+
+      REQUIRE(same_formulas(l20, l21));
+
+      REQUIRE(l20.numberOfPoints() == 11);
+      REQUIRE(l21.numberOfPoints() == 11);
+
+      REQUIRE(integrate(p0, l21, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l21, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l21, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l21, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l21, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l21, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l21, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l21, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l21, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l21, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l21, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l21, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l21, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l21, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l21, 0, 1) == Catch::Approx(15));
+      REQUIRE(integrate(p15, l21, 0, 1) == Catch::Approx(16));
+      REQUIRE(integrate(p16, l21, 0, 1) == Catch::Approx(17));
+      REQUIRE(integrate(p17, l21, 0, 1) == Catch::Approx(18));
+      REQUIRE(integrate(p18, l21, 0, 1) == Catch::Approx(19));
+      REQUIRE(integrate(p19, l21, 0, 1) == Catch::Approx(20));
+      REQUIRE(integrate(p20, l21, 0, 1) == Catch::Approx(21));
+      REQUIRE(integrate(p21, l21, 0, 1) == Catch::Approx(22));
+      REQUIRE(integrate(p22, l21, 0, 1) != Catch::Approx(23).epsilon(1E-14));
+
+      REQUIRE(get_order(p22, l21, -1, 1, 24) == Catch::Approx(22).margin(0.02));
+    }
+
+    SECTION("order 22 and 23")
+    {
+      GaussLegendreQuadrature<1> l22(22);
+      GaussLegendreQuadrature<1> l23(23);
+
+      REQUIRE(same_formulas(l22, l23));
+
+      REQUIRE(l22.numberOfPoints() == 12);
+      REQUIRE(l23.numberOfPoints() == 12);
+
+      REQUIRE(integrate(p0, l23, 0.5, 1) == Catch::Approx(0.5));
+      REQUIRE(integrate(p1, l23, 0, 1) == Catch::Approx(2));
+      REQUIRE(integrate(p2, l23, 0, 1) == Catch::Approx(3));
+      REQUIRE(integrate(p3, l23, 0, 1) == Catch::Approx(4));
+      REQUIRE(integrate(p4, l23, 0, 1) == Catch::Approx(5));
+      REQUIRE(integrate(p5, l23, 0, 1) == Catch::Approx(6));
+      REQUIRE(integrate(p6, l23, 0, 1) == Catch::Approx(7));
+      REQUIRE(integrate(p7, l23, 0, 1) == Catch::Approx(8));
+      REQUIRE(integrate(p8, l23, 0, 1) == Catch::Approx(9));
+      REQUIRE(integrate(p9, l23, 0, 1) == Catch::Approx(10));
+      REQUIRE(integrate(p10, l23, 0, 1) == Catch::Approx(11));
+      REQUIRE(integrate(p11, l23, 0, 1) == Catch::Approx(12));
+      REQUIRE(integrate(p12, l23, 0, 1) == Catch::Approx(13));
+      REQUIRE(integrate(p13, l23, 0, 1) == Catch::Approx(14));
+      REQUIRE(integrate(p14, l23, 0, 1) == Catch::Approx(15));
+      REQUIRE(integrate(p15, l23, 0, 1) == Catch::Approx(16));
+      REQUIRE(integrate(p16, l23, 0, 1) == Catch::Approx(17));
+      REQUIRE(integrate(p17, l23, 0, 1) == Catch::Approx(18));
+      REQUIRE(integrate(p18, l23, 0, 1) == Catch::Approx(19));
+      REQUIRE(integrate(p19, l23, 0, 1) == Catch::Approx(20));
+      REQUIRE(integrate(p20, l23, 0, 1) == Catch::Approx(21));
+      REQUIRE(integrate(p21, l23, 0, 1) == Catch::Approx(22));
+      REQUIRE(integrate(p22, l23, 0, 1) == Catch::Approx(23));
+      REQUIRE(integrate(p23, l23, 0, 1) == Catch::Approx(24));
+      REQUIRE(integrate(p24, l23, 0, 1) != Catch::Approx(25).epsilon(1E-15));
+
+      REQUIRE(get_order(p24, l23, -1, 1, 26) == Catch::Approx(24).margin(0.05));
+    }
+
+    SECTION("not implemented formulae")
+    {
+      REQUIRE_THROWS_WITH(GaussLegendreQuadrature<1>(24),
+                          "error: Gauss-Legendre quadrature formulae handle orders up to 23");
+    }
+  }
+}
-- 
GitLab