RandLib 2.0.0
Probability distributions, fitting, and heavy-tail tools for C++20
Loading...
Searching...
No Matches
quadrature.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <concepts>
6#include <complex>
7#include <limits>
8#include <numbers>
9#include <stdexcept>
10#include <type_traits>
11#include <utility>
12#include <vector>
13
14namespace randlib::math
15{
16
17namespace detail
18{
19
20template <std::floating_point Real>
21std::pair<std::complex<Real>, Real> accelerateOscillatorySums(const std::vector<std::complex<Real>> &partialSums)
22{
23 std::vector<std::complex<Real>> older(partialSums.size() + 1);
24 std::vector<std::complex<Real>> previous = partialSums;
25 std::complex<Real> best = partialSums.back();
26 std::complex<Real> lastEstimate = best;
27 Real bestDifference = std::numeric_limits<Real>::infinity();
28 const int maxOrder = std::min<int>(20, static_cast<int>(partialSums.size()) - 1);
29 for (int order = 1; order <= maxOrder; ++order) {
30 std::vector<std::complex<Real>> current(previous.size() - 1);
31 for (std::size_t i = 0; i < current.size(); ++i) {
32 const std::complex<Real> difference = previous[i + 1] - previous[i];
33 const Real scale = std::max({Real{1}, std::abs(previous[i]), std::abs(previous[i + 1])});
34 if (std::abs(difference) <= 20 * std::numeric_limits<Real>::epsilon() * scale)
35 current[i] = std::complex<Real>(std::numeric_limits<Real>::infinity(), 0);
36 else
37 current[i] = older[i + 1] + Real{1} / difference;
38 }
39 if (order % 2 == 0) {
40 const std::complex<Real> estimate = current.back();
41 if (std::isfinite(estimate.real()) && std::isfinite(estimate.imag())) {
42 const Real difference = std::abs(estimate - lastEstimate);
43 if (difference < bestDifference) {
44 best = estimate;
45 bestDifference = difference;
46 }
47 lastEstimate = estimate;
48 }
49 }
50 older = std::move(previous);
51 previous = std::move(current);
52 }
53 return {best, bestDifference};
54}
55
56template <std::floating_point Real>
57struct Segment
58{
59 Real a, b, estimate, error;
60 bool operator<(const Segment &other) const { return error < other.error; }
61};
62
71template <std::floating_point Real, class F>
72Segment<Real> gaussKronrod21(const F &f, Real a, Real b, Real &absEstimate)
73{
74 static constexpr long double xgk[11] = {0.995657163025808080735527280689003L,
75 0.973906528517171720077964012084452L,
76 0.930157491355708226001207180059508L,
77 0.865063366688984510732096688423493L,
78 0.780817726586416897063717578345042L,
79 0.679409568299024406234327365114874L,
80 0.562757134668604683339000099272694L,
81 0.433395394129247190799265943165784L,
82 0.294392862701460198131126603103866L,
83 0.148874338981631210884826001129720L,
84 0.0L};
85 static constexpr long double wgk[11] = {0.011694638867371874278064396062192L, 0.032558162307964727478818972459390L,
86 0.054755896574351996031381300244580L, 0.075039674810919952767043140916190L,
87 0.093125454583697605535065465083366L, 0.109387158802297641899210590325805L,
88 0.123491976262065851077208980292818L, 0.134709217311473325928054001771707L,
89 0.142775938577060080797094273138717L, 0.147739104901338491374841515972068L,
90 0.149445554002916905664936468389821L};
91 static constexpr long double wg[5] = {0.066671344308688137593568809893332L, 0.149451349150580593145776339657697L,
92 0.219086362515982043995534934228163L, 0.269266719309996355091226921569469L,
93 0.295524224714752870173892994651338L};
94 const Real center = (a + b) / 2, half = (b - a) / 2;
95 const Real fc = f(center);
96 Real kronrod = static_cast<Real>(wgk[10]) * fc, gauss = 0, absSum = static_cast<Real>(wgk[10]) * std::fabs(fc);
97 for (int j = 0; j < 10; ++j) {
98 const Real dx = half * static_cast<Real>(xgk[j]);
99 const Real f1 = f(center - dx), f2 = f(center + dx);
100 kronrod += static_cast<Real>(wgk[j]) * (f1 + f2);
101 absSum += static_cast<Real>(wgk[j]) * (std::fabs(f1) + std::fabs(f2));
102 if (j % 2 == 1)
103 gauss += static_cast<Real>(wg[j / 2]) * (f1 + f2);
104 }
105 absEstimate += std::fabs(half) * absSum;
106 return {a, b, kronrod * half, std::fabs((kronrod - gauss) * half)};
107}
108
110template <std::floating_point Real, class F>
111Real integrateFinite(const F &f, Real a, Real b, Real tolerance, int maxSubdivisions)
112{
116 Real absEstimate = 0;
117 std::vector<Segment<Real>> heap{gaussKronrod21(f, a, b, absEstimate)};
118 Real estimate = heap.front().estimate, error = heap.front().error;
121 int stalledBisections = 0, growingBisections = 0;
122 for (int i = 0; i < maxSubdivisions && stalledBisections < 6 && growingBisections < 20; ++i) {
123 Real limit = std::max(tolerance * std::fabs(estimate), 50 * std::numeric_limits<Real>::epsilon() * absEstimate);
124 if (!(error > limit) || !std::isfinite(estimate))
125 break;
126 std::pop_heap(heap.begin(), heap.end());
127 Segment<Real> worst = heap.back();
128 heap.pop_back();
129 Real middle = (worst.a + worst.b) / 2;
130 if (!(middle > worst.a && middle < worst.b)) {
131 heap.push_back(worst);
132 std::push_heap(heap.begin(), heap.end());
133 break;
134 }
135 Real newAbs = 0;
136 Segment<Real> left = gaussKronrod21(f, worst.a, middle, newAbs);
137 Segment<Real> right = gaussKronrod21(f, middle, worst.b, newAbs);
138 const Real childEstimate = left.estimate + right.estimate, childError = left.error + right.error;
139 if (left.error > 0 && right.error > 0) {
140 if (std::fabs(worst.estimate - childEstimate) <= Real(1e-5) * std::fabs(childEstimate) &&
141 childError >= Real(0.99) * worst.error)
142 ++stalledBisections;
143 if (i >= 10 && childError > worst.error)
144 ++growingBisections;
145 }
146 estimate += left.estimate + right.estimate - worst.estimate;
147 error += left.error + right.error - worst.error;
148 heap.push_back(left);
149 std::push_heap(heap.begin(), heap.end());
150 heap.push_back(right);
151 std::push_heap(heap.begin(), heap.end());
152 }
154 Real sum = 0;
155 for (const Segment<Real> &segment : heap)
156 sum += segment.estimate;
157 return sum;
158}
159
160template <std::floating_point Real, class F>
161Real evaluateTanhSinh(const F &f, Real x, Real endpointDistance)
162{
163 if constexpr (std::invocable<F, Real, Real>)
164 return static_cast<Real>(f(x, endpointDistance));
165 else
166 return static_cast<Real>(f(x));
167}
168
169template <std::floating_point Real, class F>
170std::pair<Real, Real> tanhSinhLevel(const F &f, Real a, Real b, Real step)
171{
172 const Real halfWidth = (b - a) / 2;
173 const Real center = a + halfWidth;
174 const Real halfPi = std::numbers::pi_v<Real> / 2;
175 const Real centerWeight = halfWidth * halfPi;
176 const Real centerValue = evaluateTanhSinh<Real>(f, center, b - center);
177 Real sum = centerWeight * centerValue;
178 Real absoluteSum = centerWeight * std::fabs(centerValue);
179
180 for (int k = 1; k < 32768; ++k) {
181 const Real t = step * static_cast<Real>(k);
182 const Real coshT = std::cosh(t);
183 const Real transformed = halfPi * std::sinh(t);
184 const Real q = std::exp(-2 * transformed);
185 if (q == 0)
186 break;
187 const Real onePlusQ = 1 + q;
188 const Real endpointDistance = (b - a) * q / onePlusQ;
189 if (endpointDistance < std::numeric_limits<Real>::min())
190 break;
191 const Real weight = halfWidth * halfPi * coshT * 4 * q / (onePlusQ * onePlusQ);
192 if (weight == 0)
193 break;
194
195 const Real left = a + endpointDistance;
196 const Real right = b - endpointDistance;
197 Real leftValue = 0, rightValue = 0;
198 if constexpr (std::invocable<F, Real, Real>) {
199 leftValue = evaluateTanhSinh<Real>(f, left, -endpointDistance);
200 rightValue = evaluateTanhSinh<Real>(f, right, endpointDistance);
201 } else {
202 if (left != a)
203 leftValue = evaluateTanhSinh<Real>(f, left, -endpointDistance);
204 if (right != b)
205 rightValue = evaluateTanhSinh<Real>(f, right, endpointDistance);
206 }
207 sum += weight * (leftValue + rightValue);
208 absoluteSum += weight * (std::fabs(leftValue) + std::fabs(rightValue));
209 }
210 return {step * sum, step * absoluteSum};
211}
212
213} // namespace detail
214
225template <std::floating_point Real = double, class F>
226Real integrate(const F &f, std::type_identity_t<Real> a, std::type_identity_t<Real> b,
227 std::type_identity_t<Real> tolerance = 1e-11, int maxSubdivisions = 512)
228{
229 if (a > b)
230 return -integrate<Real>(f, b, a, tolerance, maxSubdivisions);
231 if (a == b)
232 return 0;
233 const bool lowerInfinite = std::isinf(a), upperInfinite = std::isinf(b);
234 if (!lowerInfinite && !upperInfinite)
235 return detail::integrateFinite<Real>(f, a, b, tolerance, maxSubdivisions);
236 auto distance = [](Real t) { return (1 - t) / t; };
237 if (lowerInfinite && upperInfinite) {
239 [&f, &distance](Real t) {
240 Real x = distance(t);
241 return (f(x) + f(-x)) / (t * t);
242 },
243 0, 1, tolerance, maxSubdivisions);
244 }
245 if (upperInfinite) {
246 return detail::integrateFinite<Real>([&f, &distance, a](Real t) { return f(a + distance(t)) / (t * t); }, 0, 1,
247 tolerance, maxSubdivisions);
248 }
249 return detail::integrateFinite<Real>([&f, &distance, b](Real t) { return f(b - distance(t)) / (t * t); }, 0, 1,
250 tolerance, maxSubdivisions);
251}
252
265template <std::floating_point Real = double, class F>
266Real integrateTanhSinh(const F &f, std::type_identity_t<Real> a, std::type_identity_t<Real> b,
267 std::type_identity_t<Real> tolerance = 1e-11, int maxRefinements = 12)
268{
269 if (!std::isfinite(a) || !std::isfinite(b))
270 throw std::invalid_argument("tanh-sinh quadrature requires finite limits");
271 if (a > b)
272 return -integrateTanhSinh<Real>(f, b, a, tolerance, maxRefinements);
273 if (a == b)
274 return 0;
275 if (!(tolerance > 0) || maxRefinements < 1)
276 throw std::invalid_argument("tanh-sinh quadrature requires a positive tolerance and refinement count");
277
278 Real previous = 0;
279 for (int level = 0; level <= maxRefinements; ++level) {
280 const Real step = std::ldexp(Real{1}, -level);
281 const auto [estimate, absoluteEstimate] = detail::tanhSinhLevel<Real>(f, a, b, step);
282 const Real roundoff = 50 * std::numeric_limits<Real>::epsilon() * absoluteEstimate;
283 const Real target = std::max(tolerance * absoluteEstimate, roundoff);
284 if (level >= 2 && std::fabs(estimate - previous) <= target)
285 return estimate;
286 previous = estimate;
287 }
288 return previous;
289}
290
291namespace detail
292{
293
294template <std::floating_point Real, class F, class TailMass>
295std::complex<Real> integrateHalfPeriods(const F &amplitude, Real frequency, Real lower, Real tolerance, int maxCycles,
296 const TailMass &tailMass)
297{
298 if (!(frequency > 0) || !std::isfinite(frequency) || !std::isfinite(lower) || !(tolerance > 0) || maxCycles < 4)
299 throw std::invalid_argument("oscillatory quadrature requires finite limits, positive frequency and tolerance");
300
301 const Real halfPeriod = std::numbers::pi_v<Real> / frequency;
302 std::vector<std::complex<Real>> partialSums;
303 partialSums.reserve(maxCycles);
304 std::complex<Real> sum = 0;
305 int negligibleCycles = 0;
306 for (int cycle = 0; cycle < maxCycles; ++cycle) {
307 const Real a = lower + static_cast<Real>(cycle) * halfPeriod;
308 const Real b = a + halfPeriod;
309 const Real cosine = integrate<Real>(
310 [&amplitude, frequency](Real x) { return static_cast<Real>(amplitude(x)) * std::cos(frequency * x); }, a, b,
311 tolerance * 0.1, 128);
312 const Real sine = integrate<Real>(
313 [&amplitude, frequency](Real x) { return static_cast<Real>(amplitude(x)) * std::sin(frequency * x); }, a, b,
314 tolerance * 0.1, 128);
315 const std::complex<Real> contribution(cosine, sine);
316 sum += contribution;
317 partialSums.push_back(sum);
318
321 const Real remaining = static_cast<Real>(tailMass(b));
322 if (std::isnan(remaining)) {
323 negligibleCycles = (std::abs(contribution) <= tolerance) ? negligibleCycles + 1 : 0;
324 if (negligibleCycles >= 3)
325 return sum;
326 } else if (remaining <= tolerance) {
327 return sum;
328 }
329 if (!(remaining > Real{0.5}) && partialSums.size() >= 12 && partialSums.size() % 4 == 0) {
330 const auto [estimate, error] = accelerateOscillatorySums(partialSums);
331 if (error <= tolerance * std::max(Real{1}, std::abs(estimate)))
332 return estimate;
333 }
334 }
335 return accelerateOscillatorySums(partialSums).first;
336}
337
338} // namespace detail
339
351template <std::floating_point Real = double, class F>
352std::complex<Real> integrateOscillatory(const F &amplitude, std::type_identity_t<Real> frequency,
353 std::type_identity_t<Real> lower = 0,
354 std::type_identity_t<Real> tolerance = 1e-10, int maxCycles = 512)
355{
356 return detail::integrateHalfPeriods<Real>(amplitude, frequency, lower, tolerance, maxCycles,
357 [](Real) { return std::numeric_limits<Real>::quiet_NaN(); });
358}
359
371template <std::floating_point Real = double, class F, class TailMass>
372 requires std::invocable<const TailMass &, Real>
373std::complex<Real> integrateOscillatory(const F &amplitude, std::type_identity_t<Real> frequency,
374 std::type_identity_t<Real> lower, const TailMass &tailMass,
375 std::type_identity_t<Real> tolerance = 1e-10, int maxCycles = 512)
376{
377 return detail::integrateHalfPeriods<Real>(amplitude, frequency, lower, tolerance, maxCycles, tailMass);
378}
379
380} // namespace randlib::math
Real standardNormal(G &g)
Ziggurat algorithm of Marsaglia and Tsang with 256 layers and an explicit sign bit.
Definition variates.hpp:45
std::complex< Real > integrateHalfPeriods(const F &amplitude, Real frequency, Real lower, Real tolerance, int maxCycles, const TailMass &tailMass)
Definition quadrature.hpp:295
Real evaluateTanhSinh(const F &f, Real x, Real endpointDistance)
Definition quadrature.hpp:161
std::pair< Real, Real > tanhSinhLevel(const F &f, Real a, Real b, Real step)
Definition quadrature.hpp:170
Segment< Real > gaussKronrod21(const F &f, Real a, Real b, Real &absEstimate)
21-point Gauss-Kronrod rule with its embedded 10-point Gauss rule (QUADPACK qk21)
Definition quadrature.hpp:72
Real integrateFinite(const F &f, Real a, Real b, Real tolerance, int maxSubdivisions)
Global adaptive scheme (QUADPACK QAG) on a finite interval.
Definition quadrature.hpp:111
std::pair< std::complex< Real >, Real > accelerateOscillatorySums(const std::vector< std::complex< Real > > &partialSums)
Definition quadrature.hpp:21
Beta-related functions.
Definition math/beta.hpp:14
Real integrateTanhSinh(const F &f, std::type_identity_t< Real > a, std::type_identity_t< Real > b, std::type_identity_t< Real > tolerance=1e-11, int maxRefinements=12)
Double-exponential tanh-sinh quadrature on a finite interval.
Definition quadrature.hpp:266
std::complex< Real > integrateOscillatory(const F &amplitude, std::type_identity_t< Real > frequency, std::type_identity_t< Real > lower=0, std::type_identity_t< Real > tolerance=1e-10, int maxCycles=512)
QAWF-style Fourier integral on a semi-infinite interval.
Definition quadrature.hpp:352
Real integrate(const F &f, std::type_identity_t< Real > a, std::type_identity_t< Real > b, std::type_identity_t< Real > tolerance=1e-11, int maxSubdivisions=512)
Global adaptive Gauss-Kronrod quadrature (QUADPACK QAG).
Definition quadrature.hpp:226
Definition quadrature.hpp:58
Real a
Definition quadrature.hpp:59
bool operator<(const Segment &other) const
Definition quadrature.hpp:60
Real error
Definition quadrature.hpp:59
Real estimate
Definition quadrature.hpp:59
Real b
Definition quadrature.hpp:59