LehrFEM++ 1.0.0
A simple Finite Element Library for teaching
gauss_quadrature.cc
1
10#include "gauss_quadrature.h"
11
12#include <boost/math/special_functions/gamma.hpp>
13#include <boost/multiprecision/cpp_bin_float.hpp>
14
15namespace lf::quad {
16std::tuple<Eigen::VectorXd, Eigen::VectorXd> GaussLegendre(
17 unsigned num_points) {
18 LF_ASSERT_MSG(num_points > 0, "num_points must be positive.");
19
20 using scalar_t =
21 boost::multiprecision::number<boost::multiprecision::cpp_bin_float<
22 57, boost::multiprecision::digit_base_2>>;
23
24 static const scalar_t kPi = boost::math::constants::pi<scalar_t>();
25
26 Eigen::VectorXd points(num_points);
27 Eigen::VectorXd weights(num_points);
28
29 // the roots are symmetric in the interval, so we only have to find half of
30 // them
31 unsigned int m = (num_points + 1) / 2;
32
33 // approximation for the roots:
34 for (unsigned int i = 0; i < m; ++i) {
35 // initial guess for the i-th root:
36 scalar_t z = cos(kPi * (i + 0.75) / (num_points + 0.5));
37 scalar_t z1;
38 scalar_t pp;
39
40 // start of newton
41 do {
42 // calculate value of legendre polynomial at z using recurrence relation:
43 scalar_t p1 = 1.0;
44 scalar_t p2 = 0.0;
45 scalar_t p3;
46
47 for (unsigned int j = 0; j < num_points; ++j) {
48 p3 = p2;
49 p2 = p1;
50 p1 = ((2.0 * j + 1.) * z * p2 - j * p3) / (j + 1.);
51 }
52
53 // p1 is now the value of the legendre polynomial at z. Next we compute
54 // its derivative using a standard relation involving also p2, the
55 // polynomial of one lower order:
56 pp = num_points * (z * p1 - p2) / (z * z - 1.0);
57
58 z1 = z;
59 z = z1 - p1 / pp;
60 } while (abs(z - z1) > 1e-17);
61
62 points(i) = (0.5 * (1 - z)).convert_to<double>();
63 points(num_points - 1 - i) = (0.5 * (1 + z)).convert_to<double>();
64 weights(i) = (1. / ((1.0 - z * z) * pp * pp)).convert_to<double>();
65 weights(num_points - 1 - i) = weights(i);
66 }
67
68 return {std::move(points), std::move(weights)};
69}
70
71std::tuple<Eigen::VectorXd, Eigen::VectorXd> GaussJacobi(
72 quadDegree_t num_points, double alpha, double beta) {
73 LF_ASSERT_MSG(num_points > 0, "num_points must be positive.");
74 LF_ASSERT_MSG(alpha > -1, "alpha > -1 required");
75 LF_ASSERT_MSG(beta > -1, "beta > -1 required.");
76
77 using boost::math::lgamma;
78 using scalar_t =
79 boost::multiprecision::number<boost::multiprecision::cpp_bin_float<
80 57, boost::multiprecision::digit_base_2>>;
81
82 int MAX_IT = 10;
83
84 scalar_t alfbet;
85 scalar_t an;
86 scalar_t bn;
87 scalar_t r1;
88 scalar_t r2;
89 scalar_t r3;
90 scalar_t a;
91 scalar_t b;
92 scalar_t c;
93 scalar_t p1;
94 scalar_t p2;
95 scalar_t p3;
96 scalar_t pp;
97 scalar_t temp;
98 scalar_t z;
99 scalar_t z1;
100
101 std::vector<scalar_t> points(num_points);
102 Eigen::VectorXd weights(num_points);
103
104 // Make an initial guess for the zeros:
105 for (quadDegree_t i = 0; i < num_points; ++i) {
106 if (i == 0) {
107 // initial guess for the largest root
108 an = alpha / num_points;
109 bn = beta / num_points;
110 r1 = (1.0 + alpha) *
111 (2.78 / (4.0 + num_points * num_points) + 0.768 * an / num_points);
112 r2 = 1.0 + 1.48 * an + 0.96 * bn + 0.452 * an * an + 0.83 * an * bn;
113 z = 1.0 - r1 / r2;
114 } else if (i == 1) {
115 // initial guess for the second largest root
116 r1 = (4.1 + alpha) / ((1.0 + alpha) * (1.0 + 0.156 * alpha));
117 r2 = 1.0 + 0.06 * (num_points - 8.0) * (1.0 + 0.12 * alpha) / num_points;
118 r3 = 1.0 + 0.012 * beta * (1.0 + 0.25 * std::abs(alpha)) / num_points;
119 z -= (1.0 - z) * r1 * r2 * r3;
120 } else if (i == 2) {
121 // initial guess for the third largest root
122 r1 = (1.67 + 0.28 * alpha) / (1.0 + 0.37 * alpha);
123 r2 = 1.0 + 0.22 * (num_points - 8.0) / num_points;
124 r3 = 1.0 + 8.0 * beta / ((6.28 + beta) * num_points * num_points);
125 z -= (points[0] - z) * r1 * r2 * r3;
126 } else if (i == num_points - 2) {
127 // initial guess for the second smallest root
128 r1 = (1.0 + 0.235 * beta) / (0.766 + 0.119 * beta);
129 r2 = 1.0 / (1.0 + 0.639 * (num_points - 4.0) /
130 (1.0 + 0.71 * (num_points - 4.0)));
131 r3 = 1.0 /
132 (1.0 + 20.0 * alpha / ((7.5 + alpha) * num_points * num_points));
133 z += (z - points[num_points - 4]) * r1 * r2 * r3;
134 } else if (i == num_points - 1) {
135 // initial guess for the smallest root
136 r1 = (1.0 + 0.37 * beta) / (1.67 + 0.28 * beta);
137 r2 = 1.0 / (1.0 + 0.22 * (num_points - 8.0) / num_points);
138 r3 = 1.0 /
139 (1.0 + 8.0 * alpha / ((6.28 + alpha) * num_points * num_points));
140 z += (z - points[num_points - 3]) * r1 * r2 * r3;
141 } else {
142 // initial guess for the other points
143 z = 3.0 * points[i - 1] - 3.0 * points[i - 2] + points[i - 3];
144 }
145 alfbet = alpha + beta;
146 quadDegree_t its;
147 for (its = 1; its <= MAX_IT; ++its) {
148 // refinement by Newton's method
149 temp = 2.0 + alfbet;
150
151 // Start the recurrence with P_0 and P1 to avoid a division by zero when
152 // alpha * beta = 0 or -1
153 p1 = (alpha - beta + temp * z) / 2.0;
154 p2 = 1.0;
155 for (quadDegree_t j = 2; j <= num_points; ++j) {
156 p3 = p2;
157 p2 = p1;
158 temp = 2 * j + alfbet;
159 a = 2 * j * (j + alfbet) * (temp - 2.0);
160 b = (temp - 1.0) *
161 (alpha * alpha - beta * beta + temp * (temp - 2.0) * z);
162 c = 2.0 * (j - 1 + alpha) * (j - 1 + beta) * temp;
163 p1 = (b * p2 - c * p3) / a;
164 }
165 pp = (num_points * (alpha - beta - temp * z) * p1 +
166 2.0 * (num_points + alpha) * (num_points + beta) * p2) /
167 (temp * (1.0 - z * z));
168 // p1 is now the desired jacobia polynomial. We next compute pp, its
169 // derivative, by a standard relation involving p2, the polynomial of one
170 // lower order
171 z1 = z;
172 z = z1 - p1 / pp; // Newtons Formula
173 if (abs(z - z1) <= 1e-17) {
174 break;
175 }
176 }
177 LF_VERIFY_MSG(its <= MAX_IT, "too many iterations.");
178
179 points[i] = z;
180 weights(i) = (exp(lgamma(alpha + num_points) + lgamma(beta + num_points) -
181 lgamma(num_points + 1.) -
182 lgamma(double(num_points) + alfbet + 1.0)) *
183 temp * pow(2.0, alfbet) / (pp * p2))
184 .convert_to<double>();
185 }
186
187 Eigen::VectorXd points_result(num_points);
188 for (quadDegree_t i = 0; i < num_points; ++i) {
189 points_result(i) = points[i].convert_to<double>();
190 }
191
192 return {points_result, weights};
193}
194} // namespace lf::quad
Rules for numerical quadrature on reference entity shapes.
std::tuple< Eigen::VectorXd, Eigen::VectorXd > GaussJacobi(quadDegree_t num_points, double alpha, double beta)
Computes the quadrature points and weights for the interval [-1,1] of a Gauss-Jacobi quadrature rule ...
std::tuple< Eigen::VectorXd, Eigen::VectorXd > GaussLegendre(unsigned num_points)
unsigned int quadDegree_t
Definition: quad_rule.h:19