RandLib 2.0.0
Probability distributions, fitting, and heavy-tail tools for C++20
Loading...
Searching...
No Matches
stable_count.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <limits>
6#include <span>
7#include <stdexcept>
8#include <string>
9
14
15namespace randlib
16{
17
37template <typename RealType>
38class StableCountRand final : public ContinuousDistribution<RealType>
39{
40 RealType variate(detail::EngineRef g) const override { return (*this)(g); }
41 void sample(detail::EngineRef g, std::span<RealType> outputData) const override;
42
43 double alpha = 0.5;
44 double theta = 1;
45 double nu = 0;
46 double logTheta = 0;
47 double logNormalization = std::lgamma(3.0);
48 StableRand<RealType> positiveStable{0.5, 1.0, 0.5, 0.0};
49
50 [[nodiscard]] double standardLogDensity(double x) const;
51 [[nodiscard]] double wrightSeries(double x, bool integrated) const;
52 [[nodiscard]] double directStandardCdf(double x) const;
53 [[nodiscard]] double directStandardSurvival(double x) const;
54 [[nodiscard]] long double standardMoment(long double order) const;
55
56 RealType quantileImpl(double p) const override;
57 RealType quantileImpl1m(double p) const override;
58
59public:
60 StableCountRand(double exponent = 0.5, double scale = 1, double location = 0)
61 {
62 SetParameters(exponent, scale, location);
63 }
64
65 [[nodiscard]] std::string Name() const override;
66 [[nodiscard]] Support SupportType() const override { return Support::RightSemiFinite; }
67 [[nodiscard]] RealType MinValue() const override { return static_cast<RealType>(nu); }
68 [[nodiscard]] RealType MaxValue() const override { return std::numeric_limits<RealType>::infinity(); }
69
70 void SetParameters(double exponent, double scale = 1, double location = 0);
71 void SetExponent(double exponent) { SetParameters(exponent, theta, nu); }
72 void SetScale(double scale) { SetParameters(alpha, scale, nu); }
73 void SetLocation(double location) { SetParameters(alpha, theta, location); }
74
75 [[nodiscard]] double GetExponent() const noexcept { return alpha; }
76 [[nodiscard]] double GetScale() const noexcept { return theta; }
77 [[nodiscard]] double GetLocation() const noexcept { return nu; }
78
79 [[nodiscard]] double f(const RealType &x) const override;
80 [[nodiscard]] double logf(const RealType &x) const override;
81 [[nodiscard]] double F(const RealType &x) const override;
82 [[nodiscard]] double S(const RealType &x) const override;
83
84 template <std::uniform_random_bit_generator G>
85 [[nodiscard]] RealType operator()(G &g) const;
86
88 [[nodiscard]] long double StandardMoment(long double order) const { return standardMoment(order); }
90 [[nodiscard]] long double Moment(int order) const;
91 [[nodiscard]] long double Mean() const override { return Moment(1); }
92 [[nodiscard]] long double Variance() const override;
93 [[nodiscard]] long double SecondMoment() const override { return Moment(2); }
94 [[nodiscard]] long double ThirdMoment() const override { return Moment(3); }
95 [[nodiscard]] long double FourthMoment() const override { return Moment(4); }
96 [[nodiscard]] long double Skewness() const override;
97 [[nodiscard]] long double ExcessKurtosis() const override;
98 [[nodiscard]] RealType Mode() const override;
99};
100
101template <typename RealType>
103{
106 double logMode = 0;
107 math::findMin<double>([this](double u) { return -standardLogDensity(std::exp(u)); }, 0.0, logMode);
108 return static_cast<RealType>(nu + theta * std::exp(logMode));
109}
110
111template <typename RealType>
112void StableCountRand<RealType>::SetParameters(double exponent, double scale, double location)
113{
114 if (!(exponent > 0 && exponent < 1) || !std::isfinite(exponent))
115 throw std::invalid_argument("Stable Count distribution: exponent should be in the interval (0, 1)");
116 if (!(scale > 0) || !std::isfinite(scale))
117 throw std::invalid_argument("Stable Count distribution: scale should be finite and positive");
118 if (!std::isfinite(location))
119 throw std::invalid_argument("Stable Count distribution: location should be finite");
120
121 alpha = exponent;
122 theta = scale;
123 nu = location;
124 logTheta = std::log(theta);
125 logNormalization = std::lgamma(1 / alpha + 1);
126 const double stableScale = std::exp(std::log(std::cos(randlib::consts::half_pi * alpha)) / alpha);
127 positiveStable = StableRand<RealType>(alpha, 1.0, stableScale, 0.0, StableRand<RealType>::Parameterization::S1);
128}
129
130template <typename RealType>
132{
133 return "Stable Count(" + this->toStringWithPrecision(alpha) + ", " + this->toStringWithPrecision(theta) + ", " +
134 this->toStringWithPrecision(nu) + ")";
135}
136
137template <typename RealType>
139{
140 if (!(x > 0) || !std::isfinite(x))
141 return -INFINITY;
142 if (alpha == 0.5)
143 return 0.5 * std::log(x) - 0.25 * x - std::log(4.0) - 0.5 * static_cast<double>(randlib::consts::lnpi);
144 const double series = wrightSeries(x, false);
145 if (series > 0 && std::isfinite(series))
146 return std::log(series);
147 return positiveStable.logf(static_cast<RealType>(1 / x)) - logNormalization - std::log(x);
148}
149
150template <typename RealType>
151double StableCountRand<RealType>::wrightSeries(double x, bool integrated) const
152{
153 if (!(x > 0) || !std::isfinite(x) || alpha == 0.5)
154 return NAN;
155 const long double exponent = alpha;
156 const long double logX = std::log(static_cast<long double>(x));
157 const long double logPi = std::log(randlib::consts::pi);
158 long double sum = 0;
159 long double correction = 0;
160 long double largest = 0;
161 int largestIndex = 0;
162 int smallTerms = 0;
163 for (int k = 1; k <= 4096; ++k) {
164 const long double ak = exponent * k;
165 const long double sine = std::sin(randlib::consts::pi * ak);
166 if (sine == 0)
167 continue;
168 long double logMagnitude = std::lgamma(1 + ak) - std::lgamma(static_cast<long double>(k) + 1) + ak * logX +
169 std::log(std::fabs(sine)) - logPi - static_cast<long double>(logNormalization);
170 if (integrated)
171 logMagnitude += logX - std::log(ak + 1);
172 if (logMagnitude > 15)
173 return NAN;
174 const long double magnitude = std::exp(logMagnitude);
175 const long double sign = ((k % 2 == 0) ? -1 : 1) * std::copysign(1.0L, sine);
176 const long double term = sign * magnitude;
177 const long double adjusted = term - correction;
178 const long double next = sum + adjusted;
179 correction = (next - sum) - adjusted;
180 sum = next;
181 if (magnitude > largest) {
182 largest = magnitude;
183 largestIndex = k;
184 smallTerms = 0;
185 } else if (k > largestIndex + 8 &&
186 magnitude <= 8 * std::numeric_limits<long double>::epsilon() * std::max(std::fabs(sum), 1.0L)) {
187 ++smallTerms;
188 if (smallTerms >= 24)
189 return (sum >= 0) ? static_cast<double>(sum) : NAN;
190 } else {
191 smallTerms = 0;
192 }
193 }
194 return NAN;
195}
196
197template <typename RealType>
198double StableCountRand<RealType>::directStandardCdf(double x) const
199{
200 if (!(x > 0))
201 return 0;
202 if (std::isinf(x))
203 return 1;
204 const double series = wrightSeries(x, true);
205 if (series >= 0 && series <= 1 && std::isfinite(series))
206 return series;
207 const double normalization = std::exp(logNormalization);
208 return std::clamp(math::integrate<double>(
209 [this, normalization](double t) {
210 return positiveStable.f(static_cast<RealType>(t)) / (normalization * t);
211 },
212 1 / x, INFINITY, 2e-13, 1024),
213 0.0, 1.0);
214}
215
216template <typename RealType>
217double StableCountRand<RealType>::directStandardSurvival(double x) const
218{
219 if (!(x > 0))
220 return 1;
221 if (std::isinf(x))
222 return 0;
223 const double normalization = std::exp(logNormalization);
224 return std::clamp(math::integrate<double>(
225 [this, normalization](double t) {
226 return positiveStable.f(static_cast<RealType>(t)) / (normalization * t);
227 },
228 0.0, 1 / x, 2e-13, 1024),
229 0.0, 1.0);
230}
231
232template <typename RealType>
233double StableCountRand<RealType>::logf(const RealType &x) const
234{
235 const double standardized = (static_cast<double>(x) - nu) / theta;
236 return standardLogDensity(standardized) - logTheta;
237}
238
239template <typename RealType>
240double StableCountRand<RealType>::f(const RealType &x) const
241{
242 return std::exp(logf(x));
243}
244
245template <typename RealType>
246double StableCountRand<RealType>::F(const RealType &x) const
247{
248 const double standardized = (static_cast<double>(x) - nu) / theta;
249 if (!(standardized > 0))
250 return 0;
251 if (alpha == 0.5) {
252 static const double logShape = std::log(1.5);
253 static const double logGammaShape = std::lgamma(1.5);
254 return math::pgamma(1.5, 0.25 * standardized, logShape, logGammaShape);
255 }
256 const double lower = directStandardCdf(standardized);
257 return (lower <= 0.95) ? lower : 1 - directStandardSurvival(standardized);
258}
259
260template <typename RealType>
261double StableCountRand<RealType>::S(const RealType &x) const
262{
263 const double standardized = (static_cast<double>(x) - nu) / theta;
264 if (!(standardized > 0))
265 return 1;
266 if (alpha == 0.5) {
267 static const double logShape = std::log(1.5);
268 static const double logGammaShape = std::lgamma(1.5);
269 return math::qgamma(1.5, 0.25 * standardized, logShape, logGammaShape);
270 }
271 const double lower = directStandardCdf(standardized);
272 return (lower <= 0.95) ? 1 - lower : directStandardSurvival(standardized);
273}
274
275template <typename RealType>
276RealType StableCountRand<RealType>::quantileImpl(double p) const
277{
278 const long double mean = standardMoment(1);
279 const RealType guess =
280 std::isfinite(mean) ? static_cast<RealType>(nu + theta * mean) : std::numeric_limits<RealType>::quiet_NaN();
282}
283
284template <typename RealType>
285RealType StableCountRand<RealType>::quantileImpl1m(double p) const
286{
287 const long double mean = standardMoment(1);
288 const RealType guess =
289 std::isfinite(mean) ? static_cast<RealType>(nu + theta * mean) : std::numeric_limits<RealType>::quiet_NaN();
291}
292
293template <typename RealType>
294template <std::uniform_random_bit_generator G>
296{
297 if (alpha == 0.5) {
298 const RealType exponential = detail::standardExponential<RealType>(g);
299 const RealType normal = detail::standardNormal<RealType>(g);
300 const RealType standard = 4 * (exponential + 0.5 * normal * normal);
301 return static_cast<RealType>(nu) + static_cast<RealType>(theta) * standard;
302 }
303 return this->Quantile(detail::uniform01<double>(g));
304}
305
306template <typename RealType>
307void StableCountRand<RealType>::sample(detail::EngineRef g, std::span<RealType> outputData) const
308{
309 for (RealType &value : outputData)
310 value = (*this)(g);
311}
312
313template <typename RealType>
314long double StableCountRand<RealType>::standardMoment(long double order) const
315{
316 if (!(order > -1) || !std::isfinite(order))
317 throw std::invalid_argument("Stable Count distribution: standard moment order should be finite and above -1");
318 if (alpha == 0.5) {
319 if (order == 0)
320 return 1;
321 if (order == 1)
322 return 6;
323 if (order == 2)
324 return 60;
325 if (order == 3)
326 return 840;
327 if (order == 4)
328 return 15120;
329 }
330 const long double inverseAlpha = 1 / static_cast<long double>(alpha);
331 const long double logarithm =
332 std::lgamma((order + 1) * inverseAlpha) - std::lgamma(order + 1) - std::lgamma(inverseAlpha);
333 return std::exp(logarithm);
334}
335
336template <typename RealType>
337long double StableCountRand<RealType>::Moment(int order) const
338{
339 if (order < 0)
340 throw std::invalid_argument("Stable Count distribution: raw moment order should be non-negative");
341 long double sum = 0;
342 long double binomial = 1;
343 for (int k = 0; k <= order; ++k) {
344 sum += binomial * std::pow(static_cast<long double>(nu), order - k) *
345 std::pow(static_cast<long double>(theta), k) * standardMoment(k);
346 binomial *= static_cast<long double>(order - k) / (k + 1);
347 }
348 return sum;
349}
350
351template <typename RealType>
353{
354 const long double first = standardMoment(1);
355 const long double second = standardMoment(2);
356 return static_cast<long double>(theta) * theta * (second - first * first);
357}
358
359template <typename RealType>
361{
362 const long double first = standardMoment(1);
363 const long double second = standardMoment(2);
364 const long double third = standardMoment(3);
365 const long double variance = second - first * first;
366 return (third - 3 * first * second + 2 * first * first * first) / std::pow(variance, 1.5L);
367}
368
369template <typename RealType>
371{
372 const long double first = standardMoment(1);
373 const long double second = standardMoment(2);
374 const long double third = standardMoment(3);
375 const long double fourth = standardMoment(4);
376 const long double variance = second - first * first;
377 const long double centralFourth =
378 fourth - 4 * first * third + 6 * first * first * second - 3 * first * first * first * first;
379 return centralFourth / (variance * variance) - 3;
380}
381
382} // namespace randlib
The ContinuousDistribution class Abstract class for all continuous distributions.
Definition continuous.hpp:22
RealType quantileImpl(double p, RealType initValue) const override
Definition continuous.hpp:140
RealType quantileImpl1m(double p, RealType initValue) const override
Definition continuous.hpp:152
Stable Count distribution.
Definition stable_count.hpp:39
void SetScale(double scale)
Definition stable_count.hpp:72
double F(const RealType &x) const override
Definition stable_count.hpp:246
std::string Name() const override
Definition stable_count.hpp:131
long double FourthMoment() const override
Definition stable_count.hpp:95
long double Variance() const override
Definition stable_count.hpp:352
void SetParameters(double exponent, double scale=1, double location=0)
Definition stable_count.hpp:112
double GetExponent() const noexcept
Definition stable_count.hpp:75
double logf(const RealType &x) const override
Definition stable_count.hpp:233
RealType operator()(G &g) const
Definition stable_count.hpp:295
RealType MinValue() const override
Definition stable_count.hpp:67
double f(const RealType &x) const override
Definition stable_count.hpp:240
long double Mean() const override
Definition stable_count.hpp:91
long double Moment(int order) const
Definition stable_count.hpp:337
long double StandardMoment(long double order) const
Definition stable_count.hpp:88
RealType MaxValue() const override
Definition stable_count.hpp:68
long double ExcessKurtosis() const override
Definition stable_count.hpp:370
Support SupportType() const override
Definition stable_count.hpp:66
long double Skewness() const override
Definition stable_count.hpp:360
void SetLocation(double location)
Definition stable_count.hpp:73
double GetLocation() const noexcept
Definition stable_count.hpp:77
RealType Mode() const override
Mode is the value, which has the largest probability to happen, or, in the case of continuous distrib...
Definition stable_count.hpp:102
double GetScale() const noexcept
Definition stable_count.hpp:76
long double SecondMoment() const override
Definition stable_count.hpp:93
double S(const RealType &x) const override
Definition stable_count.hpp:261
StableCountRand(double exponent=0.5, double scale=1, double location=0)
Definition stable_count.hpp:60
long double ThirdMoment() const override
Definition stable_count.hpp:94
void SetExponent(double exponent)
Definition stable_count.hpp:71
The StableRand class Stable distribution.
Definition stable.hpp:521
The EngineRef class Non-owning, type-erased reference to any uniform random bit generator.
Definition engine/uniform.hpp:116
constexpr long double half_pi
Definition constants.hpp:23
constexpr long double pi
Definition constants.hpp:22
constexpr long double e
Definition constants.hpp:15
constexpr long double lnpi
Definition constants.hpp:21
Real standardNormal(G &g)
Ziggurat algorithm of Marsaglia and Tsang with 256 layers and an explicit sign bit.
Definition variates.hpp:45
Real qgamma(std::type_identity_t< Real > a, Real x, std::type_identity_t< Real > logA, std::type_identity_t< Real > lgammaA)
Calculate upper regularized incomplete gamma function, accelerated by using precalculated values of l...
Definition math/gamma.hpp:938
Real pgamma(std::type_identity_t< Real > a, Real x, std::type_identity_t< Real > logA, std::type_identity_t< Real > lgammaA)
Calculate lower regularized incomplete gamma function, accelerated by using precalculated value of lo...
Definition math/gamma.hpp:896
Definition bivariate_normal.hpp:10
Support
Shape of the support of a univariate distribution.
Definition univariate.hpp:15
@ RightSemiFinite
[a, ∞)