RandLib 2.0.0
Probability distributions, fitting, and heavy-tail tools for C++20
Loading...
Searching...
No Matches
math/beta.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <concepts>
4#include <type_traits>
5
7
8namespace randlib
9{
10
12
13namespace math
14{
15
22template <std::floating_point Real>
23Real logBeta(Real a, std::type_identity_t<Real> b);
24
25template <typename Left, typename Right>
26 requires(std::is_arithmetic_v<Left> && std::is_arithmetic_v<Right> &&
27 (std::integral<Left> || std::integral<Right> || !std::same_as<Left, Right>))
28auto logBeta(Left a, Right b) -> std::common_type_t<double, Left, Right>;
29
40template <std::floating_point Real>
41Real ibeta(Real x, std::type_identity_t<Real> a, std::type_identity_t<Real> b, std::type_identity_t<Real> logBetaFun,
42 std::type_identity_t<Real> logX, std::type_identity_t<Real> log1mX);
43
51template <std::floating_point Real>
52Real ibeta(Real x, std::type_identity_t<Real> a, std::type_identity_t<Real> b);
53
54} // namespace math
55
56} // namespace randlib
57
64
65namespace randlib
66{
67
68namespace math
69{
70
71template <std::floating_point Real>
72inline Real logBeta(Real a, std::type_identity_t<Real> b)
73{
74 if (a <= 0 || b <= 0)
75 throw std::invalid_argument("Parameters (a, b) of beta function should be both positive");
77 using Work = std::conditional_t<(sizeof(Real) < sizeof(long double)), long double, Real>;
78 Work p = std::min(static_cast<Work>(a), static_cast<Work>(b));
79 Work q = std::max(static_cast<Work>(a), static_cast<Work>(b));
80 if (p >= 10) {
81 Work corr = lgammaCorrection(p) + lgammaCorrection(q) - lgammaCorrection(p + q);
82 Work result = -Work{0.5} * std::log(q) +
83 Work{0.5} * static_cast<Work>(randlib::consts::ln2 + randlib::consts::lnpi) + corr +
84 (p - Work{0.5}) * std::log(p / (p + q)) + q * std::log1p(-p / (p + q));
85 return static_cast<Real>(result);
86 }
87 if (q >= 10) {
88 Work corr = lgammaCorrection(q) - lgammaCorrection(p + q);
89 Work result = std::lgamma(p) + corr + p - p * std::log(p + q) + (q - Work{0.5}) * std::log1p(-p / (p + q));
90 return static_cast<Real>(result);
91 }
92 return static_cast<Real>(std::log(std::tgamma(p) * (std::tgamma(q) / std::tgamma(p + q))));
93}
94
95template <typename Left, typename Right>
96 requires(std::is_arithmetic_v<Left> && std::is_arithmetic_v<Right> &&
97 (std::integral<Left> || std::integral<Right> || !std::same_as<Left, Right>))
98inline auto logBeta(Left a, Right b) -> std::common_type_t<double, Left, Right>
99{
100 using Real = std::common_type_t<double, Left, Right>;
101 return logBeta(static_cast<Real>(a), static_cast<Real>(b));
102}
103
104template <std::floating_point Real>
105inline Real ibetaPowerSeries1(Real x, Real a, Real b, Real logBetaFun, Real logX, Real log1mX)
106{
107 Real addon = 1.0, sum = 0.0;
109 Real u = (x > 1e-5) ? -x / (1.0 - x) : -std::exp(logX - log1mX);
110 int n = 1;
111 do {
112 addon *= (n - b) * u / (a + n);
113 sum += addon;
114 ++n;
115 } while (std::fabs(addon) > math::detail::minimumTolerance * std::fabs(sum));
116 Real y = a * logX;
117 y += (b - 1) * log1mX;
118 y -= logBetaFun;
119 y += std::log1p(sum);
120 return std::exp(y) / a;
121}
122
123template <std::floating_point Real>
124inline Real ibetacontinuedFraction1(Real x, Real a, Real b, int number, Real logBetaFun, Real logX, Real log1mX)
125{
126 int n = number;
128 Real u = (x > 1e-5) ? x / (1.0 - x) : std::exp(logX - log1mX);
129 Real frac = 0.0;
130 while (n > 1) {
131 Real cn = (b - n + 1) / (a + n - 1) * u;
132 frac = cn / (1 + cn - frac);
133 --n;
134 }
135 Real c1 = b / a;
136 Real F = c1 / (1 + c1 - frac);
137 Real y = a * logX;
138 y += (b - 1) * log1mX;
139 y -= logBetaFun;
140 y = std::exp(y) / b;
141 y *= F / (1.0 - F);
142 return y;
143}
144
145template <std::floating_point Real>
146inline Real ibetaPowerSeries2(Real x, Real a, Real b, Real logBetaFun, Real logX, Real log1mX)
147{
148 Real addon = 1.0, sum = 0.0;
149 int n = 0;
150 do {
151 addon *= (n + a + b) * x / (a + n + 1);
152 sum += addon;
153 ++n;
154 } while (std::fabs(addon) > math::detail::minimumTolerance * std::fabs(sum));
155 Real y = a * logX;
156 y += b * log1mX;
157 y -= logBetaFun;
158 y += std::log1p(sum);
159 return std::exp(y) / a;
160}
161
162template <std::floating_point Real>
163inline Real ibetaContinuedFraction2(Real x, Real a, Real b, int number, Real logBetaFun, Real logX, Real log1mX)
164{
165 int n = number;
166 Real frac = 0.0;
167 while (n > 1) {
168 Real cn = (a + b + n - 2) / (a + n - 1) * x;
169 frac = cn / (1 + cn - frac);
170 --n;
171 }
172 Real c1 = (a + b - 1) / a;
173 Real F = c1 / (1 + c1 - frac);
174 Real y = a * logX;
175 y += b * log1mX;
176 y -= logBetaFun;
177 y = std::exp(y) / (a + b - 1);
178 y *= F / (1.0 - F);
179 return y;
180}
181
184template <std::floating_point Real>
185inline Real ibetaContinuedFractionLentz(Real x, Real a, Real b, Real logBetaFun, Real logX, Real log1mX)
186{
187 constexpr Real epsilon = std::numeric_limits<Real>::epsilon();
188 constexpr Real tiny = std::numeric_limits<Real>::min() / epsilon;
189 auto guard = [](Real value) { return (std::fabs(value) < tiny) ? tiny : value; };
190 Real c = 1, d = 1 / guard(1 - (a + b) * x / (a + 1));
191 Real fraction = d;
192 for (int m = 1; m < 1000000; ++m) {
193 const Real even = m * (b - m) * x / ((a + 2 * m - 1) * (a + 2 * m));
194 d = 1 / guard(1 + even * d);
195 c = guard(1 + even / c);
196 fraction *= d * c;
197 const Real odd = -(a + m) * (a + b + m) * x / ((a + 2 * m) * (a + 2 * m + 1));
198 d = 1 / guard(1 + odd * d);
199 c = guard(1 + odd / c);
200 const Real step = d * c;
201 fraction *= step;
202 if (std::fabs(step - 1) <= epsilon)
203 break;
204 }
205 return std::exp(a * logX + b * log1mX - logBetaFun) * fraction / a;
206}
207
208namespace detail
209{
210
213inline constexpr double IBETA_LARGEST_FINITE_SHAPE = 4096;
214
215} // namespace detail
216
218template <std::floating_point Real>
219inline Real ibetaLowerTail(Real x, Real a, Real b, Real logBetaFun, Real logX, Real log1mX)
220{
221 if (b == 1.0)
222 return std::exp(a * logX);
223 if (a == 1.0)
224 return -std::expm1(b * log1mX);
225
228 if (x < 0.45) {
229 if (b < 1)
230 return ibetaPowerSeries1(x, a, b, logBetaFun, logX, log1mX);
231 int number = static_cast<int>(std::floor(b));
233 Real ecd = ibetacontinuedFraction1(x, a, b, number, logBetaFun, logX, log1mX);
234 Real apn = a + static_cast<Real>(number), bmn = b - static_cast<Real>(number);
235 Real residue = (b == number) ? 0.0 : ibetaPowerSeries1(x, apn, bmn, logBeta(apn, bmn), logX, log1mX);
236 return ecd + residue;
237 }
238
239 if (b < 1)
240 return ibetaPowerSeries2(x, a, b, logBetaFun, logX, log1mX);
241 int number = static_cast<int>(std::floor(b));
243 Real ecd = ibetaContinuedFraction2(x, a, b, number, logBetaFun, logX, log1mX);
244 Real apn = a + static_cast<Real>(number);
245 Real residue = ibetaPowerSeries2(x, apn, b, logBeta(apn, b), logX, log1mX);
246 return ecd + residue;
247}
248
249template <std::floating_point Real>
250inline Real ibeta(Real x, std::type_identity_t<Real> a, std::type_identity_t<Real> b,
251 std::type_identity_t<Real> logBetaFun, std::type_identity_t<Real> logX,
252 std::type_identity_t<Real> log1mX)
253{
255 if (!(a > 0 && b > 0))
256 throw std::invalid_argument(
257 "Parameters (a, b) of incomplete beta function should be both positive, but they're equal to " +
258 std::to_string(a) + " and " + std::to_string(b) + " respectively");
259 if (!(x >= 0.0 && x <= 1.0))
260 throw std::invalid_argument(
261 "Argument x of incomplete beta function should be in interval [0, 1], but it's equal to " +
262 std::to_string(x));
264 if (x == 0.0 || logX == -INFINITY)
265 return 0.0;
266 if (log1mX == -INFINITY)
267 return 1.0;
268 const Real y = (x < 0.5) ? Real{1} - x : std::exp(log1mX);
269
273 if (std::max(a, b) > detail::IBETA_LARGEST_FINITE_SHAPE) {
274 constexpr Real TINY_ARGUMENT = 1e-4, DEVIATIONS = 4;
275 const Real c = (a + 1) / (a + b + 2);
276 const bool directExtends = x < TINY_ARGUMENT && x < c * (1 + DEVIATIONS / std::sqrt(a + 1));
277 const bool complementExtends = y < TINY_ARGUMENT && y < (1 - c) * (1 + DEVIATIONS / std::sqrt(b + 1));
278 const bool direct = (x < c) ? !complementExtends : directExtends;
279 if (direct)
280 return ibetaContinuedFractionLentz<Real>(x, a, b, logBetaFun, logX, log1mX);
281 return Real{1} - ibetaContinuedFractionLentz<Real>(y, b, a, logBetaFun, log1mX, logX);
282 }
283
289 constexpr Real SLOW_SERIES_DISTANCE = 1e-3;
290 const bool complement = (x > a / (a + b)) ? x >= SLOW_SERIES_DISTANCE : y < SLOW_SERIES_DISTANCE;
291 if (complement)
292 return Real{1} - ibetaLowerTail<Real>(y, b, a, logBetaFun, log1mX, logX);
293 return ibetaLowerTail<Real>(x, a, b, logBetaFun, logX, log1mX);
294}
295
296template <std::floating_point Real>
297inline Real ibeta(Real x, std::type_identity_t<Real> a, std::type_identity_t<Real> b)
298{
300 if (a <= 0 || b <= 0 || x < 0.0 || x > 1.0)
301 throw std::invalid_argument("Parameters (a, b) of incomplete beta function should be both positive");
302 if (x < 0.0 || x > 1.0)
303 throw std::invalid_argument("Argument x of incomplete beta function should be in interval [0, 1]");
304 if (x == 0.0)
305 return 0.0;
306 if (x == 1.0)
307 return 1.0;
308 if (b == 1.0)
309 return std::pow(x, a);
310 if (a == 1.0) {
311 Real y = b * std::log1p(-x);
312 return -std::expm1(y);
313 }
314 Real logBetaFun = logBeta(a, b);
315 Real logX = std::log(x), log1mX = std::log1p(-x);
316 return ibeta(x, a, b, logBetaFun, logX, log1mX);
317}
318
319} // namespace math
320
321} // namespace randlib
constexpr long double ln2
Definition constants.hpp:18
constexpr long double lnpi
Definition constants.hpp:21
constexpr double IBETA_LARGEST_FINITE_SHAPE
Beyond this size of a shape the finite continued fractions of ibetaLowerTail accumulate rounding erro...
Definition math/beta.hpp:213
constexpr long double minimumTolerance
Definition constants.hpp:44
Real ibetaLowerTail(Real x, Real a, Real b, Real logBetaFun, Real logX, Real log1mX)
I(x, a, b) for x not above the mean a / (a + b) and shapes up to IBETA_LARGEST_FINITE_SHAPE.
Definition math/beta.hpp:219
Real logBeta(Real a, std::type_identity_t< Real > b)
Calculate logarithm of beta function.
Definition math/beta.hpp:72
Real ibetacontinuedFraction1(Real x, Real a, Real b, int number, Real logBetaFun, Real logX, Real log1mX)
Definition math/beta.hpp:124
Real ibetaContinuedFractionLentz(Real x, Real a, Real b, Real logBetaFun, Real logX, Real log1mX)
Continued fraction of I(x, a, b) (DiDonato and Morris, also Numerical Recipes betacf) by the modified...
Definition math/beta.hpp:185
Real lgammaCorrection(Real x)
Definition special_functions.hpp:350
Real ibetaPowerSeries1(Real x, Real a, Real b, Real logBetaFun, Real logX, Real log1mX)
Definition math/beta.hpp:105
Real ibeta(Real x, std::type_identity_t< Real > a, std::type_identity_t< Real > b, std::type_identity_t< Real > logBetaFun, std::type_identity_t< Real > logX, std::type_identity_t< Real > log1mX)
Fast calculation of regularized beta function, using precalculated values.
Definition math/beta.hpp:250
Real ibetaPowerSeries2(Real x, Real a, Real b, Real logBetaFun, Real logX, Real log1mX)
Definition math/beta.hpp:146
Real ibetaContinuedFraction2(Real x, Real a, Real b, int number, Real logBetaFun, Real logX, Real log1mX)
Definition math/beta.hpp:163
Definition bivariate_normal.hpp:10