RandLib 2.0.0
Probability distributions, fitting, and heavy-tail tools for C++20
Loading...
Searching...
No Matches
discrete.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <span>
4
5#include <limits>
6
11
12namespace randlib
13{
14
19template <typename IntType>
20class DiscreteDistribution : virtual public UnivariateDistribution<IntType>
21{
22 static_assert(std::is_integral_v<IntType> && std::is_signed_v<IntType>,
23 "Discrete distribution supports only signed integral types");
24
25protected:
27
28public:
30
31protected:
32public:
37 [[nodiscard]] virtual double P(const IntType &k) const { return std::exp(this->logP(k)); }
38
43 [[nodiscard]] virtual double logP(const IntType &x) const = 0;
44
50 void ProbabilityMassFunction(std::span<const IntType> x, std::span<double> y) const;
51
57 void LogProbabilityMassFunction(std::span<const IntType> x, std::span<double> y) const;
58
59 [[nodiscard]] IntType Mode() const override;
60
61protected:
62 IntType quantileImpl(double p, IntType initValue) const override;
63 IntType quantileImpl(double p) const override;
64 IntType quantileImpl1m(double p, IntType initValue) const override;
65 IntType quantileImpl1m(double p) const override;
66 long double ExpectedValue(const std::function<double(IntType)> &funPtr, IntType minPoint,
67 IntType maxPoint) const override;
68 std::complex<double> CFImpl(double t) const override;
69
70private:
77 IntType searchQuantile(double p, bool upperTail) const;
78
79public:
84 [[nodiscard]] double Hazard(const IntType &x) const override;
85
90 [[nodiscard]] double LikelihoodFunction(std::span<const IntType> sample) const override;
91
96 [[nodiscard]] double LogLikelihoodFunction(std::span<const IntType> sample) const override;
97
112 bool PearsonChiSquaredTest(std::span<const IntType> orderStatistic, double alpha, int lowerBoundary,
113 int upperBoundary, size_t numberOfEstimatedParameters = 0) const;
114
123 bool PearsonChiSquaredTest(std::span<const IntType> orderStatistic, double alpha,
124 size_t numberOfEstimatedParameters = 0) const;
125};
126
127} // namespace randlib
128
131
132namespace randlib
133{
134
135template <typename IntType>
136void DiscreteDistribution<IntType>::ProbabilityMassFunction(std::span<const IntType> x, std::span<double> y) const
137{
138 const size_t size = std::min(x.size(), y.size());
139 for (size_t i = 0; i != size; ++i)
140 y[i] = this->P(x[i]);
141}
142
143template <typename IntType>
144void DiscreteDistribution<IntType>::LogProbabilityMassFunction(std::span<const IntType> x, std::span<double> y) const
145{
146 const size_t size = std::min(x.size(), y.size());
147 for (size_t i = 0; i != size; ++i)
148 y[i] = this->logP(x[i]);
149}
150
151template <typename IntType>
153{
155 IntType x = this->Median();
156 double logProb = this->logP(x), newLogProb = this->logP(x + 1);
157 if (logProb < newLogProb) {
158 do {
159 ++x;
160 logProb = newLogProb;
161 newLogProb = this->logP(x + 1);
162 } while (logProb < newLogProb);
163 } else {
164 newLogProb = this->logP(x - 1);
165 while (logProb < newLogProb) {
166 --x;
167 logProb = newLogProb;
168 newLogProb = this->logP(x - 1);
169 }
170 }
171 return x;
172}
173
174template <typename IntType>
175IntType DiscreteDistribution<IntType>::quantileImpl(double p, IntType initValue) const
176{
177 IntType down = initValue, up = down + 1;
178 double fu = this->F(up), fd = this->F(down);
180 while (fu < p) {
181 fd = fu;
182 fu = this->F(++up);
183 }
184 down = up - 1;
186 while (fd > p) {
187 fd = this->F(--down);
188 }
189 up = down + 1;
191 return (fd < p) ? up : down;
192}
193
194template <typename IntType>
196{
197 return searchQuantile(p, false);
198}
199
200template <typename IntType>
201IntType DiscreteDistribution<IntType>::quantileImpl1m(double p, IntType initValue) const
202{
203 IntType down = initValue, up = down + 1;
204 double su = this->S(up), sd = this->S(down);
206 while (su > p) {
207 sd = su;
208 su = this->S(++up);
209 }
210 down = up - 1;
212 while (sd < p) {
213 sd = this->S(--down);
214 }
215 up = down + 1;
216
218 return (sd > p) ? up : down;
219}
220
221template <typename IntType>
223{
224 return searchQuantile(p, true);
225}
226
227template <typename IntType>
228IntType DiscreteDistribution<IntType>::searchQuantile(double p, bool upperTail) const
229{
232 static constexpr double slack = 8 * std::numeric_limits<double>::epsilon();
233 auto reached = [this, p, upperTail](long long k) {
234 return upperTail ? this->S(static_cast<IntType>(k)) <= p * (1 + slack)
235 : this->F(static_cast<IntType>(k)) >= p * (1 - slack);
236 };
237 const long long lo = this->MinValue(), hi = this->MaxValue();
238 const bool loBounded = lo > static_cast<long long>(std::numeric_limits<IntType>::lowest());
239
241 long long a, b;
242 if (loBounded || !reached(0)) {
243 a = loBounded ? lo - 1 : 0;
244 long long step = 1;
245 b = a + step;
246 while (b < hi && !reached(b)) {
247 a = b;
248 step *= 2;
249 b = (step > hi - a) ? hi : a + step;
250 }
251 b = std::min(b, hi);
252 } else {
253 b = 0;
254 long long step = 1;
255 a = b - step;
256 while (a > lo && reached(a)) {
257 b = a;
258 step *= 2;
259 a = (step > a - lo) ? lo : b - step;
260 }
261 }
262 while (b - a > 1) {
263 long long middle = a + (b - a) / 2;
264 if (reached(middle))
265 b = middle;
266 else
267 a = middle;
268 }
269 return static_cast<IntType>(b);
270}
271
272template <typename IntType>
273long double DiscreteDistribution<IntType>::ExpectedValue(const std::function<double(IntType)> &funPtr, IntType minPoint,
274 IntType maxPoint) const
275{
276 Support suppType = this->SupportType();
277 IntType k = minPoint, upperBoundary = maxPoint;
278 if (suppType == Support::Finite || suppType == Support::RightSemiFinite) {
279 k = std::max(k, this->MinValue());
280 }
281 if (suppType == Support::Finite || suppType == Support::LeftSemiFinite) {
282 upperBoundary = std::min(upperBoundary, this->MaxValue());
283 }
284
285 double sum = 0;
286 do {
287 double addon = funPtr(k);
288 if (addon != 0.0) {
289 double prob = this->P(k);
291 return sum;
292 addon *= this->P(k);
293 sum += addon;
294 }
295 ++k;
296 } while (k <= upperBoundary);
297 return sum;
298}
299
300template <typename IntType>
301std::complex<double> DiscreteDistribution<IntType>::CFImpl(double t) const
302{
308 static constexpr long long MAX_TERMS = 1000000;
309 static constexpr double EPSILON = 1e-17;
310 const long long lo = this->MinValue(), hi = this->MaxValue(), start = this->Median();
311 std::complex<long double> sum = 0;
312 auto walk = [&](long long from, long long step, long long end, bool upper) {
313 const std::complex<double> ratio = std::polar(1.0, step * t);
314 const std::complex<double> w = ratio / (1.0 - ratio);
315 const double damping = std::abs(1.0 - ratio);
316 std::complex<double> zk = std::polar(1.0, t * static_cast<double>(from));
317 double last[4] = {0, 0, 0, 0};
318 long long k = from;
319 for (long long terms = 0; (step > 0) ? k <= end : k >= end; k += step, ++terms) {
320 double p = this->P(static_cast<IntType>(k));
321 sum += std::complex<long double>(p * zk);
322 zk *= ratio;
323 if (terms % 64 == 63)
324 zk = std::polar(1.0, t * static_cast<double>(k + step));
325 last[3] = last[2];
326 last[2] = last[1];
327 last[1] = last[0];
328 last[0] = p;
329 if (p == 0.0 && terms > 16)
330 return;
331 if (terms < 16 || damping == 0.0)
332 continue;
333 double thirdDifference = std::fabs(last[0] - 3 * last[1] + 3 * last[2] - last[3]);
334 double nextTerm = std::pow(std::abs(w), 3) * thirdDifference / damping;
335 bool unbounded = (step > 0) ? end - k > MAX_TERMS : k - end > MAX_TERMS;
336 bool decreasing = last[0] < last[1] && last[1] < last[2] && last[2] < last[3];
337 bool converged = unbounded && decreasing && nextTerm < EPSILON * std::abs(sum);
338 bool powerOfTwo = ((terms + 1) & terms) == 0;
339 bool massGone = powerOfTwo &&
340 (upper ? this->S(static_cast<IntType>(k)) : this->F(static_cast<IntType>(k - 1))) < EPSILON;
341 if (converged || massGone || terms >= MAX_TERMS)
342 break;
343 }
344 if ((step > 0) ? k > end : k < end)
345 return;
346 k += step;
347 double next[3];
348 for (int j = 0; j < 3; ++j)
349 next[j] = ((step > 0) ? k + j * step <= end : k + j * step >= end)
350 ? this->P(static_cast<IntType>(k + j * step))
351 : 0.0;
352 std::complex<double> series = next[0] + w * (next[1] - next[0]) + w * w * (next[2] - 2 * next[1] + next[0]);
353 sum += std::complex<long double>(zk * series / (1.0 - ratio));
354 };
355 walk(start, 1, hi, true);
356 if (start > lo)
357 walk(start - 1, -1, lo, false);
358 return std::complex<double>(sum);
359}
360
361template <typename IntType>
362double DiscreteDistribution<IntType>::Hazard(const IntType &x) const
363{
364 if (x < this->MinValue())
365 return 0.0;
366 if (x > this->MaxValue())
367 return NAN;
368 return this->P(x) / this->S(x);
369}
370
371template <typename IntType>
372double DiscreteDistribution<IntType>::LikelihoodFunction(std::span<const IntType> sample) const
373{
374 long double res = 1.0;
375 for (const IntType &var : sample)
376 res *= this->P(var);
377 return res;
378}
379
380template <typename IntType>
381double DiscreteDistribution<IntType>::LogLikelihoodFunction(std::span<const IntType> sample) const
382{
383 long double res = 0.0;
384 for (const IntType &var : sample)
385 res += this->logP(var);
386 return res;
387}
388
389template <typename IntType>
390bool DiscreteDistribution<IntType>::PearsonChiSquaredTest(std::span<const IntType> orderStatistic, double alpha,
391 int lowerBoundary, int upperBoundary,
392 size_t numberOfEstimatedParameters) const
393{
394 const size_t n = orderStatistic.size();
395
397 if (n == 0)
398 throw std::invalid_argument("Order statistic should not be empty");
399 if (!(alpha > 0 && alpha < 1))
400 throw std::invalid_argument("Significance level should be positive and smaller than 1");
401 if (lowerBoundary >= upperBoundary)
402 throw std::invalid_argument("Lower boundary should be smaller than upper one");
403 for (size_t j = 1; j != n; ++j) {
404 if (orderStatistic[j] < orderStatistic[j - 1])
405 throw std::invalid_argument("Order statistic should be sorted in ascending order");
406 }
407 if (orderStatistic[0] < this->MinValue())
408 throw std::invalid_argument(
409 "Some elements in the sample are too small to belong to this distribution, they should be greater than " +
410 this->toStringWithPrecision(this->MinValue()));
411 if (orderStatistic[n - 1] > this->MaxValue())
412 throw std::invalid_argument(
413 "Some elements in the sample are too large to belong to this distribution, they should be smaller than " +
414 this->toStringWithPrecision(this->MaxValue()));
415
420 const long long minValue = this->MinValue(), maxValue = this->MaxValue();
421 const long long lower = (lowerBoundary > minValue) ? lowerBoundary : orderStatistic[0] - 1LL;
422 const long long upper = (upperBoundary < maxValue) ? upperBoundary : orderStatistic[n - 1];
423 const double nInv = 1.0 / n;
424 double sum = 0.0;
425 size_t k = 0;
426 auto addCell = [&sum, &k, nInv](size_t count, double expectedProb) {
427 if (!(expectedProb > 0)) {
428 if (count > 0)
429 sum = INFINITY;
430 return;
431 }
432 double addon = nInv * count - expectedProb;
433 sum += addon * addon / expectedProb;
434 ++k;
435 };
436
437 size_t i = 0;
438 if (lower >= minValue) {
439 i = std::upper_bound(orderStatistic.begin(), orderStatistic.end(), lower) - orderStatistic.begin();
440 addCell(i, this->F(static_cast<IntType>(lower)));
441 }
442 double observedMass = 0;
443 long long distinct = 0;
444 while (i < n && orderStatistic[i] <= upper) {
445 const IntType x = orderStatistic[i];
446 size_t count = 0;
447 for (; i < n && orderStatistic[i] == x; ++i)
448 ++count;
449 const double probability = this->P(x);
450 observedMass += probability;
451 ++distinct;
452 addCell(count, probability);
453 }
454 const long long unobserved = upper - lower - distinct;
455 if (unobserved > 0) {
456 const double cellMass =
457 this->F(static_cast<IntType>(upper)) - ((lower >= minValue) ? this->F(static_cast<IntType>(lower)) : 0.0);
458 sum += std::max(cellMass - observedMass, 0.0);
459 k += static_cast<size_t>(unobserved);
460 }
461 if (upper < maxValue)
462 addCell(n - i, this->S(static_cast<IntType>(upper)));
463
464 if (k <= numberOfEstimatedParameters + 1) {
465 throw std::invalid_argument("Sample is too small, number of groups (" + this->toStringWithPrecision(k) +
466 ") should be bigger than number of estimated parameters plus 1 (" +
467 this->toStringWithPrecision(numberOfEstimatedParameters + 1) + ")");
468 }
469 double statistic = n * sum;
470 ChiSquaredRand X(k - 1 - numberOfEstimatedParameters);
471 double q = X.Quantile1m(alpha);
472 return statistic <= q;
473}
474
475template <typename IntType>
476bool DiscreteDistribution<IntType>::PearsonChiSquaredTest(std::span<const IntType> orderStatistic, double alpha,
477 size_t numberOfEstimatedParameters) const
478{
479 return PearsonChiSquaredTest(orderStatistic, alpha, this->MinValue(), this->MaxValue(),
480 numberOfEstimatedParameters);
481}
482
483} // namespace randlib
The ChiSquaredRand class Chi-squared distribution.
Definition continuous/gamma.hpp:355
The DiscreteDistribution class Abstract class for all discrete distributions.
Definition discrete.hpp:21
DiscreteDistribution()
Definition discrete.hpp:26
IntType Mode() const override
Mode is the value, which has the largest probability to happen, or, in the case of continuous distrib...
Definition discrete.hpp:152
double LogLikelihoodFunction(std::span< const IntType > sample) const override
Definition discrete.hpp:381
double Hazard(const IntType &x) const override
Definition discrete.hpp:362
long double ExpectedValue(const std::function< double(IntType)> &funPtr, IntType minPoint, IntType maxPoint) const override
Definition discrete.hpp:273
void ProbabilityMassFunction(std::span< const IntType > x, std::span< double > y) const
fill vector y with P(x)
Definition discrete.hpp:136
std::complex< double > CFImpl(double t) const override
Definition discrete.hpp:301
virtual ~DiscreteDistribution()
Definition discrete.hpp:29
IntType quantileImpl1m(double p) const override
Definition discrete.hpp:222
IntType quantileImpl1m(double p, IntType initValue) const override
Definition discrete.hpp:201
double LikelihoodFunction(std::span< const IntType > sample) const override
Definition discrete.hpp:372
bool PearsonChiSquaredTest(std::span< const IntType > orderStatistic, double alpha, int lowerBoundary, int upperBoundary, size_t numberOfEstimatedParameters=0) const
Definition discrete.hpp:390
virtual double logP(const IntType &x) const =0
bool PearsonChiSquaredTest(std::span< const IntType > orderStatistic, double alpha, size_t numberOfEstimatedParameters=0) const
Definition discrete.hpp:476
void LogProbabilityMassFunction(std::span< const IntType > x, std::span< double > y) const
fill vector y with logP(x)
Definition discrete.hpp:144
IntType quantileImpl(double p, IntType initValue) const override
Definition discrete.hpp:175
IntType quantileImpl(double p) const override
Definition discrete.hpp:195
virtual double P(const IntType &k) const
Definition discrete.hpp:37
virtual void sample(detail::EngineRef g, std::span< T > outputData) const
Fills the output with variate(g); distributions with a faster bulk algorithm override it.
Definition distribution.hpp:66
The UnivariateDistribution class Abstract class for all univariate probability distributions.
Definition univariate.hpp:28
T Quantile1m(double p) const
Definition univariate.hpp:324
constexpr long double minimumTolerance
Definition constants.hpp:44
Definition bivariate_normal.hpp:10
Support
Shape of the support of a univariate distribution.
Definition univariate.hpp:15
@ LeftSemiFinite
(-∞, b]
@ RightSemiFinite
[a, ∞)