RandLib 2.0.0
Probability distributions, fitting, and heavy-tail tools for C++20
Loading...
Searching...
No Matches
negative_binomial.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <span>
5
11
12namespace randlib
13{
14
27template <typename IntType, typename T>
29{
30 IntType variate(detail::EngineRef g) const override { return (*this)(g); }
31 void sample(detail::EngineRef g, std::span<IntType> outputData) const override;
32
33protected:
34 double p = 0.5;
35 double q = 0.5;
38
39private:
40 T r = 1;
41 double pdfCoef = -randlib::consts::ln2;
42 double qDivP = 1;
43 static constexpr int tableSize = 16;
44 double table[tableSize];
45 GammaRand<float> GammaRV{};
46
47protected:
48 NegativeBinomialDistribution(T number = 1, double probability = 0.5);
49
50public:
51 [[nodiscard]] Support SupportType() const override { return Support::RightSemiFinite; }
52 [[nodiscard]] IntType MinValue() const override { return 0; }
53 [[nodiscard]] IntType MaxValue() const override { return std::numeric_limits<IntType>::max(); }
54
55protected:
56 void SetParameters(T number, double probability);
57
58public:
59 [[nodiscard]] inline double GetProbability() const noexcept { return p; }
60 [[nodiscard]] inline T GetNumber() const noexcept { return r; }
61
62 [[nodiscard]] double P(const IntType &k) const override;
63 [[nodiscard]] double logP(const IntType &k) const override;
64 [[nodiscard]] double F(const IntType &k) const override;
65 [[nodiscard]] double S(const IntType &k) const override;
66
67protected:
74
81 [[nodiscard]] GENERATOR_ID GetIdOfUsedGenerator() const
82 {
84 static constexpr T LARGEST_SUMMED_NUMBER = 4;
85 if (r <= LARGEST_SUMMED_NUMBER && r == std::round(r))
86 return (p < 0.08) ? EXPONENTIAL : TABLE;
87 return GAMMA_POISSON;
88 }
89
90 template <std::uniform_random_bit_generator G>
91 IntType variateGeometricByTable(G &g) const;
92 template <std::uniform_random_bit_generator G>
93 IntType variateGeometricThroughExponential(G &g) const;
94
95private:
96 template <std::uniform_random_bit_generator G>
97 IntType variateByTable(G &g) const;
98 template <std::uniform_random_bit_generator G>
99 IntType variateThroughExponential(G &g) const;
100 template <std::uniform_random_bit_generator G>
101 IntType variateThroughGammaPoisson(G &g) const;
102
103public:
104 template <std::uniform_random_bit_generator G>
105 [[nodiscard]] IntType operator()(G &g) const;
106
107 [[nodiscard]] long double Mean() const override;
108 [[nodiscard]] long double Variance() const override;
109 [[nodiscard]] IntType Mode() const override;
110 [[nodiscard]] long double Skewness() const override;
111 [[nodiscard]] long double ExcessKurtosis() const override;
112
113private:
114 std::complex<double> CFImpl(double t) const override;
115
116public:
124 FitProbabilityBayes(std::span<const IntType> sample, const BetaDistribution<> &priorDistribution, bool MAP = false);
125};
126
131template <typename IntType, typename T>
133{
134public:
135 NegativeBinomialRand(T number = 1, double probability = 0.5)
136 : NegativeBinomialDistribution<IntType, T>(number, probability)
137 {
138 }
139 [[nodiscard]] std::string Name() const override;
140
142
143 static constexpr char TOO_SMALL_VARIANCE[] = "Sample variance should be greater than sample mean";
144
149 RANDLIB_DEPRECATED_FIT_MEMBER void Fit(std::span<const IntType> sample);
150};
151
152template <typename IntType = int>
154
155template <typename IntType = int>
157
158} // namespace randlib
159
164
165namespace randlib
166{
167
168template <typename IntType, typename T>
170{
171 SetParameters(number, probability);
172}
173
174template <typename IntType, typename T>
176{
177 if (!(number > 0))
178 throw std::invalid_argument("Negative-Binomial distribution: number parameter should be positive");
179 if (!(probability > 0.0 && probability <= 1.0))
180 throw std::invalid_argument(
181 "Negative-Binomial distribution: probability parameter should be in interval (0, 1]");
182 r = number;
183 p = probability;
184 q = 1.0 - p;
185 logProb = std::log(p);
186 log1mProb = std::log1pl(-p);
187 if (p == 1.0) {
188 qDivP = 0.0;
189 pdfCoef = -std::lgamma(r);
190 return;
191 }
192 GammaRV.SetParameters(r, p / q);
193 qDivP = GammaRV.GetScale();
194 pdfCoef = r * logProb;
195 pdfCoef -= GammaRV.GetLogGammaShape();
196
197 if (GetIdOfUsedGenerator() == TABLE) {
199 table[0] = p;
200 double prod = p;
201 for (int i = 1; i < tableSize; ++i) {
202 prod *= q;
203 table[i] = table[i - 1] + prod;
204 }
205 }
206}
207
208template <typename IntType, typename T>
210{
211 return (k < 0) ? 0.0 : std::exp(logP(k));
212}
213
214template <typename IntType, typename T>
216{
217 if (k < 0)
218 return -INFINITY;
219 if (p == 1.0)
220 return (k == 0) ? 0.0 : -INFINITY;
221 double y = std::lgammal(r + k);
222 y -= math::lfact(k);
223 y += k * log1mProb;
224 y += pdfCoef;
225 return y;
226}
227
228template <typename IntType, typename T>
230{
231 if (k < 0)
232 return 0.0;
233 if (p == 1.0)
234 return 1.0;
235 int kp1 = k + 1;
236 double logBetaFun = math::logBeta(r, kp1);
237 return math::ibeta(p, r, kp1, logBetaFun, logProb, log1mProb);
238}
239
240template <typename IntType, typename T>
242{
243 if (k < 0)
244 return 1.0;
245 if (p == 1.0)
246 return 0.0;
247 int kp1 = k + 1;
248 double logBetaFun = math::logBeta(kp1, r);
249 return math::ibeta(q, kp1, r, logBetaFun, log1mProb, logProb);
250}
251
252template <typename IntType, typename T>
253template <std::uniform_random_bit_generator G>
255{
257 const float rate = GammaRV(g);
258 return (rate > 0) ? detail::poissonVariate<IntType>(rate, g) : 0;
259}
260
261template <typename IntType, typename T>
262template <std::uniform_random_bit_generator G>
264{
265 double U = detail::uniform01<double>(g);
267 if (U > table[tableSize - 1])
268 return tableSize + variateGeometricByTable(g);
270 IntType x = 0;
271 while (U > table[x])
272 ++x;
273 return x;
274}
275
276template <typename IntType, typename T>
277template <std::uniform_random_bit_generator G>
279{
280 float X = -detail::standardExponential<float>(g) / log1mProb;
281 if (!std::isfinite(X) || X >= static_cast<long double>(std::numeric_limits<IntType>::max()))
282 return std::numeric_limits<IntType>::max();
283 return static_cast<IntType>(X);
284}
285
286template <typename IntType, typename T>
287template <std::uniform_random_bit_generator G>
289{
290 IntType var = 0;
291 for (int i = 0; i < r; ++i) {
292 IntType add = variateGeometricByTable(g);
293 if (add > std::numeric_limits<IntType>::max() - var)
294 return std::numeric_limits<IntType>::max();
295 var += add;
296 }
297 return var;
298}
299
300template <typename IntType, typename T>
301template <std::uniform_random_bit_generator G>
302IntType NegativeBinomialDistribution<IntType, T>::variateThroughExponential(G &g) const
303{
304 IntType var = 0;
305 for (int i = 0; i < r; ++i) {
306 IntType add = variateGeometricThroughExponential(g);
307 if (add > std::numeric_limits<IntType>::max() - var)
308 return std::numeric_limits<IntType>::max();
309 var += add;
310 }
311 return var;
312}
313
314template <typename IntType, typename T>
315template <std::uniform_random_bit_generator G>
317{
318 if (p == 1.0)
319 return 0;
320 GENERATOR_ID genId = GetIdOfUsedGenerator();
321 if (genId == TABLE)
322 return variateByTable(g);
323 return (genId == EXPONENTIAL) ? variateThroughExponential(g) : variateThroughGammaPoisson(g);
324}
325
326template <typename IntType, typename T>
327void NegativeBinomialDistribution<IntType, T>::sample(detail::EngineRef g, std::span<IntType> outputData) const
328{
329 if (p == 1.0) {
330 std::fill(outputData.begin(), outputData.end(), 0);
331 return;
332 }
333 GENERATOR_ID genId = GetIdOfUsedGenerator();
334 if (genId == TABLE) {
335 for (IntType &var : outputData)
336 var = variateByTable(g);
337 } else if (genId == EXPONENTIAL) {
338 for (IntType &var : outputData)
339 var = variateThroughExponential(g);
340 } else {
341 for (IntType &var : outputData)
342 var = variateThroughGammaPoisson(g);
343 }
344}
345
346template <typename IntType, typename T>
348{
349 return qDivP * r;
350}
351
352template <typename IntType, typename T>
354{
355 return qDivP * r / p;
356}
357
358template <typename IntType, typename T>
359std::complex<double> NegativeBinomialDistribution<IntType, T>::CFImpl(double t) const
360{
361 std::complex<double> denominator(1.0 - q * std::cos(t), -q * std::sin(t));
362 return std::pow(p / denominator, r);
363}
364
365template <typename IntType, typename T>
367{
368 return (r > 1) ? std::floor((r - 1) * qDivP) : 0;
369}
370
371template <typename IntType, typename T>
373{
374 if (p == 1.0)
375 return NAN;
376 return (1 + q) / std::sqrt(q * r);
377}
378
379template <typename IntType, typename T>
381{
382 if (p == 1.0)
383 return NAN;
384 long double kurtosis = p / qDivP;
385 kurtosis += 6;
386 return kurtosis / r;
387}
388
389template <typename IntType, typename T>
391 const BetaDistribution<> &priorDistribution,
392 bool MAP)
393{
394 if (!this->allElementsAreNonNegative(sample))
395 throw this->fitError(FitErrorCode::WrongSample, this->NON_NEGATIVITY_VIOLATION);
396 int n = sample.size();
397 double alpha = priorDistribution.GetAlpha();
398 double beta = priorDistribution.GetBeta();
399 BetaRand<> posteriorDistribution(alpha + r * n, beta + this->GetSampleSum(sample));
400 SetParameters(r, MAP ? posteriorDistribution.Mode() : posteriorDistribution.Mean());
401 return posteriorDistribution;
402}
403template <typename IntType, typename T>
405{
406 if (std::is_integral_v<T>)
407 return "Pascal(" + this->toStringWithPrecision(this->GetNumber()) + ", " +
408 this->toStringWithPrecision(this->GetProbability()) + ")";
409 return "Polya(" + this->toStringWithPrecision(this->GetNumber()) + ", " +
410 this->toStringWithPrecision(this->GetProbability()) + ")";
411}
412
413template <typename IntType, typename T>
415
416template <typename IntType, typename T>
417void NegativeBinomialRand<IntType, T>::Fit(std::span<const IntType> sample)
418{
420 if (!this->allElementsAreNonNegative(sample))
421 throw this->fitError(FitErrorCode::WrongSample, this->NON_NEGATIVITY_VIOLATION);
423 DoublePair stats = this->GetSampleMeanAndVariance(sample);
424 double mean = stats.first, variance = stats.second;
426 if (variance <= mean)
427 throw this->fitError(FitErrorCode::NotApplicable, this->TOO_SMALL_VARIANCE);
428 double guess = mean * mean / (variance - mean);
429 size_t n = sample.size();
430 auto score = [sample, mean, n](double x) {
431 double first = 0.0, second = 0.0;
432 for (const IntType &var : sample) {
433 first += math::digamma(var + x);
434 second += math::trigamma(var + x);
435 }
436 first -= n * (math::digammamLog(x) + std::log(x + mean));
437 second -= n * (math::trigamma(x) - mean / (x * (mean + x)));
438 return DoublePair(first, second);
439 };
440 double logGuess = std::log(guess);
441 if (!math::findRootNewtonFirstOrder<double>(
442 [&score](double logNumber) {
443 double number = std::exp(logNumber);
444 DoublePair value = score(number);
445 value.second *= number;
446 return value;
447 },
448 logGuess))
449 throw this->fitError(FitErrorCode::Undefined, "Error in root-finding algorithm");
450 guess = std::exp(logGuess);
451 if (!std::isfinite(guess) || guess <= 0.0)
452 throw this->fitError(FitErrorCode::WrongReturn,
453 "Number should be positive, but returned value is " + this->toStringWithPrecision(guess));
454 SetParameters(guess, guess / (guess + mean));
455}
456
457} // namespace randlib
The BetaDistribution class Abstract class for Beta distribution.
Definition continuous/beta.hpp:33
RealType Mode() const override
Mode is the value, which has the largest probability to happen, or, in the case of continuous distrib...
Definition continuous/beta.hpp:808
double GetAlpha() const noexcept
Definition continuous/beta.hpp:128
long double Mean() const override
Definition continuous/beta.hpp:761
double GetBeta() const noexcept
Definition continuous/beta.hpp:132
The BetaRand class Beta distribution.
Definition continuous/beta.hpp:246
The DiscreteDistribution class Abstract class for all discrete distributions.
Definition discrete.hpp:21
The GammaRand class Gamma distribution.
Definition continuous/gamma.hpp:295
The NegativeBinomialDistribution class Abstract class for Negative binomial distribution.
Definition negative_binomial.hpp:29
BetaRand FitProbabilityBayes(std::span< const IntType > sample, const BetaDistribution<> &priorDistribution, bool MAP=false)
Definition negative_binomial.hpp:390
IntType MinValue() const override
Definition negative_binomial.hpp:52
NegativeBinomialDistribution(T number=1, double probability=0.5)
Definition negative_binomial.hpp:169
IntType Mode() const override
Mode is the value, which has the largest probability to happen, or, in the case of continuous distrib...
Definition negative_binomial.hpp:366
double P(const IntType &k) const override
Definition negative_binomial.hpp:209
T GetNumber() const noexcept
Definition negative_binomial.hpp:60
long double Mean() const override
Definition negative_binomial.hpp:347
double F(const IntType &k) const override
Definition negative_binomial.hpp:229
double log1mProb
log(q)
Definition negative_binomial.hpp:37
double S(const IntType &k) const override
Definition negative_binomial.hpp:241
double q
probability of success
Definition negative_binomial.hpp:35
long double Variance() const override
Definition negative_binomial.hpp:353
GENERATOR_ID GetIdOfUsedGenerator() const
For a small integer r the variate is a sum of r geometric variates, drawn by a table or,...
Definition negative_binomial.hpp:81
double logProb
log(p)
Definition negative_binomial.hpp:36
double p
probability of failure
Definition negative_binomial.hpp:34
long double ExcessKurtosis() const override
Definition negative_binomial.hpp:380
IntType variateGeometricByTable(G &g) const
Definition negative_binomial.hpp:263
Support SupportType() const override
Definition negative_binomial.hpp:51
void SetParameters(T number, double probability)
Definition negative_binomial.hpp:175
double GetProbability() const noexcept
Definition negative_binomial.hpp:59
double logP(const IntType &k) const override
Definition negative_binomial.hpp:215
GENERATOR_ID
Definition negative_binomial.hpp:69
@ TABLE
Definition negative_binomial.hpp:70
@ EXPONENTIAL
Definition negative_binomial.hpp:71
@ GAMMA_POISSON
Definition negative_binomial.hpp:72
IntType operator()(G &g) const
Definition negative_binomial.hpp:316
IntType variateGeometricThroughExponential(G &g) const
Definition negative_binomial.hpp:278
long double Skewness() const override
Definition negative_binomial.hpp:372
IntType MaxValue() const override
Definition negative_binomial.hpp:53
The NegativeBinomialRand class Negative binomial distribution.
Definition negative_binomial.hpp:133
NegativeBinomialRand(T number=1, double probability=0.5)
Definition negative_binomial.hpp:135
void Fit(std::span< const IntType > sample)
set number and probability, estimated via maximum-likelihood method
Definition negative_binomial.hpp:417
std::string Name() const override
Definition negative_binomial.hpp:404
static constexpr char TOO_SMALL_VARIANCE[]
Definition negative_binomial.hpp:143
The EngineRef class Non-owning, type-erased reference to any uniform random bit generator.
Definition engine/uniform.hpp:116
#define RANDLIB_DEPRECATED_FIT_MEMBER
Definition fwd.hpp:9
constexpr long double ln2
Definition constants.hpp:18
Real standardNormal(G &g)
Ziggurat algorithm of Marsaglia and Tsang with 256 layers and an explicit sign bit.
Definition variates.hpp:45
Real digamma(Real x)
Definition math/gamma.hpp:691
Real logBeta(Real a, std::type_identity_t< Real > b)
Calculate logarithm of beta function.
Definition math/beta.hpp:72
Real lfact(size_t n)
Calculate log(n!) using table values for small n <= 255 and built-in lgamma function for large n > 25...
Definition math/gamma.hpp:591
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 trigamma(Real x)
Definition math/gamma.hpp:720
Real digammamLog(Real x)
Definition math/gamma.hpp:710
Definition bivariate_normal.hpp:10
@ WrongSample
the sample could not have been drawn from this distribution
@ NotApplicable
the method cannot be applied to this distribution or sample
@ WrongReturn
the method returned invalid parameters
@ Undefined
any other failure
Support
Shape of the support of a univariate distribution.
Definition univariate.hpp:15
@ RightSemiFinite
[a, ∞)
Pair< double > DoublePair
Definition fwd.hpp:39