RandLib 2.0.0
Probability distributions, fitting, and heavy-tail tools for C++20
Loading...
Searching...
No Matches
continuous.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <span>
4
5#include <limits>
6
12
13namespace randlib
14{
15
20template <typename RealType>
21class ContinuousDistribution : virtual public UnivariateDistribution<RealType>
22{
23 static_assert(std::is_floating_point_v<RealType>, "Continuous distribution supports only floating-point types");
24
25protected:
27
28public:
30
31protected:
32public:
37 [[nodiscard]] virtual double f(const RealType &x) const { return std::exp(this->logf(x)); }
38
43 [[nodiscard]] virtual double logf(const RealType &x) const = 0;
44
50 void ProbabilityDensityFunction(std::span<const RealType> x, std::span<double> y) const;
51
57 void LogProbabilityDensityFunction(std::span<const RealType> x, std::span<double> y) const;
58
59 [[nodiscard]] RealType Mode() const override;
60
61protected:
62 std::complex<double> integrateCharacteristicFunction(double t) const;
63 RealType quantileImpl(double p, RealType initValue) const override;
64 RealType quantileImpl(double p) const override;
65 RealType quantileImpl1m(double p, RealType initValue) const override;
66 RealType quantileImpl1m(double p) const override;
67 long double ExpectedValue(const std::function<double(RealType)> &funPtr, RealType minPoint,
68 RealType maxPoint) const override;
69
81 RealType searchQuantile(double p, bool upperTail, RealType guess, double relativeStep = 1e-3) const;
82
83public:
84 [[nodiscard]] double Hazard(const RealType &x) const override;
85 [[nodiscard]] double LikelihoodFunction(std::span<const RealType> sample) const override;
86 [[nodiscard]] double LogLikelihoodFunction(std::span<const RealType> sample) const override;
87
93 bool KolmogorovSmirnovTest(std::span<const RealType> orderStatistic, double alpha) const;
94};
95
96} // namespace randlib
97
100
101namespace randlib
102{
103
104template <typename RealType>
106{
111 static constexpr double NEGLIGIBLE_MASS = 1e-15, PHASE_TOLERANCE = 1e-11;
112 const double minimum = this->MinValue();
113 const bool bounded = std::isfinite(minimum);
114 const double head = bounded ? minimum + PHASE_TOLERANCE / t : -INFINITY;
115 const double lower = std::max(head, static_cast<double>(this->Quantile(NEGLIGIBLE_MASS)));
116 const std::complex<double> headMass = bounded ? this->F(head) * std::polar(1.0, t * minimum) : 0.0;
117 return headMass + math::integrateOscillatory([this](double x) { return this->f(x); }, t, lower,
118 [this](double x) { return this->S(x); });
119}
120
121template <typename RealType>
123 std::span<double> y) const
124{
125 const size_t size = std::min(x.size(), y.size());
126 for (size_t i = 0; i != size; ++i)
127 y[i] = this->f(x[i]);
128}
129
130template <typename RealType>
132 std::span<double> y) const
133{
134 const size_t size = std::min(x.size(), y.size());
135 for (size_t i = 0; i != size; ++i)
136 y[i] = this->logf(x[i]);
137}
138
139template <typename RealType>
140RealType ContinuousDistribution<RealType>::quantileImpl(double p, RealType initValue) const
141{
142 return searchQuantile(p, false, initValue);
143}
144
145template <typename RealType>
147{
148 return searchQuantile(p, false, std::numeric_limits<RealType>::quiet_NaN());
149}
150
151template <typename RealType>
152RealType ContinuousDistribution<RealType>::quantileImpl1m(double p, RealType initValue) const
153{
154 return searchQuantile(p, true, initValue);
155}
156
157template <typename RealType>
159{
160 return searchQuantile(p, true, std::numeric_limits<RealType>::quiet_NaN());
161}
162
163template <typename RealType>
164RealType ContinuousDistribution<RealType>::searchQuantile(double p, bool upperTail, RealType guess,
165 double relativeStep) const
166{
169 if (p > 0.5) {
170 p = 1.0 - p;
171 upperTail = !upperTail;
172 }
174 const bool logScale = p < 1e-3;
175 const double logP = std::log(p);
176 auto g = [this, p, upperTail, logScale, logP](RealType x) {
177 double v = upperTail ? this->S(x) : this->F(x);
178 if (!logScale)
179 return upperTail ? p - v : v - p;
180 double logV = (v > 0) ? std::log(v) : -INFINITY;
181 return upperTail ? logP - logV : logV - logP;
182 };
183 const RealType lo = this->MinValue(), hi = this->MaxValue();
184 const bool loFinite = std::isfinite(lo), hiFinite = std::isfinite(hi);
185
188 const bool useGuess = std::isfinite(guess) && guess > lo && guess < hi;
189 const RealType origin = useGuess ? guess : (loFinite ? lo : (hiFinite ? hi : 0));
190 RealType a = lo, b = hi;
191 double ga = -INFINITY, gb = INFINITY;
192 if (useGuess || !(loFinite && hiFinite)) {
193 double gOrigin = g(origin);
194 if (gOrigin == 0)
195 return origin;
196 RealType step = useGuess ? std::max<RealType>(std::fabs(guess) * relativeStep, 1e-3) : 1;
197 bool searchUp = (useGuess || !loFinite || hiFinite) ? gOrigin < 0 : true;
198 if (!useGuess && loFinite && !hiFinite)
199 searchUp = true;
200 if (searchUp) {
201 a = origin;
202 ga = gOrigin;
203 for (;;) {
204 b = origin + step;
205 if (hiFinite && b >= hi) {
206 b = hi;
207 gb = INFINITY;
208 break;
209 }
210 if (!std::isfinite(b))
211 return b;
212 gb = g(b);
213 if (gb >= 0)
214 break;
215 a = b;
216 ga = gb;
217 step += step;
218 }
219 } else {
220 b = origin;
221 gb = gOrigin;
222 for (;;) {
223 a = origin - step;
224 if (loFinite && a <= lo) {
225 a = lo;
226 ga = -INFINITY;
227 break;
228 }
229 if (!std::isfinite(a))
230 return a;
231 ga = g(a);
232 if (ga < 0)
233 break;
234 b = a;
235 gb = ga;
236 step += step;
237 }
238 }
239 }
240
243 const RealType anchor = loFinite ? lo : (hiFinite ? hi : 0);
244 auto split = [anchor, loFinite, hiFinite](RealType left, RealType right) {
245 const RealType direction = (loFinite || (!hiFinite && left >= 0)) ? 1 : -1;
246 RealType tl = direction * (left - anchor), tr = direction * (right - anchor);
247 RealType tNear = std::min(tl, tr), tFar = std::max(tl, tr), t;
248 if (tNear > 0 && tFar > 4 * tNear)
249 t = std::sqrt(tNear) * std::sqrt(tFar);
250 else if (tNear == 0 && tFar > 1e-280 && (loFinite || hiFinite))
251 t = tFar / 65536;
252 else
253 t = tNear + (tFar - tNear) / 2;
254 return anchor + direction * t;
255 };
256
259 for (int iter = 0; iter < 2000; ++iter) {
260 RealType tl = std::fabs(a - anchor), tr = std::fabs(b - anchor);
261 bool spansMagnitudes =
262 std::min(tl, tr) == 0 ? std::max(tl, tr) > 1e-280 : std::max(tl, tr) > 4 * std::min(tl, tr);
263 if (std::isfinite(ga) && std::isfinite(gb) && !(spansMagnitudes && (loFinite || hiFinite)))
264 break;
265 RealType middle = split(a, b);
266 if (!(middle > a && middle < b))
267 return middle;
268 double gm = g(middle);
269 if (gm == 0)
270 return middle;
271 if (gm < 0) {
272 a = middle;
273 ga = gm;
274 } else {
275 b = middle;
276 gb = gm;
277 }
278 }
279 auto [left, right] = math::toms748<RealType>([&g](RealType x) { return static_cast<RealType>(g(x)); }, a, b,
280 static_cast<RealType>(ga), static_cast<RealType>(gb));
281 return left + (right - left) / 2;
282}
283
284template <typename RealType>
286{
287 RealType guess = this->Mean();
288 if (!std::isfinite(guess))
289 guess = this->Median();
290 RealType root = 0;
291 math::findMin<RealType>([this](const RealType &x) { return -this->logf(x); }, guess, root);
292 return root;
293}
294
295template <typename RealType>
296long double ContinuousDistribution<RealType>::ExpectedValue(const std::function<double(RealType)> &funPtr,
297 RealType minPoint, RealType maxPoint) const
298{
301 const double lowerBoundary = std::max(minPoint, this->MinValue());
302 const double upperBoundary = std::min(maxPoint, this->MaxValue());
303 if (!(lowerBoundary < upperBoundary))
304 return 0.0;
305
308 double center = std::clamp<double>(this->Median(), lowerBoundary, upperBoundary);
309 if (!std::isfinite(center))
310 center = std::isfinite(lowerBoundary) ? lowerBoundary : (std::isfinite(upperBoundary) ? upperBoundary : 0.0);
311 double scale = 1.0 / this->f(center);
312 if (!(scale > 0) || !std::isfinite(scale))
313 scale = 1.0;
314 auto side = [this, &funPtr, center, scale](double end, double direction) -> long double {
315 const double ratio = direction * (end - center) / scale;
316 if (!(ratio > 0))
317 return 0.0;
318 const double limit = std::isfinite(ratio) ? ratio / (1 + ratio) : 1.0;
319 return math::integrate(
320 [this, &funPtr, center, scale, direction](double u) {
321 if (u >= 1.0)
322 return 0.0;
323 const double denominator = 1.0 - u;
324 const double x = center + direction * scale * u / denominator;
325 const double y = funPtr(x);
326 return (y == 0.0) ? 0.0 : y * this->f(x) * scale / (denominator * denominator);
327 },
328 0.0, limit);
329 };
330 return side(lowerBoundary, -1.0) + side(upperBoundary, 1.0);
331}
332
333template <typename RealType>
334double ContinuousDistribution<RealType>::Hazard(const RealType &x) const
335{
336 if (x < this->MinValue())
337 return 0.0;
338 if (x > this->MaxValue())
339 return NAN;
340 return this->f(x) / this->S(x);
341}
342
343template <typename RealType>
344double ContinuousDistribution<RealType>::LikelihoodFunction(std::span<const RealType> sample) const
345{
346 return std::exp(LogLikelihoodFunction(sample));
347}
348
349template <typename RealType>
350double ContinuousDistribution<RealType>::LogLikelihoodFunction(std::span<const RealType> sample) const
351{
352 long double res = 0.0;
353 for (const RealType &var : sample)
354 res += this->logf(var);
355 return res;
356}
357
358template <typename RealType>
359bool ContinuousDistribution<RealType>::KolmogorovSmirnovTest(std::span<const RealType> orderStatistic,
360 double alpha) const
361{
362 size_t n = orderStatistic.size();
363 if (n == 0)
364 throw std::invalid_argument("Order statistic should not be empty");
365 if (!(alpha > 0 && alpha < 1))
366 throw std::invalid_argument("Significance level should be positive and smaller than 1");
368 double K = KSRand.Quantile1m(alpha);
369 double interval = K / std::sqrt(n);
370 double nInv = 1.0 / n;
371 double Fn = 0.0;
372 for (size_t i = 1; i != n; ++i) {
373 RealType x = orderStatistic[i - 1];
374 if (x > orderStatistic[i])
375 throw std::invalid_argument("Order statistic should be sorted in ascending order");
376 double upperBound = Fn + interval;
377 Fn = i * nInv;
378 double lowerBound = Fn - interval;
379 double FReal = this->F(x);
380 if (FReal < lowerBound || FReal > upperBound)
381 return false;
382 }
383 double SReal = this->S(orderStatistic[n - 1]);
384 return (SReal > interval || SReal < nInv - interval) ? false : true;
385}
386
387} // namespace randlib
The ContinuousDistribution class Abstract class for all continuous distributions.
Definition continuous.hpp:22
void LogProbabilityDensityFunction(std::span< const RealType > x, std::span< double > y) const
fill vector y with logf(x)
Definition continuous.hpp:131
RealType quantileImpl(double p) const override
Definition continuous.hpp:146
RealType quantileImpl(double p, RealType initValue) const override
Definition continuous.hpp:140
bool KolmogorovSmirnovTest(std::span< const RealType > orderStatistic, double alpha) const
Definition continuous.hpp:359
virtual ~ContinuousDistribution()
Definition continuous.hpp:29
RealType searchQuantile(double p, bool upperTail, RealType guess, double relativeStep=1e-3) const
Deterministic root search for the quantile: brackets the root, starting from the guess when it is fin...
Definition continuous.hpp:164
RealType quantileImpl1m(double p, RealType initValue) const override
Definition continuous.hpp:152
virtual double logf(const RealType &x) const =0
std::complex< double > integrateCharacteristicFunction(double t) const
Definition continuous.hpp:105
RealType Mode() const override
Mode is the value, which has the largest probability to happen, or, in the case of continuous distrib...
Definition continuous.hpp:285
long double ExpectedValue(const std::function< double(RealType)> &funPtr, RealType minPoint, RealType maxPoint) const override
Definition continuous.hpp:296
void ProbabilityDensityFunction(std::span< const RealType > x, std::span< double > y) const
fill vector y with f(x)
Definition continuous.hpp:122
RealType quantileImpl1m(double p) const override
Definition continuous.hpp:158
double LikelihoodFunction(std::span< const RealType > sample) const override
Definition continuous.hpp:344
virtual double f(const RealType &x) const
Definition continuous.hpp:37
double LogLikelihoodFunction(std::span< const RealType > sample) const override
Definition continuous.hpp:350
ContinuousDistribution()
Definition continuous.hpp:26
double Hazard(const RealType &x) const override
Definition continuous.hpp:334
The KolmogorovSmirnovRand class Kolmogorov-Smirnov distribution.
Definition kolmogorov_smirnov.hpp:19
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
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 bivariate_normal.hpp:10