numerical-collection-cpp 0.10.0
A collection of algorithms in numerical analysis implemented in C++
Loading...
Searching...
No Matches
gauss_legendre_kronrod_integrator.h
Go to the documentation of this file.
1/*
2 * Copyright 2021 MusicScience37 (Kenta Kabashima)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
20#pragma once
21
22// IWYU pragma: no_include <__hash_table>
23// IWYU pragma: no_include <complex>
24
25#include <cmath>
26#include <cstddef>
27#include <limits>
28#include <type_traits>
29#include <unordered_set>
30#include <utility>
31#include <vector>
32
33#include <Eigen/Core>
34#include <Eigen/Eigenvalues>
35
42#include "num_collect/constants/half.h" // IWYU pragma: keep
43#include "num_collect/constants/one.h" // IWYU pragma: keep
44#include "num_collect/constants/two.h" // IWYU pragma: keep
45#include "num_collect/constants/zero.h" // IWYU pragma: keep
52
54
57 "num_collect::integration::gauss_legendre_kronrod_integrator");
58
65template <typename Signature>
67
75template <typename Result, base::concepts::real_scalar Variable>
76class gauss_legendre_kronrod_integrator<Result(Variable)>
77 : public logging::logging_mixin {
78public:
80 using variable_type = std::decay_t<Variable>;
81
83 using result_type = std::decay_t<Result>;
84
86 static constexpr index_type default_degree = 5;
87
94 index_type degree = default_degree)
95 : logging::logging_mixin(gauss_legendre_kronrod_integrator_tag),
96 degree_(degree) {
97 NUM_COLLECT_PRECONDITION(degree >= 1, this->logger(),
98 "Degree of Legendre function must be at least one.");
99 compute_parameters();
100 }
101
107 void prepare(index_type degree) {
108 NUM_COLLECT_PRECONDITION(degree >= 1, this->logger(),
109 "Degree of Legendre function must be at least one.");
110 degree_ = degree;
111 compute_parameters();
112 }
113
123 template <base::concepts::invocable_as<result_type(variable_type)> Function>
124 [[nodiscard]] auto integrate_once(
125 const Function& function, variable_type left, variable_type right) const
126 -> std::pair<result_type, result_type> {
127 const auto center = constants::half<variable_type> * (left + right);
128 const auto half_width = constants::half<variable_type> * (right - left);
129
130 result_type sum_gauss =
131 function(center) * constants::zero<variable_type>;
132 result_type sum_kronrod = sum_gauss;
133 for (index_type i = 0; i < nodes_gauss_.size(); ++i) {
134 const variable_type x = center + half_width * nodes_gauss_[i];
135 const result_type val = function(x);
136 sum_gauss += val * weights_gauss_[i];
137 sum_kronrod += val * weights_gauss_for_kronrod_[i];
138 }
139 for (index_type i = 0; i < nodes_kronrod_.size(); ++i) {
140 const variable_type x = center + half_width * nodes_kronrod_[i];
141 const result_type val = function(x);
142 sum_kronrod += val * weights_kronrod_[i];
143 }
144
145 sum_gauss *= half_width;
146 sum_kronrod *= half_width;
147 return {sum_gauss, sum_kronrod};
148 }
149
159 template <base::concepts::invocable_as<result_type(variable_type)> Function>
160 [[nodiscard]] auto integrate(const Function& function, variable_type left,
161 variable_type right) const -> result_type {
162 NUM_COLLECT_PRECONDITION(left < right, this->logger(),
163 "The boundaries of the range to integrate on must satisfy left < "
164 "right.");
165
166 const variable_type inv_width =
167 constants::one<variable_type> / (right - left);
168
169 result_type sum =
170 function(constants::half<variable_type> * (left + right)) *
172
173 constexpr auto stack_size = static_cast<std::size_t>(
174 std::numeric_limits<variable_type>::digits);
176 variable_type cur_left = left;
177 variable_type cur_right = right;
178
179 while (true) {
180 const auto [val_gauss, val_kronrod] =
181 integrate_once(function, cur_left, cur_right);
182 const variable_type val_norm = num_collect::norm(val_kronrod);
183 const variable_type error_norm =
184 num_collect::norm(val_gauss - val_kronrod);
185 using std::abs;
186 const auto div_rate = (cur_right - cur_left) * inv_width;
187 if ((error_norm < tol_abs_error_ * div_rate) ||
188 (error_norm < tol_rel_error_ * val_norm) ||
189 (div_rate < min_div_rate_)) {
190 sum += val_kronrod;
191 if (remaining_right.empty()) {
192 break;
193 }
194 cur_left = cur_right;
195 cur_right = remaining_right.top();
196 remaining_right.pop();
197 } else {
198 remaining_right.push(cur_right);
199 cur_right =
200 constants::half<variable_type> * (cur_left + cur_right);
201 }
202 }
203 return sum;
204 }
205
215 template <base::concepts::invocable_as<result_type(variable_type)> Function>
216 [[nodiscard]] auto operator()(const Function& function, variable_type left,
217 variable_type right) const -> result_type {
218 return integrate(function, left, right);
219 }
220
229 NUM_COLLECT_PRECONDITION(val > static_cast<variable_type>(0),
230 this->logger(),
231 "Tolerance of absolute error must be positive value.");
232 tol_abs_error_ = val;
233 return *this;
234 }
235
244 NUM_COLLECT_PRECONDITION(val > static_cast<variable_type>(0),
245 this->logger(),
246 "Tolerance of relative error must be positive value.");
247 tol_rel_error_ = val;
248 return *this;
249 }
250
259 val > std::numeric_limits<variable_type>::epsilon(), this->logger(),
260 "Minimum rate of division of integration region must be larger "
261 "than the machine epsilon.");
262 min_div_rate_ = val;
263 return *this;
264 }
265
266private:
268 using vector_type = Eigen::Matrix<variable_type, Eigen::Dynamic, 1>;
269
272 Eigen::Matrix<variable_type, Eigen::Dynamic, Eigen::Dynamic>;
273
278 const index_type n = degree_;
279 const index_type extended_size = n * 2 + 1;
280 vector_type a = vector_type::Zero(extended_size);
281 vector_type b = vector_type::Zero(extended_size);
282
284 for (index_type i = 1; i <= (3 * n + 1) / 2; ++i) {
285 auto temp = static_cast<variable_type>(i);
286 temp *= temp;
287 temp /= static_cast<variable_type>(2 * i + 1) *
288 static_cast<variable_type>(2 * i - 1);
289 b[i] = temp;
290 }
291 jacobi2gauss(a, b, degree_, nodes_gauss_, weights_gauss_);
292 NUM_COLLECT_ASSERT(nodes_gauss_.allFinite());
293 NUM_COLLECT_ASSERT(weights_gauss_.allFinite());
294
295 generate_jacobi_kronrod_matrix(a, b);
296 NUM_COLLECT_ASSERT(a.allFinite());
297 NUM_COLLECT_ASSERT(b.allFinite());
298 vector_type nodes_all;
299 vector_type weights_all;
300 jacobi2gauss(a, b, extended_size, nodes_all, weights_all);
301 NUM_COLLECT_ASSERT(nodes_all.allFinite());
302 NUM_COLLECT_ASSERT(weights_all.allFinite());
303
304 std::unordered_set<index_type> additional_nodes_index;
305 additional_nodes_index.reserve(
306 util::safe_cast<std::size_t>(extended_size));
307 for (index_type i = 0; i < extended_size; ++i) {
308 additional_nodes_index.insert(i);
309 }
310 weights_gauss_for_kronrod_ = vector_type::Zero(degree_);
311 for (index_type i = 0; i < degree_; ++i) {
312 variable_type min_dist =
313 std::numeric_limits<variable_type>::infinity();
314 index_type min_ind = -1;
315 for (index_type j : additional_nodes_index) {
316 using std::abs;
317 const variable_type dist = abs(nodes_gauss_[i] - nodes_all[j]);
318 if (dist < min_dist) {
319 min_dist = dist;
320 min_ind = j;
321 }
322 }
323 weights_gauss_for_kronrod_[i] = weights_all[min_ind];
324 additional_nodes_index.erase(min_ind);
325 }
326 nodes_kronrod_ = vector_type::Zero(n + 1);
327 weights_kronrod_ = vector_type::Zero(n + 1);
328 const auto additional_nodes_vec = std::vector<index_type>(
329 additional_nodes_index.begin(), additional_nodes_index.end());
330 for (std::size_t i = 0; i < additional_nodes_vec.size(); ++i) {
331 nodes_kronrod_[util::safe_cast<index_type>(i)] =
332 nodes_all[additional_nodes_vec[i]];
333 weights_kronrod_[util::safe_cast<index_type>(i)] =
334 weights_all[additional_nodes_vec[i]];
335 }
336 NUM_COLLECT_ASSERT(nodes_kronrod_.allFinite());
337 NUM_COLLECT_ASSERT(weights_kronrod_.allFinite());
338 }
339
348 const index_type n = degree_;
349 vector_type s = vector_type::Zero(n / 2 + 2);
350 vector_type t = vector_type::Zero(n / 2 + 2);
351 t[1] = b[n + 1];
352 for (index_type m = 0; m < n - 1; ++m) {
354 for (index_type k = (m + 1) / 2; k > 0; --k) {
355 index_type l = m - k;
356 u += (a[k + n + 1] - a[l]) * t[k + 1] + b[k + n + 1] * s[k] -
357 b[l] * s[k + 1];
358 s[k + 1] = u;
359 }
360 {
361 constexpr index_type k = 0;
362 index_type l = m - k;
363 u += (a[k + n + 1] - a[l]) * t[k + 1] + b[k + n + 1] * s[k] -
364 b[l] * s[k + 1];
365 s[k + 1] = u;
366 }
367 s.swap(t);
368 }
369 for (index_type j = n / 2; j > 0; --j) {
370 s[j + 1] = s[j];
371 }
372 s[1] = s[0];
373 for (index_type m = n - 1; m <= 2 * n - 3; ++m) {
375 index_type j = 0;
376 for (index_type k = m + 1 - n; k <= (m - 1) / 2; ++k) {
377 index_type l = m - k;
378 j = n - 1 - l;
379 u += -(a[k + n + 1] - a[l]) * t[j + 1] -
380 b[k + n + 1] * s[j + 1] + b[l] * s[j + 2];
381 s[j + 1] = u;
382 }
383 if (m % 2 == 0) {
384 index_type k = m / 2;
385 a[k + n + 1] =
386 a[k] + (s[j + 1] - b[k + n + 1] * s[j + 2]) / t[j + 2];
387 } else {
388 index_type k = (m + 1) / 2;
389 b[k + n + 1] = s[j + 1] / s[j + 2];
390 }
391 s.swap(t);
392 }
393 a[2 * n] = a[n - 1] - b[2 * n] * s[1] * t[1];
394 }
395
405 static void jacobi2gauss(const vector_type& a, const vector_type& b,
406 index_type size, vector_type& nodes, vector_type& weights) {
407 matrix_type jacobi = matrix_type::Zero(size, size);
408 jacobi(0, 0) = a[0];
409 for (index_type i = 1; i < size; ++i) {
410 jacobi(i, i) = a[i];
411 const variable_type temp = std::sqrt(b[i]);
412 jacobi(i - 1, i) = temp;
413 jacobi(i, i - 1) = temp;
414 }
415
416 Eigen::SelfAdjointEigenSolver<matrix_type> eig(
417 jacobi, Eigen::ComputeEigenvectors);
418
419 // reverse to get the results in ascending order of nodes
420 nodes = eig.eigenvalues();
421 weights = b[0] * eig.eigenvectors().row(0).transpose().cwiseAbs2();
422 }
423
426
428 vector_type nodes_gauss_{};
429
431 vector_type weights_gauss_{};
432
434 vector_type weights_gauss_for_kronrod_{};
435
437 vector_type nodes_kronrod_{};
438
440 vector_type weights_kronrod_{};
441
443 static constexpr variable_type default_tol_error =
444 std::numeric_limits<variable_type>::epsilon() *
445 static_cast<variable_type>(1e+4);
446
448 variable_type tol_abs_error_{default_tol_error};
449
451 variable_type tol_rel_error_{default_tol_error};
452
454 static constexpr variable_type default_min_div_rate =
455 std::numeric_limits<variable_type>::epsilon() *
456 static_cast<variable_type>(1e+4);
457
459 variable_type min_div_rate_{default_min_div_rate};
460};
461
462} // namespace num_collect::integration
Definition of assertion macros.
#define NUM_COLLECT_ASSERT(CONDITION)
Macro to check whether a condition is satisfied.
Definition assert.h:66
auto integrate(const Function &function, variable_type left, variable_type right) const -> result_type
Integrate a function adaptively.
Eigen::Matrix< variable_type, Eigen::Dynamic, 1 > vector_type
Type of vectors.
auto tol_rel_error(variable_type val) -> gauss_legendre_kronrod_integrator &
Set tolerance of relative error.
auto integrate_once(const Function &function, variable_type left, variable_type right) const -> std::pair< result_type, result_type >
Integrate a function and return two estimates.
void generate_jacobi_kronrod_matrix(vector_type &a, vector_type &b)
Generate Jacobi-Kronrod matrix as in a pseudo-code in the appendix of laurie1997.
auto tol_abs_error(variable_type val) -> gauss_legendre_kronrod_integrator &
Set tolerance of absolute error.
auto operator()(const Function &function, variable_type left, variable_type right) const -> result_type
Integrate a function.
static void jacobi2gauss(const vector_type &a, const vector_type &b, index_type size, vector_type &nodes, vector_type &weights)
Calculate Gauss quadrature from Jacobi matrix.
Eigen::Matrix< variable_type, Eigen::Dynamic, Eigen::Dynamic > matrix_type
Type of matrices.
auto min_div_rate(variable_type val) -> gauss_legendre_kronrod_integrator &
Set minimum rate of division of integration region.
Class to perform numerical adaptive integration with Gauss-Legendre-Kronrod formula in laurie1997.
Class of tags of logs without memory management.
Class to incorporate logging in algorithms.
Class of stacks using static arrays.
void pop() noexcept
Pop a value.
void push(const T &value)
Push a value.
auto top() const noexcept -> const T &
Get the top value.
auto empty() const noexcept -> bool
Check whether this stack is empty.
Definition of exceptions.
Definition of half.
Definition of index_type type.
Definition of invocable_as concept.
Definition of log_tag_view class.
Definition of macros for logging.
Definition of logging_mixin class.
std::ptrdiff_t index_type
Type of indices in this library.
Definition index_type.h:33
auto norm(const Matrix &matrix)
Calculate norm of a matrix.
Definition norm.h:39
constexpr T two
Value 2.
Definition two.h:30
constexpr T zero
Value 0.
Definition zero.h:30
constexpr T half
Value 0.5.
Definition half.h:30
constexpr T one
Value 1.
Definition one.h:30
Namespace of numerical integration.
constexpr auto gauss_legendre_kronrod_integrator_tag
Tag of gauss_legendre_kronrod_integrator.
auto safe_cast(const From &value) -> To
Cast safely.
Definition safe_cast.h:54
Definition of norm class.
Definition of one.
Definition of NUM_COLLECT_PRECONDITION macro.
#define NUM_COLLECT_PRECONDITION(CONDITION,...)
Check whether a precondition is satisfied and throw an exception if not.
Definition of real_scalar concept.
Definition of safe_cast function.
Definition of static_stack class.
Definition of two.
Definition of zero.