diff --git a/src/analysis/SimplicialGaussLegendreQuadrature.cpp b/src/analysis/SimplicialGaussLegendreQuadrature.cpp index c6143bdec0f2013660378d36625788e21408c7a7..48843282c27922109de9716778a8eee478883f9f 100644 --- a/src/analysis/SimplicialGaussLegendreQuadrature.cpp +++ b/src/analysis/SimplicialGaussLegendreQuadrature.cpp @@ -196,5 +196,273 @@ template <> void SimplicialGaussLegendreQuadrature<3>::_buildPointAndWeightLists() { - throw NotImplementedError("gauss legendre quadrature on 3d simplex"); + switch (m_order) { + 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.5, -0.5, -0.5}; + weight_list[0] = 1.33333333333333; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 2: { + constexpr size_t nb_points = 4; + SmallArray<TinyVector<3>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {-0.7236067977, -0.7236067977, -0.7236067977}; + point_list[1] = {+0.1708203932, -0.7236067977, -0.7236067977}; + point_list[2] = {-0.7236067977, +0.1708203932, -0.7236067977}; + point_list[3] = {-0.7236067977, -0.7236067977, +0.1708203932}; + + weight_list[0] = 0.333333333333333; + weight_list[1] = 0.333333333333333; + weight_list[2] = 0.333333333333333; + weight_list[3] = 0.333333333333333; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 3: { + constexpr size_t nb_points = 5; + SmallArray<TinyVector<3>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {-0.5000000000, -0.5000000000, -0.5000000000}; + point_list[1] = {-0.66666666666666666666, -0.66666666666666666666, -0.66666666666666666666}; + point_list[2] = {-0.66666666666666666666, -0.66666666666666666666, +0.0000000000}; + point_list[3] = {-0.66666666666666666666, +0.0000000000, -0.66666666666666666666}; + point_list[4] = {+0.0000000000, -0.66666666666666666666, -0.66666666666666666666}; + + weight_list[0] = -1.066666666666666; + weight_list[1] = +0.600000000000000; + weight_list[2] = +0.600000000000000; + weight_list[3] = +0.600000000000000; + weight_list[4] = +0.600000000000000; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 4: { + constexpr size_t nb_points = 11; + SmallArray<TinyVector<3>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {-0.5000000000, -0.5000000000, -0.5000000000}; + point_list[1] = {-0.8571428571, -0.8571428571, -0.8571428571}; + point_list[2] = {-0.8571428571, -0.8571428571, +0.5714285714}; + point_list[3] = {-0.8571428571, +0.5714285714, -0.8571428571}; + point_list[4] = {+0.5714285714, -0.8571428571, -0.8571428571}; + point_list[5] = {-0.2011928476, -0.2011928476, -0.7988071523}; + point_list[6] = {-0.2011928476, -0.7988071523, -0.2011928476}; + point_list[7] = {-0.7988071523, -0.2011928476, -0.2011928476}; + point_list[8] = {-0.2011928476, -0.7988071523, -0.7988071523}; + point_list[9] = {-0.7988071523, -0.2011928476, -0.7988071523}; + point_list[10] = {-0.7988071523, -0.7988071523, -0.2011928476}; + + weight_list[0] = -0.105244444444444; + weight_list[1] = +0.060977777777777; + weight_list[2] = +0.060977777777777; + weight_list[3] = +0.060977777777777; + weight_list[4] = +0.060977777777777; + weight_list[5] = +0.199111111111111; + weight_list[6] = +0.199111111111111; + weight_list[7] = +0.199111111111111; + weight_list[8] = +0.199111111111111; + weight_list[9] = +0.199111111111111; + weight_list[10] = +0.199111111111111; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + 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.8145294993, -0.8145294993, -0.8145294993}; + point_list[1] = {+0.4435884981, -0.8145294993, -0.8145294993}; + point_list[2] = {-0.8145294993, +0.4435884981, -0.8145294993}; + point_list[3] = {-0.8145294993, -0.8145294993, +0.4435884981}; + point_list[4] = {-0.3782281614, -0.3782281614, -0.3782281614}; + point_list[5] = {-0.8653155155, -0.3782281614, -0.3782281614}; + point_list[6] = {-0.3782281614, -0.8653155155, -0.3782281614}; + point_list[7] = {-0.3782281614, -0.3782281614, -0.8653155155}; + point_list[8] = {-0.0910074082, -0.0910074082, -0.9089925917}; + point_list[9] = {-0.0910074082, -0.9089925917, -0.0910074082}; + point_list[10] = {-0.9089925917, -0.0910074082, -0.0910074082}; + point_list[11] = {-0.0910074082, -0.9089925917, -0.9089925917}; + point_list[12] = {-0.9089925917, -0.0910074082, -0.9089925917}; + point_list[13] = {-0.9089925917, -0.9089925917, -0.0910074082}; + + weight_list[0] = 0.0979907241; + weight_list[1] = 0.0979907241; + weight_list[2] = 0.0979907241; + weight_list[3] = 0.0979907241; + weight_list[4] = 0.1502505676; + weight_list[5] = 0.1502505676; + weight_list[6] = 0.1502505676; + weight_list[7] = 0.1502505676; + weight_list[8] = 0.0567280277; + weight_list[9] = 0.0567280277; + weight_list[10] = 0.0567280277; + weight_list[11] = 0.0567280277; + weight_list[12] = 0.0567280277; + weight_list[13] = 0.0567280277; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 6: { + constexpr size_t nb_points = 24; + SmallArray<TinyVector<3>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {-0.5707942574, -0.5707942574, -0.5707942574}; + point_list[1] = {-0.2876172275, -0.5707942574, -0.5707942574}; + point_list[2] = {-0.5707942574, -0.2876172275, -0.5707942574}; + point_list[3] = {-0.5707942574, -0.5707942574, -0.2876172275}; + point_list[4] = {-0.9186520829, -0.9186520829, -0.9186520829}; + point_list[5] = {+0.7559562487, -0.9186520829, -0.9186520829}; + point_list[6] = {-0.9186520829, +0.7559562487, -0.9186520829}; + point_list[7] = {-0.9186520829, -0.9186520829, +0.7559562487}; + point_list[8] = {-0.3553242197, -0.3553242197, -0.3553242197}; + point_list[9] = {-0.9340273408, -0.3553242197, -0.3553242197}; + point_list[10] = {-0.3553242197, -0.9340273408, -0.3553242197}; + point_list[11] = {-0.3553242197, -0.3553242197, -0.9340273408}; + point_list[12] = {-0.8726779962, -0.8726779962, -0.4606553370}; + point_list[13] = {-0.8726779962, -0.4606553370, -0.8726779962}; + point_list[14] = {-0.8726779962, -0.8726779962, +0.2060113295}; + point_list[15] = {-0.8726779962, +0.2060113295, -0.8726779962}; + point_list[16] = {-0.8726779962, -0.4606553370, +0.2060113295}; + point_list[17] = {-0.8726779962, +0.2060113295, -0.4606553370}; + point_list[18] = {-0.4606553370, -0.8726779962, -0.8726779962}; + point_list[19] = {-0.4606553370, -0.8726779962, +0.2060113295}; + point_list[20] = {-0.4606553370, +0.2060113295, -0.8726779962}; + point_list[21] = {+0.2060113295, -0.8726779962, -0.4606553370}; + point_list[22] = {+0.2060113295, -0.8726779962, -0.8726779962}; + point_list[23] = {+0.2060113295, -0.4606553370, -0.8726779962}; + + weight_list[0] = 0.0532303336; + weight_list[1] = 0.0532303336; + weight_list[2] = 0.0532303336; + weight_list[3] = 0.0532303336; + weight_list[4] = 0.0134362814; + weight_list[5] = 0.0134362814; + weight_list[6] = 0.0134362814; + weight_list[7] = 0.0134362814; + weight_list[8] = 0.0738095753; + weight_list[9] = 0.0738095753; + weight_list[10] = 0.0738095753; + weight_list[11] = 0.0738095753; + weight_list[12] = 0.0642857142; + weight_list[13] = 0.0642857142; + weight_list[14] = 0.0642857142; + weight_list[15] = 0.0642857142; + weight_list[16] = 0.0642857142; + weight_list[17] = 0.0642857142; + weight_list[18] = 0.0642857142; + weight_list[19] = 0.0642857142; + weight_list[20] = 0.0642857142; + weight_list[21] = 0.0642857142; + weight_list[22] = 0.0642857142; + weight_list[23] = 0.0642857142; + weight_list[23] = 0.0642857142; + weight_list[23] = 0.0642857142; + weight_list[23] = 0.0642857142; + weight_list[23] = 0.0642857142; + weight_list[23] = 0.0642857142; + weight_list[23] = 0.0642857142; + weight_list[23] = 0.0642857142; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + case 7: { + constexpr size_t nb_points = 31; + SmallArray<TinyVector<3>> point_list(nb_points); + SmallArray<double> weight_list(nb_points); + + point_list[0] = {+0.0000000000, +0.0000000000, -1.0000000000}; + point_list[1] = {+0.0000000000, -1.0000000000, +0.0000000000}; + point_list[2] = {-1.0000000000, +0.0000000000, +0.0000000000}; + point_list[3] = {-1.0000000000, -1.0000000000, +0.0000000000}; + point_list[4] = {-1.0000000000, +0.0000000000, -1.0000000000}; + point_list[5] = {+0.0000000000, -1.0000000000, -1.0000000000}; + point_list[6] = {-0.5000000000, -0.5000000000, -0.5000000000}; + point_list[7] = {-0.8435736153, -0.8435736153, -0.8435736153}; + point_list[8] = {-0.8435736153, -0.8435736153, +0.5307208460}; + point_list[9] = {-0.8435736153, +0.5307208460, -0.8435736153}; + point_list[10] = {+0.5307208460, -0.8435736153, -0.8435736153}; + point_list[11] = {-0.7563135666, -0.7563135666, -0.7563135666}; + point_list[12] = {-0.7563135666, -0.7563135666, +0.2689407000}; + point_list[13] = {-0.7563135666, +0.2689407000, -0.7563135666}; + point_list[14] = {+0.2689407000, -0.7563135666, -0.7563135666}; + point_list[15] = {-0.3349216711, -0.3349216711, -0.3349216711}; + point_list[16] = {-0.3349216711, -0.3349216711, -0.9952349866}; + point_list[17] = {-0.3349216711, -0.9952349866, -0.3349216711}; + point_list[18] = {-0.9952349866, -0.3349216711, -0.3349216711}; + point_list[19] = {-0.8000000000, -0.8000000000, -0.6000000000}; + point_list[20] = {-0.8000000000, -0.6000000000, -0.8000000000}; + point_list[21] = {-0.8000000000, -0.8000000000, +0.2000000000}; + point_list[22] = {-0.8000000000, +0.2000000000, -0.8000000000}; + point_list[23] = {-0.8000000000, -0.6000000000, +0.2000000000}; + point_list[24] = {-0.8000000000, +0.2000000000, -0.6000000000}; + point_list[25] = {-0.6000000000, -0.8000000000, -0.8000000000}; + point_list[26] = {-0.6000000000, -0.8000000000, +0.2000000000}; + point_list[27] = {-0.6000000000, +0.2000000000, -0.8000000000}; + point_list[28] = {+0.2000000000, -0.8000000000, -0.6000000000}; + point_list[29] = {+0.2000000000, -0.8000000000, -0.8000000000}; + point_list[30] = {+0.2000000000, -0.6000000000, -0.8000000000}; + + weight_list[0] = +0.0077601410; + weight_list[1] = +0.0077601410; + weight_list[2] = +0.0077601410; + weight_list[3] = +0.0077601410; + weight_list[4] = +0.0077601410; + weight_list[5] = +0.0077601410; + weight_list[6] = +0.1461137877; + weight_list[7] = +0.0847995321; + weight_list[8] = +0.0847995321; + weight_list[9] = +0.0847995321; + weight_list[10] = +0.0847995321; + weight_list[11] = -0.5001419209; + weight_list[12] = -0.5001419209; + weight_list[13] = -0.5001419209; + weight_list[14] = -0.5001419209; + weight_list[15] = +0.0391314021; + weight_list[16] = +0.0391314021; + weight_list[17] = +0.0391314021; + weight_list[18] = +0.0391314021; + weight_list[19] = +0.2204585537; + weight_list[20] = +0.2204585537; + weight_list[21] = +0.2204585537; + weight_list[22] = +0.2204585537; + weight_list[23] = +0.2204585537; + weight_list[24] = +0.2204585537; + weight_list[25] = +0.2204585537; + weight_list[26] = +0.2204585537; + weight_list[27] = +0.2204585537; + weight_list[28] = +0.2204585537; + weight_list[29] = +0.2204585537; + weight_list[30] = +0.2204585537; + + m_point_list = point_list; + m_weight_list = weight_list; + break; + } + default: { + throw NormalError("Gauss-Legendre quadrature formulae handle orders up to 7 on tetrahedra"); + } + } } diff --git a/tests/test_SimplicialGaussLegendreQuadrature.cpp b/tests/test_SimplicialGaussLegendreQuadrature.cpp index 9517a9f1fd2326424fac92ffbbdf91b1f0ea4efa..a4334e6ad958835441972a70c99bb571875a64bb 100644 --- a/tests/test_SimplicialGaussLegendreQuadrature.cpp +++ b/tests/test_SimplicialGaussLegendreQuadrature.cpp @@ -725,4 +725,225 @@ TEST_CASE("SimplicialGaussLegendreQuadrature", "[analysis]") "error: Gauss-Legendre quadrature formulae handle orders up to 7 on triangles"); } } + + 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_tetra = [](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_tetra](auto f, auto quadrature_formula, const double exact_value) { + using R3 = TinyVector<3>; + + const R3 A{-1, -1, -1}; + const R3 B{+1, -1, -1}; + const R3 C{-1, +1, -1}; + const R3 D{-1, -1, +1}; + + const R3 M_AB = 0.5 * (A + B); + const R3 M_AC = 0.5 * (A + C); + const R3 M_AD = 0.5 * (A + D); + const R3 M_BC = 0.5 * (B + C); + const R3 M_BD = 0.5 * (B + D); + const R3 M_CD = 0.5 * (C + D); + + const double int_T_hat = integrate(f, quadrature_formula); + const double int_refined // + = integrate_on_tetra(f, quadrature_formula, {A, M_AB, M_AC, M_AD}) + + integrate_on_tetra(f, quadrature_formula, {B, M_AB, M_BD, M_BC}) + + integrate_on_tetra(f, quadrature_formula, {C, M_AC, M_BC, M_CD}) + + integrate_on_tetra(f, quadrature_formula, {D, M_AD, M_CD, M_BD}) + + integrate_on_tetra(f, quadrature_formula, {M_BD, M_AC, M_AD, M_CD}) + + integrate_on_tetra(f, quadrature_formula, {M_AB, M_AC, M_AD, M_BD}) + + integrate_on_tetra(f, quadrature_formula, {M_BC, M_AB, M_BD, M_AC}) + + integrate_on_tetra(f, quadrature_formula, {M_BC, M_AC, M_BD, M_CD}); + + return -std::log((int_refined - exact_value) / (int_T_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("order 2") + { + SimplicialGaussLegendreQuadrature<3> l1(1); + + REQUIRE(l1.numberOfPoints() == 1); + + REQUIRE(integrate(p0, l1) == Catch::Approx(16. / 3)); + REQUIRE(integrate(p1, l1) == Catch::Approx(-16. / 3)); + REQUIRE(integrate(p2, l1) != Catch::Approx(-47. / 3)); + + REQUIRE(get_order(p2, l1, -47. / 3) >= Catch::Approx(2)); + } + + SECTION("order 3") + { + SimplicialGaussLegendreQuadrature<3> l2(2); + + REQUIRE(l2.numberOfPoints() == 4); + + REQUIRE(integrate(p0, l2) == Catch::Approx(16. / 3)); + REQUIRE(integrate(p1, l2) == Catch::Approx(-16. / 3)); + REQUIRE(integrate(p2, l2) == Catch::Approx(-47. / 3)); + REQUIRE(integrate(p3, l2) != Catch::Approx(557. / 15)); + + REQUIRE(get_order(p3, l2, 557. / 15) >= Catch::Approx(3)); + } + + SECTION("order 4") + { + SimplicialGaussLegendreQuadrature<3> l3(3); + + REQUIRE(l3.numberOfPoints() == 5); + + REQUIRE(integrate(p0, l3) == Catch::Approx(16. / 3)); + REQUIRE(integrate(p1, l3) == Catch::Approx(-16. / 3)); + REQUIRE(integrate(p2, l3) == Catch::Approx(-47. / 3)); + REQUIRE(integrate(p3, l3) == Catch::Approx(557. / 15)); + REQUIRE(integrate(p4, l3) != Catch::Approx(4499. / 1575)); + } + + SECTION("order 5") + { + SimplicialGaussLegendreQuadrature<3> l4(4); + + REQUIRE(l4.numberOfPoints() == 11); + + REQUIRE(integrate(p0, l4) == Catch::Approx(16. / 3)); + REQUIRE(integrate(p1, l4) == Catch::Approx(-16. / 3)); + REQUIRE(integrate(p2, l4) == Catch::Approx(-47. / 3)); + REQUIRE(integrate(p3, l4) == Catch::Approx(557. / 15)); + REQUIRE(integrate(p4, l4) == Catch::Approx(4499. / 1575)); + REQUIRE(integrate(p5, l4) != Catch::Approx(143443. / 7875)); + + REQUIRE(get_order(p5, l4, 143443. / 7875) >= Catch::Approx(5)); + } + + SECTION("order 6") + { + SimplicialGaussLegendreQuadrature<3> l5(5); + + REQUIRE(l5.numberOfPoints() == 14); + + REQUIRE(integrate(p0, l5) == Catch::Approx(16. / 3)); + REQUIRE(integrate(p1, l5) == Catch::Approx(-16. / 3)); + REQUIRE(integrate(p2, l5) == Catch::Approx(-47. / 3)); + REQUIRE(integrate(p3, l5) == Catch::Approx(557. / 15)); + REQUIRE(integrate(p4, l5) == Catch::Approx(4499. / 1575)); + REQUIRE(integrate(p5, l5) == Catch::Approx(143443. / 7875)); + REQUIRE(integrate(p6, l5) != Catch::Approx(-75773. / 2581974)); + } + + SECTION("order 7") + { + SimplicialGaussLegendreQuadrature<3> l6(6); + + REQUIRE(l6.numberOfPoints() == 24); + + REQUIRE(integrate(p0, l6) == Catch::Approx(16. / 3)); + REQUIRE(integrate(p1, l6) == Catch::Approx(-16. / 3)); + REQUIRE(integrate(p2, l6) == Catch::Approx(-47. / 3)); + REQUIRE(integrate(p3, l6) == Catch::Approx(557. / 15)); + REQUIRE(integrate(p4, l6) == Catch::Approx(4499. / 1575)); + REQUIRE(integrate(p5, l6) == Catch::Approx(143443. / 7875)); + REQUIRE(integrate(p6, l6) == Catch::Approx(-75773. / 2581974)); + REQUIRE(integrate(p7, l6) != Catch::Approx(86951548. / 32274675)); + + REQUIRE(get_order(p7, l6, 86951548. / 32274675) >= Catch::Approx(7)); + } + + SECTION("order 8") + { + SimplicialGaussLegendreQuadrature<3> l7(7); + + REQUIRE(l7.numberOfPoints() == 31); + + REQUIRE(integrate(p0, l7) == Catch::Approx(16. / 3)); + REQUIRE(integrate(p1, l7) == Catch::Approx(-16. / 3)); + REQUIRE(integrate(p2, l7) == Catch::Approx(-47. / 3)); + REQUIRE(integrate(p3, l7) == Catch::Approx(557. / 15)); + REQUIRE(integrate(p4, l7) == Catch::Approx(4499. / 1575)); + REQUIRE(integrate(p5, l7) == Catch::Approx(143443. / 7875)); + REQUIRE(integrate(p6, l7) == Catch::Approx(-75773. / 2581974)); + REQUIRE(integrate(p7, l7) == Catch::Approx(86951548. / 32274675)); + + REQUIRE(get_order(p8, l7, -863556317. / 322746750) == Catch::Approx(8).margin(0.01)); + } + + SECTION("not implemented formulae") + { + REQUIRE_THROWS_WITH(SimplicialGaussLegendreQuadrature<3>(8), + "error: Gauss-Legendre quadrature formulae handle orders up to 7 on tetrahedra"); + } + } }