From bb66aff13a6e8217f71241fce89b32bafa81cab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com> Date: Thu, 21 Oct 2021 16:03:16 +0200 Subject: [PATCH] Add economical Gauss quadrature for square and cube (up to degree 7) --- src/analysis/CMakeLists.txt | 1 + src/analysis/EconomicalGaussQuadrature.cpp | 276 +++++++ src/analysis/EconomicalGaussQuadrature.hpp | 31 + tests/CMakeLists.txt | 1 + tests/test_EconomicalGaussQuadrature.cpp | 877 +++++++++++++++++++++ 5 files changed, 1186 insertions(+) create mode 100644 src/analysis/EconomicalGaussQuadrature.cpp create mode 100644 src/analysis/EconomicalGaussQuadrature.hpp create mode 100644 tests/test_EconomicalGaussQuadrature.cpp diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index bb934db37..5e30087d9 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -2,6 +2,7 @@ add_library( PugsAnalysis + EconomicalGaussQuadrature.cpp SimplicialGaussQuadrature.cpp TensorialGaussLegendreQuadrature.cpp TensorialGaussLobattoQuadrature.cpp) diff --git a/src/analysis/EconomicalGaussQuadrature.cpp b/src/analysis/EconomicalGaussQuadrature.cpp new file mode 100644 index 000000000..27ada266d --- /dev/null +++ b/src/analysis/EconomicalGaussQuadrature.cpp @@ -0,0 +1,276 @@ +#include <analysis/EconomicalGaussQuadrature.hpp> +#include <analysis/TensorialGaussLegendreQuadrature.hpp> +#include <utils/Exceptions.hpp> + +template <> +void +EconomicalGaussQuadrature<1>::_buildPointAndWeightLists() +{ + TensorialGaussLegendreQuadrature<1> gauss(m_degree); + + m_point_list = gauss.pointList(); + m_weight_list = gauss.weightList(); +} + +template <> +void +EconomicalGaussQuadrature<2>::_buildPointAndWeightLists() +{ + switch (m_degree) { + 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, 0}; + weight_list[0] = 4; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 2: + 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.577350269189626, +0.577350269189626}; + point_list[1] = {+0.577350269189626, -0.577350269189626}; + point_list[2] = {-0.577350269189626, +0.577350269189626}; + point_list[3] = {-0.577350269189626, -0.577350269189626}; + + weight_list[0] = 1; + weight_list[1] = 1; + weight_list[2] = 1; + weight_list[3] = 1; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 4: + case 5: { + constexpr size_t nb_points = 8; + SmallArray<TinyVector<2>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {+0.683130051063973, +0.000000000000000}; + point_list[1] = {-0.683130051063973, +0.000000000000000}; + point_list[2] = {+0.000000000000000, +0.683130051063973}; + point_list[3] = {+0.000000000000000, -0.683130051063973}; + point_list[4] = {+0.881917103688197, +0.881917103688197}; + point_list[5] = {+0.881917103688197, -0.881917103688197}; + point_list[6] = {-0.881917103688197, +0.881917103688197}; + point_list[7] = {-0.881917103688197, -0.881917103688197}; + + weight_list[0] = 0.816326530612245; + weight_list[1] = 0.816326530612245; + weight_list[2] = 0.816326530612245; + weight_list[3] = 0.816326530612245; + weight_list[4] = 0.183673469387755; + weight_list[5] = 0.183673469387755; + weight_list[6] = 0.183673469387755; + weight_list[7] = 0.183673469387755; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 6: + case 7: { + constexpr size_t nb_points = 12; + SmallArray<TinyVector<2>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {+0.925820099772551, +0.000000000000000}; + point_list[1] = {-0.925820099772551, +0.000000000000000}; + point_list[2] = {+0.000000000000000, +0.925820099772551}; + point_list[3] = {+0.000000000000000, -0.925820099772551}; + point_list[4] = {+0.805979782918599, +0.805979782918599}; + point_list[5] = {+0.805979782918599, -0.805979782918599}; + point_list[6] = {-0.805979782918599, +0.805979782918599}; + point_list[7] = {-0.805979782918599, -0.805979782918599}; + point_list[8] = {+0.380554433208316, +0.380554433208316}; + point_list[9] = {+0.380554433208316, -0.380554433208316}; + point_list[10] = {-0.380554433208316, +0.380554433208316}; + point_list[11] = {-0.380554433208316, -0.380554433208316}; + + weight_list[0] = 0.241975308641975; + weight_list[1] = 0.241975308641975; + weight_list[2] = 0.241975308641975; + weight_list[3] = 0.241975308641975; + weight_list[4] = 0.237431774690630; + weight_list[5] = 0.237431774690630; + weight_list[6] = 0.237431774690630; + weight_list[7] = 0.237431774690630; + weight_list[8] = 0.520592916667394; + weight_list[9] = 0.520592916667394; + weight_list[10] = 0.520592916667394; + weight_list[11] = 0.520592916667394; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + default: { + throw NormalError("Gauss quadrature formulae handle degrees up to 7 on quadrangles"); + } + } +} + +template <> +void +EconomicalGaussQuadrature<3>::_buildPointAndWeightLists() +{ + switch (m_degree) { + case 0: + case 1: { + constexpr size_t nb_points = 1; + SmallArray<TinyVector<3>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {0, 0, 0}; + + weight_list[0] = 8; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 2: + case 3: { + constexpr size_t nb_points = 6; + SmallArray<TinyVector<3>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {+1.0000000000, +0.0000000000, +0.0000000000}; + point_list[1] = {-1.0000000000, +0.0000000000, +0.0000000000}; + point_list[2] = {+0.0000000000, +1.0000000000, +0.0000000000}; + point_list[3] = {+0.0000000000, -1.0000000000, +0.0000000000}; + point_list[4] = {+0.0000000000, +0.0000000000, +1.0000000000}; + point_list[5] = {+0.0000000000, +0.0000000000, -1.0000000000}; + + weight_list[0] = 1.3333333333; + weight_list[1] = 1.3333333333; + weight_list[2] = 1.3333333333; + weight_list[3] = 1.3333333333; + weight_list[4] = 1.3333333333; + weight_list[5] = 1.3333333333; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 4: + case 5: { + constexpr size_t nb_points = 14; + SmallArray<TinyVector<3>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {+0.7958224257, +0.0000000000, +0.0000000000}; + point_list[1] = {-0.7958224257, +0.0000000000, +0.0000000000}; + point_list[2] = {+0.0000000000, +0.7958224257, +0.0000000000}; + point_list[3] = {+0.0000000000, -0.7958224257, +0.0000000000}; + point_list[4] = {+0.0000000000, +0.0000000000, +0.7958224257}; + point_list[5] = {+0.0000000000, +0.0000000000, -0.7958224257}; + point_list[6] = {+0.7587869106, +0.7587869106, +0.7587869106}; + point_list[7] = {+0.7587869106, -0.7587869106, +0.7587869106}; + point_list[8] = {+0.7587869106, +0.7587869106, -0.7587869106}; + point_list[9] = {+0.7587869106, -0.7587869106, -0.7587869106}; + point_list[10] = {-0.7587869106, +0.7587869106, +0.7587869106}; + point_list[11] = {-0.7587869106, -0.7587869106, +0.7587869106}; + point_list[12] = {-0.7587869106, +0.7587869106, -0.7587869106}; + point_list[13] = {-0.7587869106, -0.7587869106, -0.7587869106}; + + weight_list[0] = 0.8864265927; + weight_list[1] = 0.8864265927; + weight_list[2] = 0.8864265927; + weight_list[3] = 0.8864265927; + weight_list[4] = 0.8864265927; + weight_list[5] = 0.8864265927; + weight_list[6] = 0.3351800554; + weight_list[7] = 0.3351800554; + weight_list[8] = 0.3351800554; + weight_list[9] = 0.3351800554; + weight_list[10] = 0.3351800554; + weight_list[11] = 0.3351800554; + weight_list[12] = 0.3351800554; + weight_list[13] = 0.3351800554; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 6: + case 7: { + constexpr size_t nb_points = 27; + SmallArray<TinyVector<3>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {+0.0000000000, +0.0000000000, +0.0000000000}; + point_list[1] = {+0.8484180014, +0.0000000000, +0.0000000000}; + point_list[2] = {-0.8484180014, +0.0000000000, +0.0000000000}; + point_list[3] = {+0.0000000000, +0.8484180014, +0.0000000000}; + point_list[4] = {+0.0000000000, -0.8484180014, +0.0000000000}; + point_list[5] = {+0.0000000000, +0.0000000000, +0.8484180014}; + point_list[6] = {+0.0000000000, +0.0000000000, -0.8484180014}; + point_list[7] = {+0.6528164721, +0.6528164721, +0.6528164721}; + point_list[8] = {+0.6528164721, -0.6528164721, +0.6528164721}; + point_list[9] = {+0.6528164721, +0.6528164721, -0.6528164721}; + point_list[10] = {+0.6528164721, -0.6528164721, -0.6528164721}; + point_list[11] = {-0.6528164721, +0.6528164721, +0.6528164721}; + point_list[12] = {-0.6528164721, -0.6528164721, +0.6528164721}; + point_list[13] = {-0.6528164721, +0.6528164721, -0.6528164721}; + point_list[14] = {-0.6528164721, -0.6528164721, -0.6528164721}; + point_list[15] = {+0.0000000000, +1.1064128986, +1.1064128986}; + point_list[16] = {+0.0000000000, -1.1064128986, +1.1064128986}; + point_list[17] = {+0.0000000000, +1.1064128986, -1.1064128986}; + point_list[18] = {+0.0000000000, -1.1064128986, -1.1064128986}; + point_list[19] = {+1.1064128986, +0.0000000000, +1.1064128986}; + point_list[20] = {-1.1064128986, +0.0000000000, +1.1064128986}; + point_list[21] = {+1.1064128986, +0.0000000000, -1.1064128986}; + point_list[22] = {-1.1064128986, +0.0000000000, -1.1064128986}; + point_list[23] = {+1.1064128986, +1.1064128986, +0.0000000000}; + point_list[24] = {-1.1064128986, +1.1064128986, +0.0000000000}; + point_list[25] = {+1.1064128986, -1.1064128986, +0.0000000000}; + point_list[26] = {-1.1064128986, -1.1064128986, +0.0000000000}; + + weight_list[0] = 0.7880734827; + weight_list[1] = 0.4993690023; + weight_list[2] = 0.4993690023; + weight_list[3] = 0.4993690023; + weight_list[4] = 0.4993690023; + weight_list[5] = 0.4993690023; + weight_list[6] = 0.4993690023; + weight_list[7] = 0.4785084494; + weight_list[8] = 0.4785084494; + weight_list[9] = 0.4785084494; + weight_list[10] = 0.4785084494; + weight_list[11] = 0.4785084494; + weight_list[12] = 0.4785084494; + weight_list[13] = 0.4785084494; + weight_list[14] = 0.4785084494; + weight_list[15] = 0.0323037423; + weight_list[16] = 0.0323037423; + weight_list[17] = 0.0323037423; + weight_list[18] = 0.0323037423; + weight_list[19] = 0.0323037423; + weight_list[20] = 0.0323037423; + weight_list[21] = 0.0323037423; + weight_list[22] = 0.0323037423; + weight_list[23] = 0.0323037423; + weight_list[24] = 0.0323037423; + weight_list[25] = 0.0323037423; + weight_list[26] = 0.0323037423; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + default: { + throw NormalError("Gauss quadrature formulae handle degrees up to 7 on hexahedra"); + } + } +} diff --git a/src/analysis/EconomicalGaussQuadrature.hpp b/src/analysis/EconomicalGaussQuadrature.hpp new file mode 100644 index 000000000..0862d0a66 --- /dev/null +++ b/src/analysis/EconomicalGaussQuadrature.hpp @@ -0,0 +1,31 @@ +#ifndef ECONOMICAL_GAUSS_QUADRATURE_HPP +#define ECONOMICAL_GAUSS_QUADRATURE_HPP + +#include <analysis/QuadratureFormula.hpp> + +/** + * Defines Economical Gauss quadrature on the reference element + * \f$]-1,1[^d\f$, where \f$d\f$ denotes the \a Dimension. + * + * \note formulae are extracted from High-order Finite Element Method [2004 - Chapman & Hall] + */ +template <size_t Dimension> +class EconomicalGaussQuadrature final : public QuadratureForumla<Dimension> +{ + private: + void _buildPointAndWeightLists(); + + public: + EconomicalGaussQuadrature(EconomicalGaussQuadrature&&) = default; + EconomicalGaussQuadrature(const EconomicalGaussQuadrature&) = default; + + explicit EconomicalGaussQuadrature(const size_t degree) : QuadratureForumla<Dimension>(degree) + { + this->_buildPointAndWeightLists(); + } + + EconomicalGaussQuadrature() = delete; + ~EconomicalGaussQuadrature() = default; +}; + +#endif // ECONOMICAL_GAUSS_QUADRATURE_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b188acce3..a55587ae5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -68,6 +68,7 @@ add_executable (unit_tests test_DiscreteFunctionType.cpp test_DiscreteFunctionUtils.cpp test_DoWhileProcessor.cpp + test_EconomicalGaussQuadrature.cpp test_EigenvalueSolver.cpp test_EmbeddedData.cpp test_EmbeddedIDiscreteFunctionUtils.cpp diff --git a/tests/test_EconomicalGaussQuadrature.cpp b/tests/test_EconomicalGaussQuadrature.cpp new file mode 100644 index 000000000..1da84db8d --- /dev/null +++ b/tests/test_EconomicalGaussQuadrature.cpp @@ -0,0 +1,877 @@ +#include <catch2/catch_approx.hpp> +#include <catch2/catch_test_macros.hpp> +#include <catch2/matchers/catch_matchers_all.hpp> + +#include <analysis/EconomicalGaussQuadrature.hpp> +#include <geometry/AffineTransformation.hpp> +#include <utils/Exceptions.hpp> + +// clazy:excludeall=non-pod-global-static + +TEST_CASE("EconomicalGaussQuadrature", "[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 is_symmetric_formula = [](auto quadrature_formula) { + auto point_list = quadrature_formula.pointList(); + auto weight_list = quadrature_formula.weightList(); + + bool is_symmetric = true; + + const size_t middle_index = point_list.size() / 2; + for (size_t i = 0; i <= middle_index; ++i) { + if (point_list[i] != -point_list[point_list.size() - 1 - i]) { + return false; + } + } + for (size_t i = 0; i <= middle_index; ++i) { + if (weight_list[i] != weight_list[point_list.size() - 1 - i]) { + return false; + } + } + + return is_symmetric; + }; + + 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("degree 0 and 1") + { + EconomicalGaussQuadrature<1> l0(0); + EconomicalGaussQuadrature<1> l1(1); + + REQUIRE(same_formulas(l0, l1)); + REQUIRE(is_symmetric_formula(l1)); + + 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("degree 2 and 3") + { + EconomicalGaussQuadrature<1> l2(2); + EconomicalGaussQuadrature<1> l3(3); + + REQUIRE(same_formulas(l2, l3)); + REQUIRE(is_symmetric_formula(l3)); + + 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("degree 4 and 5") + { + EconomicalGaussQuadrature<1> l4(4); + EconomicalGaussQuadrature<1> l5(5); + + REQUIRE(same_formulas(l4, l5)); + REQUIRE(is_symmetric_formula(l5)); + + 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("degree 6 and 7") + { + EconomicalGaussQuadrature<1> l6(6); + EconomicalGaussQuadrature<1> l7(7); + + REQUIRE(same_formulas(l6, l7)); + REQUIRE(is_symmetric_formula(l7)); + + 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("degree 8 and 9") + { + EconomicalGaussQuadrature<1> l8(8); + EconomicalGaussQuadrature<1> l9(9); + + REQUIRE(same_formulas(l8, l9)); + REQUIRE(is_symmetric_formula(l9)); + + 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("degree 10 and 11") + { + EconomicalGaussQuadrature<1> l10(10); + EconomicalGaussQuadrature<1> l11(11); + + REQUIRE(same_formulas(l10, l11)); + REQUIRE(is_symmetric_formula(l11)); + + 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("degree 12 and 13") + { + EconomicalGaussQuadrature<1> l12(12); + EconomicalGaussQuadrature<1> l13(13); + + REQUIRE(same_formulas(l12, l13)); + REQUIRE(is_symmetric_formula(l13)); + + 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("degree 14 and 15") + { + EconomicalGaussQuadrature<1> l14(14); + EconomicalGaussQuadrature<1> l15(15); + + REQUIRE(same_formulas(l14, l15)); + REQUIRE(is_symmetric_formula(l15)); + + 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("degree 16 and 17") + { + EconomicalGaussQuadrature<1> l16(16); + EconomicalGaussQuadrature<1> l17(17); + + REQUIRE(same_formulas(l16, l17)); + REQUIRE(is_symmetric_formula(l17)); + + 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("degree 18 and 19") + { + EconomicalGaussQuadrature<1> l18(18); + EconomicalGaussQuadrature<1> l19(19); + + REQUIRE(same_formulas(l18, l19)); + REQUIRE(is_symmetric_formula(l19)); + + 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("degree 20 and 21") + { + EconomicalGaussQuadrature<1> l20(20); + EconomicalGaussQuadrature<1> l21(21); + + REQUIRE(same_formulas(l20, l21)); + REQUIRE(is_symmetric_formula(l21)); + + 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("degree 22 and 23") + { + EconomicalGaussQuadrature<1> l22(22); + EconomicalGaussQuadrature<1> l23(23); + + REQUIRE(same_formulas(l22, l23)); + REQUIRE(is_symmetric_formula(l23)); + + 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(EconomicalGaussQuadrature<1>(24), + "error: Gauss-Legendre quadrature formulae handle degrees 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_rectangle = [](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_rectangle](auto f, auto quadrature_formula, const double exact_value) { + using R2 = TinyVector<2>; + const double int_K_hat = integrate(f, quadrature_formula); + const double int_refined // + = integrate_on_rectangle(f, quadrature_formula, {R2{-1, -1}, R2{0, -1}, R2{-1, 0}}) + + integrate_on_rectangle(f, quadrature_formula, {R2{0, -1}, R2{1, -1}, R2{0, 0}}) + + integrate_on_rectangle(f, quadrature_formula, {R2{-1, 0}, R2{0, 0}, R2{-1, 1}}) + + integrate_on_rectangle(f, quadrature_formula, {R2{0, 0}, R2{1, 0}, R2{0, 1}}); + + return -std::log((int_refined - exact_value) / (int_K_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("degree 0 and 1") + { + EconomicalGaussQuadrature<2> l1(1); + + REQUIRE(l1.numberOfPoints() == 1); + + REQUIRE(integrate(p0, l1) == Catch::Approx(8)); + REQUIRE(integrate(p1, l1) == Catch::Approx(4)); + REQUIRE(integrate(p2, l1) != Catch::Approx(20. / 3)); + + REQUIRE(get_order(p2, l1, 20. / 3) == Catch::Approx(2)); + } + + SECTION("degree 2 and 3") + { + EconomicalGaussQuadrature<2> l2(2); + EconomicalGaussQuadrature<2> l3(3); + + REQUIRE(same_formulas(l2, l3)); + + REQUIRE(l3.numberOfPoints() == 4); + + REQUIRE(integrate(p0, l3) == Catch::Approx(8)); + REQUIRE(integrate(p1, l3) == Catch::Approx(4)); + REQUIRE(integrate(p2, l3) == Catch::Approx(20. / 3)); + REQUIRE(integrate(p3, l3) == Catch::Approx(-13)); + REQUIRE(integrate(p4, l3) != Catch::Approx(-1184. / 15)); + + REQUIRE(get_order(p4, l3, -1184. / 15) == Catch::Approx(4)); + } + + SECTION("degree 4 and 5") + { + EconomicalGaussQuadrature<2> l4(4); + EconomicalGaussQuadrature<2> l5(5); + + REQUIRE(same_formulas(l4, l5)); + + REQUIRE(l5.numberOfPoints() == 8); + + REQUIRE(integrate(p0, l5) == Catch::Approx(8)); + REQUIRE(integrate(p1, l5) == Catch::Approx(4)); + REQUIRE(integrate(p2, l5) == Catch::Approx(20. / 3)); + REQUIRE(integrate(p3, l5) == Catch::Approx(-13)); + REQUIRE(integrate(p4, l5) == Catch::Approx(-1184. / 15)); + REQUIRE(integrate(p5, l5) == Catch::Approx(1971. / 25)); + REQUIRE(integrate(p6, l5) != Catch::Approx(60441. / 175)); + + REQUIRE(get_order(p6, l5, 60441. / 175) == Catch::Approx(6)); + } + + SECTION("degree 6 and 7") + { + EconomicalGaussQuadrature<2> l6(6); + EconomicalGaussQuadrature<2> l7(7); + + REQUIRE(same_formulas(l6, l7)); + + REQUIRE(l7.numberOfPoints() == 12); + + REQUIRE(integrate(p0, l7) == Catch::Approx(8)); + REQUIRE(integrate(p1, l7) == Catch::Approx(4)); + REQUIRE(integrate(p2, l7) == Catch::Approx(20. / 3)); + REQUIRE(integrate(p3, l7) == Catch::Approx(-13)); + REQUIRE(integrate(p4, l7) == Catch::Approx(-1184. / 15)); + REQUIRE(integrate(p5, l7) == Catch::Approx(1971. / 25)); + REQUIRE(integrate(p6, l7) == Catch::Approx(60441. / 175)); + REQUIRE(integrate(p7, l7) == Catch::Approx(-1307119. / 525)); + REQUIRE(integrate(p8, l7) != Catch::Approx(957697. / 175)); + + REQUIRE(get_order(p8, l7, 957697. / 175) == Catch::Approx(8)); + } + + SECTION("not implemented formulae") + { + REQUIRE_THROWS_WITH(EconomicalGaussQuadrature<2>(8), + "error: Gauss quadrature formulae handle degrees up to 7 on quadrangles"); + } + } + + SECTION("3D") + { + 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_brick = [](auto f, auto quadrature_formula, const std::array<TinyVector<3>, 4>& tetrahedron) { + AffineTransformation<3> t{tetrahedron}; + + 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_brick](auto f, auto quadrature_formula, const double exact_value) { + using R3 = TinyVector<3>; + + const double int_K_hat = integrate(f, quadrature_formula); + const double int_refined // + = integrate_on_brick(f, quadrature_formula, {R3{-1, -1, -1}, R3{+0, -1, -1}, R3{-1, +0, -1}, R3{-1, -1, +0}}) + + integrate_on_brick(f, quadrature_formula, {R3{+0, -1, -1}, R3{+1, -1, -1}, R3{+0, +0, -1}, R3{+0, -1, +0}}) + + integrate_on_brick(f, quadrature_formula, {R3{-1, +0, -1}, R3{+0, +0, -1}, R3{-1, +1, -1}, R3{-1, +0, +0}}) + + integrate_on_brick(f, quadrature_formula, {R3{+0, +0, -1}, R3{+1, +0, -1}, R3{+0, +1, -1}, R3{+0, +0, +0}}) + + integrate_on_brick(f, quadrature_formula, {R3{-1, -1, +0}, R3{+0, -1, +0}, R3{-1, +0, +0}, R3{-1, -1, +1}}) + + integrate_on_brick(f, quadrature_formula, {R3{+0, -1, +0}, R3{+1, -1, +0}, R3{+0, +0, +0}, R3{+0, -1, +1}}) + + integrate_on_brick(f, quadrature_formula, {R3{-1, +0, +0}, R3{+0, +0, +0}, R3{-1, +1, +0}, R3{-1, +0, +1}}) + + integrate_on_brick(f, quadrature_formula, {R3{+0, +0, +0}, R3{+1, +0, +0}, R3{+0, +1, +0}, R3{+0, +0, +1}}); + + return -std::log((int_refined - exact_value) / (int_K_hat - exact_value)) / std::log(2); + }; + + auto p0 = [](const TinyVector<3>&) { return 4; }; + auto p1 = [](const TinyVector<3>& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return 2 * x + 3 * y + z - 1; + }; + auto p2 = [&p1](const TinyVector<3>& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return p1(X) * (2.5 * x - 3 * y + z + 3); + }; + auto p3 = [&p2](const TinyVector<3>& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return p2(X) * (3 * x + 2 * y - 3 * z - 1); + }; + auto p4 = [&p3](const TinyVector<3>& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return p3(X) * (2 * x - 0.5 * y - 1.3 * z + 1); + }; + auto p5 = [&p4](const TinyVector<3>& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return p4(X) * (-0.1 * x + 1.3 * y - 3 * z + 1); + }; + auto p6 = [&p5](const TinyVector<3>& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return p5(X) * 7875. / 143443 * (2 * x - y + 4 * z + 1); + }; + auto p7 = [&p6](const TinyVector<3>& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return p6(X) * (0.7 * x - 2.7 * y + 1.3 * z - 2); + }; + auto p8 = [&p7](const TinyVector<3>& X) { + const double x = X[0]; + const double y = X[1]; + const double z = X[2]; + return p7(X) * (0.3 * x + 1.2 * y - 0.7 * z + 0.2); + }; + + SECTION("degree 0 and 1") + { + EconomicalGaussQuadrature<3> l1(1); + + REQUIRE(l1.numberOfPoints() == 1); + + REQUIRE(integrate(p0, l1) == Catch::Approx(32)); + REQUIRE(integrate(p1, l1) == Catch::Approx(-8)); + REQUIRE(integrate(p2, l1) != Catch::Approx(-32)); + + REQUIRE(get_order(p2, l1, -32) == Catch::Approx(2)); + } + + SECTION("degree 2 and 3") + { + EconomicalGaussQuadrature<3> l2(2); + EconomicalGaussQuadrature<3> l3(2); + + REQUIRE(same_formulas(l2, l3)); + + REQUIRE(l3.numberOfPoints() == 6); + + REQUIRE(integrate(p0, l3) == Catch::Approx(32)); + REQUIRE(integrate(p1, l3) == Catch::Approx(-8)); + REQUIRE(integrate(p2, l3) == Catch::Approx(-32)); + REQUIRE(integrate(p3, l3) == Catch::Approx(108)); + REQUIRE(integrate(p4, l3) != Catch::Approx(868. / 75)); + + REQUIRE(get_order(p4, l3, 868. / 75) == Catch::Approx(4)); + } + + SECTION("degree 4 and 5") + { + EconomicalGaussQuadrature<3> l4(4); + EconomicalGaussQuadrature<3> l5(5); + + REQUIRE(same_formulas(l4, l5)); + + REQUIRE(l5.numberOfPoints() == 14); + + REQUIRE(integrate(p0, l5) == Catch::Approx(32)); + REQUIRE(integrate(p1, l5) == Catch::Approx(-8)); + REQUIRE(integrate(p2, l5) == Catch::Approx(-32)); + REQUIRE(integrate(p3, l5) == Catch::Approx(108)); + REQUIRE(integrate(p4, l5) == Catch::Approx(868. / 75)); + REQUIRE(integrate(p5, l5) == Catch::Approx(11176. / 225)); + REQUIRE(integrate(p6, l5) != Catch::Approx(-34781. / 430329)); + + REQUIRE(get_order(p6, l5, -34781. / 430329) == Catch::Approx(6)); + } + + SECTION("degree 6 and 7") + { + EconomicalGaussQuadrature<3> l6(6); + EconomicalGaussQuadrature<3> l7(7); + + REQUIRE(same_formulas(l6, l7)); + + REQUIRE(l7.numberOfPoints() == 27); + + REQUIRE(integrate(p0, l7) == Catch::Approx(32)); + REQUIRE(integrate(p1, l7) == Catch::Approx(-8)); + REQUIRE(integrate(p2, l7) == Catch::Approx(-32)); + REQUIRE(integrate(p3, l7) == Catch::Approx(108)); + REQUIRE(integrate(p4, l7) == Catch::Approx(868. / 75)); + REQUIRE(integrate(p5, l7) == Catch::Approx(11176. / 225)); + REQUIRE(integrate(p6, l7) == Catch::Approx(-34781. / 430329)); + REQUIRE(integrate(p7, l7) == Catch::Approx(-37338109. / 4303290)); + REQUIRE(integrate(p8, l7) != Catch::Approx(422437099. / 21516450)); + + REQUIRE(get_order(p8, l7, 422437099. / 21516450) == Catch::Approx(8)); + } + + SECTION("not implemented formulae") + { + REQUIRE_THROWS_WITH(EconomicalGaussQuadrature<3>(8), + "error: Gauss quadrature formulae handle degrees up to 7 on hexahedra"); + } + } + + SECTION("Access functions") + { + EconomicalGaussQuadrature<3> quadrature_formula(7); + auto point_list = quadrature_formula.pointList(); + auto weight_list = quadrature_formula.weightList(); + + REQUIRE(point_list.size() == quadrature_formula.numberOfPoints()); + REQUIRE(weight_list.size() == quadrature_formula.numberOfPoints()); + + for (size_t i = 0; i < quadrature_formula.numberOfPoints(); ++i) { + REQUIRE(&point_list[i] == &quadrature_formula.point(i)); + REQUIRE(&weight_list[i] == &quadrature_formula.weight(i)); + } + } +} -- GitLab