RandLib 2.0.0
Probability distributions, fitting, and heavy-tail tools for C++20
Loading...
Searching...
No Matches
stable.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <array>
5#include <functional>
6#include <vector>
7
13
14namespace randlib
15{
16
17namespace detail
18{
19
20inline double stableTanHalfPi(double alpha)
21{
22 const double reduced = std::remainder(0.5 * alpha, 1.0);
23 if (std::fabs(reduced) == 0.25)
24 return std::copysign(1.0, reduced);
25 return std::tan(randlib::consts::pi * reduced);
26}
27
28inline double stableAtan(double value)
29{
30 if (std::fabs(value) == 1.0)
31 return std::copysign(static_cast<double>(0.5L * randlib::consts::half_pi), value);
32 return math::atan(value);
33}
34
35} // namespace detail
36
49inline double stableTailQuantile(double p, bool upperTail, double alpha, double beta, double gamma, double mu)
50{
51 double weight = upperTail ? 1 + beta : 1 - beta;
52 if (alpha >= 2 || weight <= 0)
53 return NAN;
54 double C = (alpha == 1)
56 : std::tgamma(alpha) * std::sin(0.5 * randlib::consts::pi * alpha) * randlib::consts::inv_pi;
57 double distance = gamma * std::pow(C * weight / p, 1.0 / alpha);
58 return upperTail ? mu + distance : mu - distance;
59}
60
72template <typename RealType>
73class StableDistribution : virtual public ContinuousDistribution<RealType>
74{
75 RealType variate(detail::EngineRef g) const override { return (*this)(g); }
76 void sample(detail::EngineRef g, std::span<RealType> outputData) const override;
77
78protected:
81 {
82 S0,
83 S1
84 };
85
86 double alpha = 2;
87 double beta = 0;
88 double mu = 0;
91
92private:
93 double alphaInv = 0.5;
94 double zeta = 0;
95 double omega = 0;
96 double xi = 0;
97 double alpha_alpham1 = 2;
98 double logGammaPi_2 = randlib::consts::lnpi - 1.5 * randlib::consts::ln2;
99 Parameterization parameterization = Parameterization::S1;
100 double locationParameter = 0;
101
102 static constexpr double BIG_NUMBER = 1e9;
103 static constexpr double ALMOST_TWO = 1.99999;
104 static constexpr double S0_PROBABILITY_UNITY_TOLERANCE = 1.1e-8;
105 static constexpr double S0_DIRECT_KERNEL_ALPHA_TOLERANCE = 1e-5;
106 static constexpr double S0_DIRECT_KERNEL_RANGE = 100;
107 static constexpr double S0_SAMPLING_UNITY_TOLERANCE = 1e-10;
108
109 enum DISTRIBUTION_TYPE
110 {
111 NORMAL,
112 LEVY,
113 CAUCHY,
114 UNITY_EXPONENT,
115 GENERAL
116 };
117
118 DISTRIBUTION_TYPE distributionType = NORMAL;
119
120protected:
121 double pdfCoef =
123 double pdftailBound = INFINITY;
124 double cdftailBound = INFINITY;
125
126 StableDistribution(double exponent, double skewness, double scale = 1, double location = 0,
127 Parameterization selectedParameterization = Parameterization::S1);
128
129public:
131
132 [[nodiscard]] Support SupportType() const override;
133 [[nodiscard]] RealType MinValue() const override;
134 [[nodiscard]] RealType MaxValue() const override;
135
136private:
137 void parametersVerification(double exponent, double skewness, double scale);
138 void setParametersForNormal();
139 void setParametersForCauchy();
140 void setParametersForLevy();
141 void setParametersForUnityExponent();
142 void setParametersForGeneralExponent();
143 static double s0LocationShift(double exponent, double skewness, double scale);
144
145protected:
146 void SetParameters(double exponent, double skewness, double scale = 1, double location = 0);
147
148public:
149 void SetLocation(double location);
150 void SetScale(double scale);
151 void SetParameterization(Parameterization value) noexcept;
152
156 [[nodiscard]] inline double GetExponent() const noexcept { return alpha; }
160 [[nodiscard]] inline double GetSkewness() const noexcept { return beta; }
164 [[nodiscard]] inline double GetScale() const noexcept { return gamma; }
168 [[nodiscard]] inline double GetLocation() const noexcept { return locationParameter; }
172 [[nodiscard]] inline Parameterization GetParameterization() const noexcept { return parameterization; }
176 [[nodiscard]] inline double GetLogScale() const noexcept { return logGamma; }
177
178protected:
183 double pdfNormal(RealType x) const;
188 double logpdfNormal(RealType x) const;
193 double pdfCauchy(RealType x) const;
198 double logpdfCauchy(RealType x) const;
203 double pdfLevy(RealType x) const;
208 double logpdfLevy(RealType x) const;
209
210private:
215 static double fastpdfExponentiation(double u);
216
221 double pdfShortTailExpansionForUnityExponent(double x) const;
222 std::complex<double> unityTailIntegral(int power, int logarithmPower, double distance) const;
223 double unityTailExpansion(double distance, bool integrated) const;
224
230 double limitCaseForIntegrandAuxForUnityExponent(double theta, double xAdj) const;
236 double integrandAuxForUnityExponent(double theta, double xAdj) const;
242 double integrandForUnityExponent(double theta, double xAdj) const;
247 double pdfForUnityExponent(double x) const;
251 static double rootOfMonotone(const std::function<double(double)> &u, double a, double b);
255 static double integrateAroundRoot(const std::function<double(double)> &integrand,
256 const std::function<double(double)> &u, double a, double b, double root);
261 double peakForUnityExponent(double xAdj) const;
262
263 DoublePair seriesZeroParams{};
264
269 double pdfShortTailExpansionForGeneralExponent(double logX) const;
274 double cdfShortTailExpansion(double logX) const;
278 double pdfAtZero() const;
285 double pdfSeriesExpansionAtZero(double logX, double xiAdj, int k) const;
291 double pdfSeriesExpansionAtInf(double logX, double xiAdj) const;
296 double pdfTaylorExpansionTailNearCauchy(double x) const;
302 double limitCaseForIntegrandAuxForGeneralExponent(double theta, double xiAdj) const;
309 double integrandAuxForGeneralExponent(double theta, double xAdj, double xiAdj) const;
316 double integrandFoGeneralExponent(double theta, double xAdj, double xiAdj) const;
321 double pdfForGeneralExponent(double x) const;
322 [[nodiscard]] bool useS0UnityLimit() const noexcept;
323 double pdfS0UnityLimit(double x) const;
324 double cdfS0UnityLimit(double x, bool complement) const;
325 [[nodiscard]] bool useS0NearUnityKernel(double x) const noexcept;
326 double s0NearUnityPhase(double u, double standardizedX) const;
327 double pdfS0NearUnity(double x) const;
328 double cdfS0NearUnity(double x, bool complement) const;
329
330public:
331 [[nodiscard]] double f(const RealType &x) const override;
332 [[nodiscard]] double logf(const RealType &x) const override;
333
334protected:
339 double cdfNormal(RealType x) const;
344 double cdfNormalCompl(RealType x) const;
349 double cdfCauchy(RealType x) const;
354 double cdfCauchyCompl(RealType x) const;
359 double cdfLevy(RealType x) const;
364 double cdfLevyCompl(RealType x) const;
365
366private:
371 static double fastcdfExponentiation(double u);
376 static double fastccdfExponentiation(double u);
381 double cdfAtZero(double xiAdj) const;
387 double cdfForUnityExponent(double x, bool complement) const;
394 double cdfSeriesExpansionAtZero(double logX, double xiAdj, int k) const;
400 double cdfSeriesExpansionAtInf(double logX, double xiAdj) const;
406 double cdfIntegralRepresentation(double logX, double xiAdj, bool complement) const;
411 double cdfForGeneralExponent(double x) const;
416 double ccdfForGeneralExponent(double x) const;
417
418public:
419 [[nodiscard]] double F(const RealType &x) const override;
420 [[nodiscard]] double S(const RealType &x) const override;
421
422private:
426 template <std::uniform_random_bit_generator G>
427 double variateForUnityExponent(G &g) const;
428 template <std::uniform_random_bit_generator G>
429 double variateForS0UnityLimit(G &g) const;
433 template <std::uniform_random_bit_generator G>
434 double variateForGeneralExponent(G &g) const;
438 template <std::uniform_random_bit_generator G>
439 double variateForExponentEqualOneHalf(G &g) const;
440
441public:
442 template <std::uniform_random_bit_generator G>
443 [[nodiscard]] RealType operator()(G &g) const;
444
445 [[nodiscard]] long double Mean() const override;
446 [[nodiscard]] long double Variance() const override;
447 [[nodiscard]] RealType Median() const override;
448 [[nodiscard]] RealType Mode() const override;
449 [[nodiscard]] long double Skewness() const override;
450 [[nodiscard]] long double ExcessKurtosis() const override;
451
452protected:
457 RealType quantileNormal(double p) const;
462 RealType quantileNormal1m(double p) const;
467 RealType quantileCauchy(double p) const;
472 RealType quantileCauchy1m(double p) const;
477 RealType quantileLevy(double p) const;
482 RealType quantileLevy1m(double p) const;
483
484private:
490 RealType searchTailQuantile(double p, bool upperTail) const;
491 RealType quantileImpl(double p) const override;
492 RealType quantileImpl1m(double p) const override;
493
494protected:
499 std::complex<double> cfNormal(double t) const;
504 std::complex<double> cfCauchy(double t) const;
509 std::complex<double> cfLevy(double t) const;
510
511private:
512 std::complex<double> CFImpl(double t) const override;
513};
514
519template <typename RealType>
520class StableRand final : public StableDistribution<RealType>
521{
522public:
524
525 StableRand(double exponent = 2, double skewness = 0, double scale = 1, double location = 0,
526 Parameterization selectedParameterization = Parameterization::S1)
527 : StableDistribution<RealType>(exponent, skewness, scale, location, selectedParameterization)
528 {
529 }
530 [[nodiscard]] std::string Name() const override;
531
532 void SetExponent(double exponent);
533 void SetSkewness(double skewness);
534};
535
545template <typename RealType>
546class HoltsmarkRand final : public StableDistribution<RealType>
547{
548public:
549 HoltsmarkRand(double scale = 1, double location = 0) : StableDistribution<RealType>(1.5, 0.0, scale, location) {}
550 [[nodiscard]] std::string Name() const override;
551};
552
562template <typename RealType>
563class LandauRand final : public StableDistribution<RealType>
564{
565public:
566 LandauRand(double scale = 1, double location = 0) : StableDistribution<RealType>(1.0, 1.0, scale, location) {}
567 [[nodiscard]] std::string Name() const override;
568};
569
570} // namespace randlib
571
577
578namespace randlib
579{
580
581template <typename RealType>
582StableDistribution<RealType>::StableDistribution(double exponent, double skewness, double scale, double location,
583 Parameterization selectedParameterization)
584 : parameterization(selectedParameterization)
585{
586 SetParameters(exponent, skewness, scale, location);
587}
588
589template <typename RealType>
591{
592 if (alpha < 1) {
593 if (beta == 1)
595 if (beta == -1)
597 }
598 return Support::Infinite;
599}
600
601template <typename RealType>
603{
604 return (alpha < 1 && beta == 1) ? mu : -INFINITY;
605}
606
607template <typename RealType>
609{
610 return (alpha < 1 && beta == -1) ? mu : INFINITY;
611}
612
613template <typename RealType>
614void StableDistribution<RealType>::parametersVerification(double exponent, double skewness, double scale)
615{
616 if (!(exponent > 0.0 && exponent <= 2.0))
617 throw std::invalid_argument(
618 "Stable distribution: exponent should be in the interval (0, 2], but it's equal to " +
619 std::to_string(exponent));
620 if (!(std::fabs(skewness) <= 1.0))
621 throw std::invalid_argument(
622 "Stable distribution: skewness should be in the interval [-1, 1], but it's equal to " +
623 std::to_string(skewness));
624 if (!(scale > 0.0))
625 throw std::invalid_argument("Stable distribution: scale should be positive, but it's equal to " +
626 std::to_string(scale));
627}
628
629template <typename RealType>
630void StableDistribution<RealType>::setParametersForNormal()
631{
632 distributionType = NORMAL;
633 pdfCoef = randlib::consts::ln2 + logGamma + 0.5 * randlib::consts::lnpi;
634}
635
636template <typename RealType>
637void StableDistribution<RealType>::setParametersForCauchy()
638{
639 distributionType = CAUCHY;
640 pdfCoef = -logGamma - randlib::consts::lnpi;
641}
642
643template <typename RealType>
644void StableDistribution<RealType>::setParametersForLevy()
645{
646 distributionType = LEVY;
647 pdfCoef = logGamma - randlib::consts::ln2 - randlib::consts::lnpi;
648}
649
650template <typename RealType>
651void StableDistribution<RealType>::setParametersForUnityExponent()
652{
653 distributionType = UNITY_EXPONENT;
654 pdfCoef = 0.5 / (gamma * std::fabs(beta));
655 pdftailBound = 0; // not in the use for now
657 logGammaPi_2 = randlib::consts::lnpi - randlib::consts::ln2 - logGamma;
658}
659
660template <typename RealType>
661void StableDistribution<RealType>::setParametersForGeneralExponent()
662{
663 distributionType = GENERAL;
664 if (beta != 0.0) {
665 zeta = -beta * detail::stableTanHalfPi(alpha);
666 const double zetaSquared = zeta * zeta;
667 omega = (zetaSquared == 1.0) ? 0.5 * alphaInv * static_cast<double>(randlib::consts::ln2)
668 : 0.5 * alphaInv * std::log1pl(zetaSquared);
669 xi = alphaInv * detail::stableAtan(-zeta);
670 } else {
671 zeta = omega = xi = 0.0;
672 }
673 pdfCoef = randlib::consts::inv_pi * std::fabs(alpha_alpham1) / gamma;
674 pdftailBound = 3.0 / (1.0 + alpha) * randlib::consts::ln10;
675 cdftailBound = 3.0 / alpha * randlib::consts::ln10;
677 if (alpha <= ALMOST_TWO) {
678 seriesZeroParams.first = std::round(std::min(alpha * alpha * 40 + 1, 10.0));
679 seriesZeroParams.second =
680 -(alphaInv * 1.5 + 0.5) * randlib::consts::ln10;
681 } else {
682 seriesZeroParams.first = 85;
683 seriesZeroParams.second = randlib::consts::ln2 + randlib::consts::ln3;
684 }
685}
686
687template <typename RealType>
688double StableDistribution<RealType>::s0LocationShift(double exponent, double skewness, double scale)
689{
690 if (skewness == 0.0 || exponent == 2.0)
691 return 0.0;
692 if (exponent == 1.0)
693 return randlib::consts::two_inv_pi * skewness * scale * std::log(scale);
694 return skewness * scale * detail::stableTanHalfPi(exponent);
695}
696
697template <typename RealType>
698void StableDistribution<RealType>::SetParameters(double exponent, double skewness, double scale, double location)
699{
700 if (!std::isfinite(location))
701 throw std::invalid_argument("Stable distribution: location should be finite");
702 parametersVerification(exponent, skewness, scale);
703 alpha = exponent;
704 alphaInv = 1.0 / alpha;
705 beta = skewness;
706 locationParameter = location;
707 mu = locationParameter -
708 ((parameterization == Parameterization::S0) ? s0LocationShift(exponent, skewness, scale) : 0.0);
709 gamma = scale;
710 logGamma = std::log(gamma);
711 alpha_alpham1 = alpha / (alpha - 1.0);
712
714 if (alpha == 2.0)
715 return setParametersForNormal();
716 if (alpha == 1.0)
717 return (beta == 0.0) ? setParametersForCauchy() : setParametersForUnityExponent();
718 if (alpha == 0.5 && std::fabs(beta) == 1.0)
719 return setParametersForLevy();
720 return setParametersForGeneralExponent();
721}
722
723template <typename RealType>
725{
726 SetParameters(alpha, beta, gamma, location);
727}
728
729template <typename RealType>
731{
732 SetParameters(alpha, beta, scale, GetLocation());
733}
734
735template <typename RealType>
737{
738 parameterization = value;
739 locationParameter = mu + ((parameterization == Parameterization::S0) ? s0LocationShift(alpha, beta, gamma) : 0.0);
740}
741
742template <typename RealType>
744{
745 return std::exp(logpdfNormal(x));
746}
747
748template <typename RealType>
750{
751 double y = x - mu;
752 y *= 0.5 / gamma;
753 y *= y;
754 y += pdfCoef;
755 return -y;
756}
757
758template <typename RealType>
760{
761 double y = x - mu;
762 y *= y;
763 y /= gamma;
764 y += gamma;
765 return randlib::consts::inv_pi / y;
766}
767
768template <typename RealType>
770{
771 double x0 = x - mu;
772 x0 /= gamma;
773 double xSq = x0 * x0;
774 return pdfCoef - std::log1pl(xSq);
775}
776
777template <typename RealType>
779{
780 return (x <= mu) ? 0.0 : std::exp(logpdfLevy(x));
781}
782
783template <typename RealType>
785{
786 double x0 = x - mu;
787 if (x0 <= 0.0)
788 return -INFINITY;
789 double y = gamma / x0;
790 y += 3 * std::log(x0);
791 y -= pdfCoef;
792 return -0.5 * y;
793}
794
795template <typename RealType>
797{
799 return (u < -700) ? std::exp(u) : std::exp(u - std::exp(u));
800}
801
802template <typename RealType>
803double StableDistribution<RealType>::pdfShortTailExpansionForUnityExponent(double x) const
804{
805 if (x > 10)
806 return 0.0;
807 double xm1 = x - 1.0;
808 double y = 0.5 * xm1 - std::exp(xm1);
810 return std::exp(y);
811}
812
813template <typename RealType>
814std::complex<double> StableDistribution<RealType>::unityTailIntegral(int power, int logarithmPower,
815 double distance) const
816{
817 const double absoluteDistance = std::fabs(distance);
818 const double direction = std::copysign(1.0, distance);
819 const double halfPi = static_cast<double>(randlib::consts::half_pi);
820 const std::complex<double> imaginary(0, 1);
821 const std::complex<double> integral = std::tgamma(power + 1.0) * std::pow(absoluteDistance, -power - 1.0) *
822 std::exp(imaginary * direction * halfPi * (power + 1.0));
823 if (logarithmPower == 0)
824 return integral;
825
826 std::array<std::complex<double>, 9> derivatives{};
827 derivatives[1] = math::digamma(power + 1.0) - std::log(absoluteDistance) + imaginary * direction * halfPi;
828 for (int order = 2; order <= logarithmPower; ++order) {
829 double harmonic = 0;
830 for (int term = 1; term <= power; ++term)
831 harmonic += std::pow(static_cast<double>(term), -order);
832 const double factorial = std::tgamma(static_cast<double>(order));
833 const double sign = (order % 2 == 0) ? 1.0 : -1.0;
834 derivatives[order] = sign * factorial * (math::riemannZeta(static_cast<double>(order)) - harmonic);
835 }
836
837 std::array<std::complex<double>, 9> bell{};
838 bell[0] = 1;
839 for (int order = 1; order <= logarithmPower; ++order) {
840 double combination = 1;
841 for (int part = 1; part <= order; ++part) {
842 bell[order] += combination * bell[order - part] * derivatives[part];
843 combination *= static_cast<double>(order - part) / part;
844 }
845 }
846 return integral * bell[logarithmPower];
847}
848
849template <typename RealType>
850double StableDistribution<RealType>::unityTailExpansion(double distance, bool integrated) const
851{
852 static constexpr int ORDER = 8;
853 const std::complex<double> imaginary(0, 1);
854 const double logarithmicCoefficient = randlib::consts::two_inv_pi * beta * gamma;
855 std::complex<double> sum = 0;
856 for (int totalOrder = 1; totalOrder <= ORDER; ++totalOrder) {
857 for (int logarithmPower = 0; logarithmPower <= totalOrder; ++logarithmPower) {
858 const int exponentialPower = totalOrder - logarithmPower;
859 const std::complex<double> coefficient =
860 std::pow(-gamma, exponentialPower) / std::tgamma(exponentialPower + 1.0) *
861 std::pow(imaginary * logarithmicCoefficient, logarithmPower) / std::tgamma(logarithmPower + 1.0);
862 const int power = totalOrder - (integrated ? 1 : 0);
863 sum += coefficient * unityTailIntegral(power, logarithmPower, distance);
864 }
865 }
866 const double inversePi = static_cast<double>(randlib::consts::inv_pi);
867 if (!integrated)
868 return std::max(0.0, sum.real() * inversePi);
869 return std::clamp(-std::copysign(1.0, distance) * sum.imag() * inversePi, 0.0, 1.0);
870}
871
872template <typename RealType>
873double StableDistribution<RealType>::limitCaseForIntegrandAuxForUnityExponent(double theta, double xAdj) const
874{
875 if (theta > 0.0) {
876 if (beta > 0.0)
877 return BIG_NUMBER;
878 return (beta == -1) ? xAdj - 1.0 : -BIG_NUMBER;
879 }
880 if (beta < 0.0)
881 return BIG_NUMBER;
882 return (beta == 1) ? xAdj - 1.0 : -BIG_NUMBER;
883}
884
885template <typename RealType>
886double StableDistribution<RealType>::integrandAuxForUnityExponent(double theta, double xAdj) const
887{
888 if (std::fabs(theta) >= randlib::consts::half_pi)
889 return limitCaseForIntegrandAuxForUnityExponent(theta, xAdj);
890 if (theta == 0.0)
892 double thetaAdj = (randlib::consts::half_pi + beta * theta) / std::cos(theta);
893 double u = std::log(thetaAdj);
894 u += thetaAdj * std::sin(theta) / beta;
895 return std::isfinite(u) ? u + xAdj : limitCaseForIntegrandAuxForUnityExponent(theta, xAdj);
896}
897
898template <typename RealType>
899double StableDistribution<RealType>::integrandForUnityExponent(double theta, double xAdj) const
900{
901 if (std::fabs(theta) >= randlib::consts::half_pi)
902 return 0.0;
903 double u = integrandAuxForUnityExponent(theta, xAdj);
904 return fastpdfExponentiation(u);
905}
906
907template <typename RealType>
908double StableDistribution<RealType>::rootOfMonotone(const std::function<double(double)> &u, double a, double b)
909{
911 bool increasing = u(b) > u(a);
912 for (int i = 0; i < 200; ++i) {
913 double middle = 0.5 * (a + b);
914 if (!(middle > a && middle < b))
915 break;
916 bool belowRoot = (u(middle) < 0) == increasing;
917 (belowRoot ? a : b) = middle;
918 }
919 return 0.5 * (a + b);
920}
921
922template <typename RealType>
923double StableDistribution<RealType>::integrateAroundRoot(const std::function<double(double)> &integrand,
924 const std::function<double(double)> &u, double a, double b,
925 double root)
926{
931 double h = 1e-7 * std::max(std::min(root - a, b - root), 1e-300);
932 double slope = (u(root + h) - u(root - h)) / (2 * h);
933 double width = (std::isfinite(slope) && slope != 0.0) ? 1.0 / std::fabs(slope) : b - a;
934 std::vector<double> points = {a, root, b};
935 for (double scale : {1e-3, 1e-2, 1e-1, 1.0, 10.0, 100.0, 1000.0}) {
936 points.push_back(root - scale * width);
937 points.push_back(root + scale * width);
938 }
939 std::sort(points.begin(), points.end());
940 double sum = 0.0, left = a;
941 for (double point : points) {
942 if (point <= left || point > b)
943 continue;
944 sum += math::integrate(integrand, left, point, 1e-11, 262144);
945 left = point;
946 }
947 return sum;
948}
949
950template <typename RealType>
951double StableDistribution<RealType>::peakForUnityExponent(double xAdj) const
952{
953 return rootOfMonotone([this, xAdj](double theta) { return integrandAuxForUnityExponent(theta, xAdj); },
955}
956
957template <typename RealType>
958double StableDistribution<RealType>::pdfForUnityExponent(double x) const
959{
960 if (std::fabs(x - mu) >= 100 * gamma)
961 return unityTailExpansion(x - mu, false);
962 double xSt = (x - mu) / gamma;
963 double xAdj = -randlib::consts::half_pi * xSt / beta - logGammaPi_2;
966 double theta0 = peakForUnityExponent(xAdj);
967 auto integrand = [this, xAdj](double theta) { return integrandForUnityExponent(theta, xAdj); };
968 auto u = [this, xAdj](double theta) { return integrandAuxForUnityExponent(theta, xAdj); };
969 return pdfCoef * integrateAroundRoot(integrand, u, -randlib::consts::half_pi, randlib::consts::half_pi, theta0);
970}
971
972template <typename RealType>
973double StableDistribution<RealType>::pdfShortTailExpansionForGeneralExponent(double logX) const
974{
975 double logAlpha = std::log(alpha);
976 double log1mAlpha = (alpha < 1) ? std::log1pl(-alpha) : std::log(alpha - 1);
977 double temp = logX - logAlpha;
978 double y = std::exp(alpha_alpham1 * temp);
979 y *= -std::fabs(1.0 - alpha);
980 double z = (1.0 - 0.5 * alpha) / (alpha - 1) * temp;
981 z -= 0.5 * (randlib::consts::ln2 + randlib::consts::lnpi + logAlpha + log1mAlpha);
982 return std::exp(y + z);
983}
984
985template <typename RealType>
986double StableDistribution<RealType>::cdfShortTailExpansion(double logX) const
987{
990 double logU = alpha / (alpha - 1.0) * (logX - std::log(alpha));
991 double u = std::exp(logU);
992 double y = -(1.0 - alpha) * u -
993 0.5 * (randlib::consts::ln2 + randlib::consts::lnpi + std::log(alpha) + std::log1p(-alpha) + logU);
994 return std::exp(y);
995}
996
997template <typename RealType>
998double StableDistribution<RealType>::pdfAtZero() const
999{
1000 double y0 = 0.0;
1001 if (beta == 0.0)
1002 y0 = std::tgammal(alphaInv);
1003 else {
1004 y0 = std::lgammal(alphaInv) - omega;
1005 y0 = std::exp(y0) * std::cos(xi);
1006 }
1007 return y0 * randlib::consts::inv_pi / alpha;
1008}
1009
1010template <typename RealType>
1011double StableDistribution<RealType>::pdfSeriesExpansionAtZero(double logX, double xiAdj, int k) const
1012{
1015 double y0 = pdfAtZero();
1016 double sum = 0.0;
1017 if (beta == 0.0) {
1019 for (int n = 1; n <= k; ++n) {
1020 int n2 = n + n;
1021 double term = std::lgammal((n2 + 1) / alpha);
1022 term += n2 * logX;
1023 term -= math::lfact(n2);
1024 term = std::exp(term);
1025 sum += (n & 1) ? -term : term;
1026 }
1027 } else {
1029 double rhoPi_alpha = randlib::consts::half_pi + xiAdj;
1030 for (int n = 1; n <= k; ++n) {
1031 int np1 = n + 1;
1032 double term = std::lgammal(np1 * alphaInv);
1033 term += n * logX;
1034 term -= math::lfact(n);
1035 term = std::exp(term - omega);
1036 term *= std::sin(np1 * rhoPi_alpha);
1037 sum += (n & 1) ? -term : term;
1038 }
1039 }
1040 return y0 + sum * randlib::consts::inv_pi / alpha;
1041}
1042
1043template <typename RealType>
1044double StableDistribution<RealType>::pdfSeriesExpansionAtInf(double logX, double xiAdj) const
1045{
1046 static constexpr int k = 10;
1047 double rhoPi = randlib::consts::half_pi + xiAdj;
1048 rhoPi *= alpha;
1049 double sum = 0.0;
1050 for (int n = 1; n <= k; ++n) {
1051 double aux = n * alpha + 1.0;
1052 double term = std::lgammal(aux);
1053 term -= aux * logX;
1054 term -= math::lfact(n);
1055 term = std::exp(term - omega);
1056 term *= std::sin(rhoPi * n);
1057 sum += (n & 1) ? term : -term;
1058 }
1059 return randlib::consts::inv_pi * sum;
1060}
1061
1062template <typename RealType>
1063double StableDistribution<RealType>::pdfTaylorExpansionTailNearCauchy(double x) const
1064{
1065 double xSq = x * x;
1066 double y = 1.0 + xSq;
1067 double ySq = y * y;
1068 double z = math::atan(x);
1069 double zSq = z * z;
1070 double logY = std::log1pl(xSq);
1071 double alpham1 = alpha - 1.0;
1072 double temp = 1.0 - randlib::consts::egamma - 0.5 * logY;
1074 double f_a = temp;
1075 f_a *= xSq - 1.0;
1076 f_a += 2 * x * z;
1077 f_a /= ySq;
1078 static constexpr long double M_PI_SQ_6 = 1.64493406684822643647l;
1080 double f_aa1 = M_PI_SQ_6;
1081 f_aa1 += temp * temp;
1082 f_aa1 -= 1.0 + z * z;
1083 f_aa1 *= xSq * xSq - 6.0 * xSq + 1.0;
1084 double f_aa2 = 0.5 + temp;
1085 f_aa2 *= z;
1086 f_aa2 *= 8 * x * (xSq - 1.0);
1087 double f_aa3 = (1.0 - 3 * xSq) * temp;
1088 f_aa3 -= x * y * z;
1089 f_aa3 += f_aa3;
1090 double f_aa = f_aa1 + f_aa2 + f_aa3;
1091 f_aa /= std::pow(y, 3);
1094 static constexpr int gammaTable[] = {1, 2, 6};
1096 static constexpr long double gammaDerTable[] = {1.0 - randlib::consts::egamma, 3.0 - 2.0 * randlib::consts::egamma,
1097 11.0 - 6.0 * randlib::consts::egamma};
1099 static constexpr long double gammaSecDerTable[] = {0.82368066085287938958l, 2.49292999190269305794l,
1100 11.1699273161019477314l};
1102 static constexpr long double digammaTable[] = {1.0 - randlib::consts::egamma, 1.5 - randlib::consts::egamma,
1103 11.0 / 6 - randlib::consts::egamma};
1105 static constexpr long double digammaDerTable[] = {M_PI_SQ_6 - 1.0, M_PI_SQ_6 - 1.25, M_PI_SQ_6 - 49.0 / 36};
1107 static constexpr long double digammaSecDerTable[] = {-0.40411380631918857080l, -0.15411380631918857080l,
1108 -0.08003973224511449673l};
1110 double gTable[] = {0, 0, 0};
1111 for (int i = 0; i < 3; ++i) {
1112 double g_11 = 0.25 * gammaTable[i] * logY * logY;
1113 g_11 -= gammaDerTable[i] * logY;
1114 g_11 += gammaSecDerTable[i];
1115 double aux = digammaTable[i] - 0.5 * logY;
1116 double g_12 = aux;
1117 double zip2 = z * (i + 2);
1118 double cosZNu = std::cos(zip2), zSinZNu = z * std::sin(zip2);
1119 g_12 *= cosZNu;
1120 g_12 -= zSinZNu;
1121 double g_1 = g_11 * g_12;
1122 double g_21 = -gammaTable[i] * logY + 2 * gammaDerTable[i];
1123 double g_22 = -zSinZNu * aux;
1124 g_22 -= zSq * cosZNu;
1125 g_22 += cosZNu * digammaDerTable[i];
1126 double g_2 = g_21 * g_22;
1127 double g_3 = -zSq * cosZNu * aux;
1128 g_3 -= 2 * zSinZNu * digammaDerTable[i];
1129 g_3 += zSq * zSinZNu;
1130 g_3 += cosZNu * digammaSecDerTable[i];
1131 g_3 *= gammaTable[i];
1132 double g = g_1 + g_2 + g_3;
1133 g *= std::pow(y, -0.5 * i - 1);
1134 gTable[i] = g;
1135 }
1136 double f_aaa = -gTable[0] + 3 * gTable[1] - gTable[2];
1138 double tail = f_a * alpham1;
1139 tail += 0.5 * f_aa * alpham1 * alpham1;
1140 tail += std::pow(alpham1, 3) * f_aaa / 6.0;
1141 tail /= randlib::consts::pi;
1142 return tail;
1143}
1144
1145template <typename RealType>
1146double StableDistribution<RealType>::limitCaseForIntegrandAuxForGeneralExponent(double theta, double xiAdj) const
1147{
1149 if (theta < 0.5 * (randlib::consts::half_pi - xiAdj))
1150 return alpha < 1 ? -BIG_NUMBER : BIG_NUMBER;
1151 return alpha < 1 ? BIG_NUMBER : -BIG_NUMBER;
1152}
1153
1154template <typename RealType>
1155double StableDistribution<RealType>::integrandAuxForGeneralExponent(double theta, double xAdj, double xiAdj) const
1156{
1157 const double halfPi = static_cast<double>(randlib::consts::half_pi);
1159 if (theta >= halfPi && alpha > 1.0 && std::fabs(beta) == 1.0 && xiAdj > 0.0)
1160 return xAdj + std::log(alpha - 1.0) - alpha_alpham1 * std::log(alpha);
1161 if (std::fabs(theta) >= halfPi || theta <= -xiAdj)
1162 return limitCaseForIntegrandAuxForGeneralExponent(theta, xiAdj);
1163 double thetaAdj = alpha * (theta + xiAdj);
1164 double sinThetaAdj = std::sin(thetaAdj);
1165 double y = std::log(std::cos(theta));
1166 y -= alpha * std::log(sinThetaAdj);
1167 y /= alpha - 1.0;
1168 y += std::log(std::cos(thetaAdj - theta));
1169 y += xAdj;
1170 return std::isfinite(y) ? y : limitCaseForIntegrandAuxForGeneralExponent(theta, xiAdj);
1171}
1172
1173template <typename RealType>
1174double StableDistribution<RealType>::integrandFoGeneralExponent(double theta, double xAdj, double xiAdj) const
1175{
1176 if (std::fabs(theta) >= randlib::consts::half_pi)
1177 return 0.0;
1178 if (theta <= -xiAdj)
1179 return 0.0;
1180 double u = integrandAuxForGeneralExponent(theta, xAdj, xiAdj);
1181 return fastpdfExponentiation(u);
1182}
1183
1184template <typename RealType>
1185double StableDistribution<RealType>::pdfForGeneralExponent(double x) const
1186{
1188 double xSt = (x - mu) / gamma;
1189 double absXSt = xSt;
1191 double xiAdj = xi;
1192 if (xSt > 0) {
1193 if (alpha < 1 && beta == -1)
1194 return 0.0;
1195 } else {
1196 if (alpha < 1 && beta == 1)
1197 return 0.0;
1198 absXSt = -xSt;
1199 xiAdj = -xi;
1200 }
1201
1203 if (beta == 0.0 && std::fabs(alpha - 1.0) < 0.01)
1204 return pdfCauchy(x) + pdfTaylorExpansionTailNearCauchy(absXSt) / gamma;
1205
1207 if (xSt == 0.0)
1208 return pdfAtZero() / gamma;
1209
1210 double logAbsX = std::log(absXSt) - omega;
1211
1213 if (logAbsX < seriesZeroParams.second) {
1214 if (alpha < 1 && std::fabs(beta) == 1)
1215 return pdfShortTailExpansionForGeneralExponent(logAbsX);
1216 return pdfSeriesExpansionAtZero(logAbsX, xiAdj, seriesZeroParams.first) / gamma;
1217 }
1218
1220 if (logAbsX > pdftailBound && alpha <= ALMOST_TWO) {
1222 double skewnessTowardsX = (xSt > 0) ? beta : -beta;
1223 if (alpha > 1 && skewnessTowardsX == -1)
1224 return pdfShortTailExpansionForGeneralExponent(logAbsX);
1225 return pdfSeriesExpansionAtInf(logAbsX, xiAdj) / gamma;
1226 }
1227
1228 double xAdj = alpha_alpham1 * logAbsX;
1229
1231 auto u = [this, xAdj, xiAdj](double theta) { return integrandAuxForGeneralExponent(theta, xAdj, xiAdj); };
1232 auto integrand = [this, xAdj, xiAdj](double theta) { return integrandFoGeneralExponent(theta, xAdj, xiAdj); };
1233 double theta0 = rootOfMonotone(u, -xiAdj, randlib::consts::half_pi);
1234 double integral = integrateAroundRoot(integrand, u, -xiAdj, randlib::consts::half_pi, theta0);
1235 double res = pdfCoef * integral / absXSt;
1236
1238 if (alpha <= ALMOST_TWO)
1239 return res;
1240
1243 double alphap1 = alpha + 1.0;
1244 double tail = std::lgammal(alphap1);
1245 tail -= alphap1 * logAbsX;
1246 tail = std::exp(tail);
1247 tail *= (1.0 - 0.5 * alpha) / gamma;
1248 return std::max(tail, res);
1249}
1250
1251template <typename RealType>
1252bool StableDistribution<RealType>::useS0UnityLimit() const noexcept
1253{
1254 return parameterization == Parameterization::S0 && alpha != 1.0 && beta != 0.0 &&
1255 std::fabs(alpha - 1.0) <= S0_PROBABILITY_UNITY_TOLERANCE;
1256}
1257
1258template <typename RealType>
1259double StableDistribution<RealType>::pdfS0UnityLimit(double x) const
1260{
1261 const double unityMu = locationParameter - randlib::consts::two_inv_pi * beta * gamma * logGamma;
1262 if (std::fabs(x - unityMu) >= 100 * gamma)
1263 return unityTailExpansion(x - unityMu, false);
1264 const double standardizedX = (x - unityMu) / gamma;
1265 const double unityLogGammaPi2 = randlib::consts::lnpi - randlib::consts::ln2 - logGamma;
1266 const double xAdjusted = -randlib::consts::half_pi * standardizedX / beta - unityLogGammaPi2;
1267 const double theta0 = peakForUnityExponent(xAdjusted);
1268 const auto integrand = [this, xAdjusted](double theta) { return integrandForUnityExponent(theta, xAdjusted); };
1269 const auto auxiliary = [this, xAdjusted](double theta) { return integrandAuxForUnityExponent(theta, xAdjusted); };
1270 const double coefficient = 0.5 / (gamma * std::fabs(beta));
1271 return coefficient *
1272 integrateAroundRoot(integrand, auxiliary, -randlib::consts::half_pi, randlib::consts::half_pi, theta0);
1273}
1274
1275template <typename RealType>
1276double StableDistribution<RealType>::cdfS0UnityLimit(double x, bool complement) const
1277{
1278 const double unityMu = locationParameter - randlib::consts::two_inv_pi * beta * gamma * logGamma;
1279 if (std::fabs(x - unityMu) >= 100 * gamma) {
1280 const double tail = unityTailExpansion(x - unityMu, true);
1281 return complement == (x < unityMu) ? 1.0 - tail : tail;
1282 }
1283 const double standardizedX = (x - unityMu) / gamma;
1284 const double unityLogGammaPi2 = randlib::consts::lnpi - randlib::consts::ln2 - logGamma;
1285 const double xAdjusted = -randlib::consts::half_pi * standardizedX / beta - unityLogGammaPi2;
1286 const bool useComplement = complement == (beta > 0);
1287 const auto integrand = [this, xAdjusted, useComplement](double theta) {
1288 const double value = integrandAuxForUnityExponent(theta, xAdjusted);
1289 return useComplement ? fastccdfExponentiation(value) : fastcdfExponentiation(value);
1290 };
1291 const double theta0 = peakForUnityExponent(xAdjusted);
1292 const auto auxiliary = [this, xAdjusted](double theta) { return integrandAuxForUnityExponent(theta, xAdjusted); };
1294 integrateAroundRoot(integrand, auxiliary, -randlib::consts::half_pi, randlib::consts::half_pi, theta0);
1295}
1296
1297template <typename RealType>
1298bool StableDistribution<RealType>::useS0NearUnityKernel(double x) const noexcept
1299{
1300 const double standardizedX = (x - locationParameter) / gamma;
1301 return parameterization == Parameterization::S0 && alpha != 1.0 && beta != 0.0 &&
1302 std::fabs(alpha - 1.0) < S0_DIRECT_KERNEL_ALPHA_TOLERANCE &&
1303 std::fabs(standardizedX) <= S0_DIRECT_KERNEL_RANGE;
1304}
1305
1306template <typename RealType>
1307double StableDistribution<RealType>::s0NearUnityPhase(double u, double standardizedX) const
1308{
1309 const double epsilon = alpha - 1.0;
1310 const double difference = std::expm1(epsilon * std::log(u));
1311 const double skewPhase = -beta * u * difference / std::tan(randlib::consts::half_pi * epsilon);
1312 return skewPhase - standardizedX * u;
1313}
1314
1315template <typename RealType>
1316double StableDistribution<RealType>::pdfS0NearUnity(double x) const
1317{
1318 const double standardizedX = (x - locationParameter) / gamma;
1319 const double cutoff = std::pow(36.0, 1.0 / alpha);
1320 const double integral = math::integrateTanhSinh(
1321 [this, standardizedX](double u) {
1322 const double attenuation = std::exp(-std::pow(u, alpha));
1323 return attenuation * std::cos(s0NearUnityPhase(u, standardizedX));
1324 },
1325 0.0, cutoff, 2e-13);
1326 return std::max(0.0, integral * static_cast<double>(randlib::consts::inv_pi) / gamma);
1327}
1328
1329template <typename RealType>
1330double StableDistribution<RealType>::cdfS0NearUnity(double x, bool complement) const
1331{
1332 const double standardizedX = (x - locationParameter) / gamma;
1333 const double cutoff = std::pow(36.0, 1.0 / alpha);
1334 const double integral = math::integrateTanhSinh(
1335 [this, standardizedX](double u) {
1336 const double attenuation = std::exp(-std::pow(u, alpha));
1337 return attenuation * std::sin(s0NearUnityPhase(u, standardizedX)) / u;
1338 },
1339 0.0, cutoff, 2e-13);
1340 const double correction = integral * static_cast<double>(randlib::consts::inv_pi);
1341 return std::clamp(complement ? 0.5 + correction : 0.5 - correction, 0.0, 1.0);
1342}
1343
1344template <typename RealType>
1345double StableDistribution<RealType>::f(const RealType &x) const
1346{
1347 if (useS0UnityLimit())
1348 return pdfS0UnityLimit(x);
1349 if (useS0NearUnityKernel(x))
1350 return pdfS0NearUnity(x);
1351 switch (distributionType) {
1352 case NORMAL:
1353 return pdfNormal(x);
1354 case CAUCHY:
1355 return pdfCauchy(x);
1356 case LEVY:
1357 return (beta > 0) ? pdfLevy(x) : pdfLevy(2 * mu - x);
1358 case UNITY_EXPONENT:
1359 return pdfForUnityExponent(x);
1360 case GENERAL:
1361 return pdfForGeneralExponent(x);
1362 default:
1363 throw std::runtime_error("Stable distribution: invalid distribution type");
1364 }
1365}
1366
1367template <typename RealType>
1368double StableDistribution<RealType>::logf(const RealType &x) const
1369{
1370 if (useS0UnityLimit())
1371 return std::log(pdfS0UnityLimit(x));
1372 if (useS0NearUnityKernel(x))
1373 return std::log(pdfS0NearUnity(x));
1374 switch (distributionType) {
1375 case NORMAL:
1376 return logpdfNormal(x);
1377 case CAUCHY:
1378 return logpdfCauchy(x);
1379 case LEVY:
1380 return (beta > 0) ? logpdfLevy(x) : logpdfLevy(2 * mu - x);
1381 case UNITY_EXPONENT:
1382 return std::log(pdfForUnityExponent(x));
1383 case GENERAL:
1384 return std::log(pdfForGeneralExponent(x));
1385 default:
1386 throw std::runtime_error("Stable distribution: invalid distribution type");
1387 }
1388}
1389
1390template <typename RealType>
1392{
1393 double y = mu - x;
1394 y *= 0.5 / gamma;
1395 return 0.5 * std::erfc(y);
1396}
1397
1398template <typename RealType>
1400{
1401 double y = x - mu;
1402 y *= 0.5 / gamma;
1403 return 0.5 * std::erfc(y);
1404}
1405
1406template <typename RealType>
1408{
1409 double x0 = x - mu;
1410 x0 /= gamma;
1412 return (x0 < -1.0) ? randlib::consts::inv_pi * std::atan(-1.0 / x0) : 0.5 + randlib::consts::inv_pi * std::atan(x0);
1413}
1414
1415template <typename RealType>
1417{
1418 double x0 = mu - x;
1419 x0 /= gamma;
1420 return (x0 < -1.0) ? randlib::consts::inv_pi * std::atan(-1.0 / x0) : 0.5 + randlib::consts::inv_pi * std::atan(x0);
1421}
1422
1423template <typename RealType>
1425{
1426 if (x <= mu)
1427 return 0;
1428 double y = x - mu;
1429 y += y;
1430 y = gamma / y;
1431 y = std::sqrt(y);
1432 return std::erfc(y);
1433}
1434
1435template <typename RealType>
1437{
1438 if (x <= mu)
1439 return 1.0;
1440 double y = x - mu;
1441 y += y;
1442 y = gamma / y;
1443 y = std::sqrt(y);
1444 return std::erf(y);
1445}
1446
1447template <typename RealType>
1449{
1451 return std::exp(-std::exp(u));
1452}
1453
1454template <typename RealType>
1455double StableDistribution<RealType>::fastccdfExponentiation(double u)
1456{
1457 return (u > 5.0) ? 1.0 : -std::expm1(-std::exp(u));
1458}
1459
1460template <typename RealType>
1461double StableDistribution<RealType>::cdfAtZero(double xiAdj) const
1462{
1463 return 0.5 - randlib::consts::inv_pi * xiAdj;
1464}
1465
1466template <typename RealType>
1467double StableDistribution<RealType>::cdfForUnityExponent(double x, bool complement) const
1468{
1472 if (std::fabs(x - mu) >= 100 * gamma) {
1473 const double tail = unityTailExpansion(x - mu, true);
1474 return complement == (x < mu) ? 1.0 - tail : tail;
1475 }
1476 double xSt = (x - mu) / gamma;
1477 double xAdj = -randlib::consts::half_pi * xSt / beta - logGammaPi_2;
1478 bool useComplement = complement == (beta > 0);
1479 auto integrand = [this, xAdj, useComplement](double theta) {
1480 double u = integrandAuxForUnityExponent(theta, xAdj);
1481 return useComplement ? fastccdfExponentiation(u) : fastcdfExponentiation(u);
1482 };
1483 double theta0 = peakForUnityExponent(xAdj);
1484 auto u = [this, xAdj](double theta) { return integrandAuxForUnityExponent(theta, xAdj); };
1486 integrateAroundRoot(integrand, u, -randlib::consts::half_pi, randlib::consts::half_pi, theta0);
1487}
1488
1489template <typename RealType>
1490double StableDistribution<RealType>::cdfSeriesExpansionAtZero(double logX, double xiAdj, int k) const
1491{
1494 double y0 = cdfAtZero(xiAdj);
1495 double sum = 0.0;
1496 if (beta == 0.0) {
1498 for (int m = 0; m <= k; ++m) {
1499 int m2p1 = 2 * m + 1;
1500 double term = std::lgammal(m2p1 * alphaInv);
1501 term += m2p1 * logX;
1502 term -= math::lfact(m2p1);
1503 term = std::exp(term);
1504 sum += (m & 1) ? -term : term;
1505 }
1506 } else {
1508 double rhoPi_alpha = randlib::consts::half_pi + xiAdj;
1509 for (int n = 1; n <= k; ++n) {
1510 double term = std::lgammal(n * alphaInv);
1511 term += n * logX;
1512 term -= math::lfact(n);
1513 term = std::exp(term);
1514 term *= std::sin(n * rhoPi_alpha);
1515 sum += (n & 1) ? term : -term;
1516 }
1517 }
1518 return y0 + sum * randlib::consts::inv_pi * alphaInv;
1519}
1520
1521template <typename RealType>
1522double StableDistribution<RealType>::cdfSeriesExpansionAtInf(double logX, double xiAdj) const
1523{
1524 static constexpr int k = 10;
1525 double rhoPi = randlib::consts::half_pi + xiAdj;
1526 rhoPi *= alpha;
1527 double sum = 0.0;
1528 for (int n = 1; n <= k; ++n) {
1529 double aux = n * alpha;
1530 double term = std::lgammal(aux);
1531 term -= aux * logX;
1532 term -= math::lfact(n);
1533 term = std::exp(term);
1534 term *= std::sin(rhoPi * n);
1535 sum += (n & 1) ? term : -term;
1536 }
1537 return randlib::consts::inv_pi * sum;
1538}
1539
1540template <typename RealType>
1541double StableDistribution<RealType>::cdfIntegralRepresentation(double logX, double xiAdj, bool complement) const
1542{
1544 double xAdj = alpha_alpham1 * logX;
1545 auto u = [this, xAdj, xiAdj](double theta) { return integrandAuxForGeneralExponent(theta, xAdj, xiAdj); };
1546 auto integrand = [&u, complement](double theta) {
1547 return complement ? fastccdfExponentiation(u(theta)) : fastcdfExponentiation(u(theta));
1548 };
1549 double theta0 = rootOfMonotone(u, -xiAdj, randlib::consts::half_pi);
1550 return static_cast<double>(randlib::consts::inv_pi) *
1551 integrateAroundRoot(integrand, u, -xiAdj, randlib::consts::half_pi, theta0);
1552}
1553
1554template <typename RealType>
1555double StableDistribution<RealType>::cdfForGeneralExponent(double x) const
1556{
1557 double xSt = (x - mu) / gamma;
1558 if (xSt == 0) {
1560 if (alpha < 1 && std::fabs(beta) == 1)
1561 return (beta > 0) ? 0.0 : 1.0;
1562 return cdfAtZero(xi);
1563 }
1564 if (xSt > 0.0) {
1565 if (alpha > 1.0)
1566 return 1.0 - ccdfForGeneralExponent(x);
1568 if (beta == -1.0)
1569 return 1.0;
1570 double logAbsX = std::log(xSt) - omega;
1571 if (logAbsX > cdftailBound)
1572 return 1.0 - cdfSeriesExpansionAtInf(logAbsX, xi);
1573 if (logAbsX < seriesZeroParams.second)
1574 return (beta == 1.0) ? cdfShortTailExpansion(logAbsX)
1575 : cdfSeriesExpansionAtZero(logAbsX, xi, seriesZeroParams.first);
1576 double atZero = (beta == 1.0) ? 0.0 : cdfAtZero(xi);
1577 return atZero + cdfIntegralRepresentation(logAbsX, xi, false);
1578 }
1580 double logAbsX = std::log(-xSt) - omega;
1581 if (logAbsX < seriesZeroParams.second) {
1582 if (alpha < 1.0 && beta == -1.0)
1583 return 1.0 - cdfShortTailExpansion(logAbsX);
1584 return 1.0 - cdfSeriesExpansionAtZero(logAbsX, -xi, seriesZeroParams.first);
1585 }
1586 if (logAbsX > cdftailBound)
1587 return cdfSeriesExpansionAtInf(logAbsX, -xi);
1588 if (alpha > 1.0)
1589 return cdfIntegralRepresentation(logAbsX, -xi, false);
1590 return (beta == 1.0) ? 0.0 : cdfIntegralRepresentation(logAbsX, -xi, true);
1591}
1592
1593template <typename RealType>
1594double StableDistribution<RealType>::ccdfForGeneralExponent(double x) const
1595{
1596 double xSt = (x - mu) / gamma;
1597 if (xSt <= 0)
1598 return 1.0 - cdfForGeneralExponent(x);
1599 double logAbsX = std::log(xSt) - omega;
1601 if (logAbsX < seriesZeroParams.second) {
1602 if (alpha < 1.0 && beta == 1.0)
1603 return 1.0 - cdfShortTailExpansion(logAbsX);
1604 if (alpha < 1.0 && beta == -1.0)
1605 return cdfShortTailExpansion(logAbsX);
1606 return 1.0 - cdfSeriesExpansionAtZero(logAbsX, xi, seriesZeroParams.first);
1607 }
1609 if (logAbsX > cdftailBound)
1610 return cdfSeriesExpansionAtInf(logAbsX, xi);
1611 if (alpha > 1.0)
1612 return cdfIntegralRepresentation(logAbsX, xi, false);
1613 return (beta == -1.0) ? 0.0 : cdfIntegralRepresentation(logAbsX, xi, true);
1614}
1615
1616template <typename RealType>
1617double StableDistribution<RealType>::F(const RealType &x) const
1618{
1619 if (useS0UnityLimit())
1620 return cdfS0UnityLimit(x, false);
1621 if (useS0NearUnityKernel(x))
1622 return cdfS0NearUnity(x, false);
1623 switch (distributionType) {
1624 case NORMAL:
1625 return cdfNormal(x);
1626 case CAUCHY:
1627 return cdfCauchy(x);
1628 case LEVY:
1629 return (beta > 0) ? cdfLevy(x) : cdfLevyCompl(2 * mu - x);
1630 case UNITY_EXPONENT:
1631 return cdfForUnityExponent(x, false);
1632 case GENERAL:
1633 return cdfForGeneralExponent(x);
1634 default:
1635 throw std::runtime_error("Stable distribution: invalid distribution type");
1636 }
1637}
1638
1639template <typename RealType>
1640double StableDistribution<RealType>::S(const RealType &x) const
1641{
1642 if (useS0UnityLimit())
1643 return cdfS0UnityLimit(x, true);
1644 if (useS0NearUnityKernel(x))
1645 return cdfS0NearUnity(x, true);
1646 switch (distributionType) {
1647 case NORMAL:
1648 return cdfNormalCompl(x);
1649 case CAUCHY:
1650 return cdfCauchyCompl(x);
1651 case LEVY:
1652 return (beta > 0) ? cdfLevyCompl(x) : cdfLevy(2 * mu - x);
1653 case UNITY_EXPONENT:
1654 return cdfForUnityExponent(x, true);
1655 case GENERAL:
1656 return ccdfForGeneralExponent(x);
1657 default:
1658 throw std::runtime_error("Stable distribution: invalid distribution type");
1659 }
1660}
1661
1662template <typename RealType>
1663template <std::uniform_random_bit_generator G>
1665{
1668 RealType pi_2pBetaU = randlib::consts::half_pi + beta * U;
1669 RealType Y = W * std::cos(U) / pi_2pBetaU;
1670 RealType X = std::log(Y);
1671 X += logGammaPi_2;
1672 X *= -beta;
1673 X += pi_2pBetaU * std::tan(U);
1675 return mu + gamma * X;
1676}
1677
1678template <typename RealType>
1679template <std::uniform_random_bit_generator G>
1680double StableDistribution<RealType>::variateForS0UnityLimit(G &g) const
1681{
1683 const RealType w = detail::standardExponential<RealType>(g);
1684 const RealType angle = randlib::consts::half_pi + beta * u;
1685 const RealType logRatio = std::log(w * std::cos(u) / angle);
1686 const RealType standardized =
1688 (angle * std::tan(u) - beta * (logRatio + randlib::consts::lnpi - randlib::consts::ln2));
1689 return locationParameter + gamma * standardized;
1690}
1691
1692template <typename RealType>
1693template <std::uniform_random_bit_generator G>
1694double StableDistribution<RealType>::variateForGeneralExponent(G &g) const
1695{
1698 RealType alphaUpxi = alpha * (U + xi);
1699 RealType X = std::sin(alphaUpxi);
1700 RealType W_adj = W / std::cos(U - alphaUpxi);
1701 X *= W_adj;
1702 RealType R = omega - alphaInv * std::log(W_adj * std::cos(U));
1703 X *= std::exp(R);
1704 return mu + gamma * X;
1705}
1706
1707template <typename RealType>
1708template <std::uniform_random_bit_generator G>
1709double StableDistribution<RealType>::variateForExponentEqualOneHalf(G &g) const
1710{
1711 RealType Z1 = detail::standardNormal<RealType>(g);
1712 RealType Z2 = detail::standardNormal<RealType>(g);
1713 RealType temp1 = (1.0 + beta) / Z1, temp2 = (1.0 - beta) / Z2;
1714 RealType var = temp1 - temp2;
1715 var *= temp1 + temp2;
1716 var *= 0.25;
1717 return mu + gamma * var;
1718}
1719
1720template <typename RealType>
1721template <std::uniform_random_bit_generator G>
1723{
1724 if (parameterization == Parameterization::S0 && alpha != 1.0 &&
1725 std::fabs(alpha - 1.0) < S0_SAMPLING_UNITY_TOLERANCE)
1726 return variateForS0UnityLimit(g);
1727 switch (distributionType) {
1728 case NORMAL:
1730 case CAUCHY:
1731 return mu + gamma * CauchyRand<RealType>::StandardVariate(g);
1732 case LEVY:
1733 return mu + math::sign(beta) * gamma * LevyRand<RealType>::StandardVariate(g);
1734 case UNITY_EXPONENT:
1735 return variateForUnityExponent(g);
1736 case GENERAL:
1737 return (alpha == 0.5) ? variateForExponentEqualOneHalf(g) : variateForGeneralExponent(g);
1738 default:
1739 throw std::runtime_error("Stable distribution: invalid distribution type");
1740 }
1741}
1742
1743template <typename RealType>
1744void StableDistribution<RealType>::sample(detail::EngineRef g, std::span<RealType> outputData) const
1745{
1746 if (parameterization == Parameterization::S0 && alpha != 1.0 &&
1747 std::fabs(alpha - 1.0) < S0_SAMPLING_UNITY_TOLERANCE) {
1748 for (RealType &var : outputData)
1749 var = variateForS0UnityLimit(g);
1750 return;
1751 }
1752 switch (distributionType) {
1753 case NORMAL: {
1754 double stdev = randlib::consts::sqrt2 * gamma;
1755 for (RealType &var : outputData)
1756 var = mu + stdev * detail::standardNormal<RealType>(g);
1757 } break;
1758 case CAUCHY: {
1759 for (RealType &var : outputData)
1760 var = mu + gamma * CauchyRand<RealType>::StandardVariate(g);
1761 } break;
1762 case LEVY: {
1763 if (beta > 0) {
1764 for (RealType &var : outputData)
1765 var = mu + gamma * LevyRand<RealType>::StandardVariate(g);
1766 } else {
1767 for (RealType &var : outputData)
1768 var = mu - gamma * LevyRand<RealType>::StandardVariate(g);
1769 }
1770 } break;
1771 case UNITY_EXPONENT: {
1772 for (RealType &var : outputData)
1773 var = variateForUnityExponent(g);
1774 } break;
1775 case GENERAL: {
1776 if (alpha == 0.5) {
1777 for (RealType &var : outputData)
1778 var = variateForExponentEqualOneHalf(g);
1779 } else {
1780 for (RealType &var : outputData)
1781 var = variateForGeneralExponent(g);
1782 }
1783 } break;
1784 default:
1785 break;
1786 }
1787}
1788
1789template <typename RealType>
1791{
1792 if (alpha > 1)
1793 return mu;
1794 if (beta == 1)
1795 return INFINITY;
1796 return (beta == -1) ? -INFINITY : NAN;
1797}
1798
1799template <typename RealType>
1801{
1802 return (distributionType == NORMAL) ? 2 * gamma * gamma : INFINITY;
1803}
1804
1805template <typename RealType>
1807{
1809 if (beta == 0 || distributionType == NORMAL)
1810 return mu;
1811 if (distributionType == LEVY)
1812 return mu + beta * gamma / 3.0;
1814}
1815
1816template <typename RealType>
1818{
1820 if (beta == 0 || distributionType == NORMAL)
1821 return mu;
1823}
1824
1825template <typename RealType>
1827{
1828 return (distributionType == NORMAL) ? 0 : NAN;
1829}
1830
1831template <typename RealType>
1833{
1834 return (distributionType == NORMAL) ? 0 : NAN;
1835}
1836
1837template <typename RealType>
1839{
1840 return mu - 2 * gamma * math::erfcinv(2 * p);
1841}
1842
1843template <typename RealType>
1845{
1846 return mu + 2 * gamma * math::erfcinv(2 * p);
1847}
1848
1849template <typename RealType>
1851{
1852 return mu - gamma / std::tan(randlib::consts::pi * p);
1853}
1854
1855template <typename RealType>
1857{
1858 return mu + gamma / std::tan(randlib::consts::pi * p);
1859}
1860
1861template <typename RealType>
1863{
1864 double y = math::erfcinv(p);
1865 return mu + 0.5 * gamma / (y * y);
1866}
1867
1868template <typename RealType>
1870{
1871 double y = math::erfinv(p);
1872 return mu + 0.5 * gamma / (y * y);
1873}
1874
1875template <typename RealType>
1876RealType StableDistribution<RealType>::searchTailQuantile(double p, bool upperTail) const
1877{
1878 double tail = std::min(p, 1.0 - p);
1879 double guess =
1880 (tail < 0.05) ? stableTailQuantile(tail, upperTail != (p > 0.5), alpha, beta, gamma, GetLocation()) : NAN;
1881 return this->searchQuantile(p, upperTail, guess, 0.05);
1882}
1883
1884template <typename RealType>
1885RealType StableDistribution<RealType>::quantileImpl(double p) const
1886{
1887 switch (distributionType) {
1888 case NORMAL:
1889 return quantileNormal(p);
1890 case CAUCHY:
1891 return quantileCauchy(p);
1892 case LEVY:
1893 return (beta > 0) ? quantileLevy(p) : 2 * mu - quantileLevy1m(p);
1894 default:
1895 return searchTailQuantile(p, false);
1896 }
1897}
1898
1899template <typename RealType>
1900RealType StableDistribution<RealType>::quantileImpl1m(double p) const
1901{
1902 switch (distributionType) {
1903 case NORMAL:
1904 return quantileNormal1m(p);
1905 case CAUCHY:
1906 return quantileCauchy1m(p);
1907 case LEVY:
1908 return (beta > 0) ? quantileLevy1m(p) : 2 * mu - quantileLevy(p);
1909 default:
1910 return searchTailQuantile(p, true);
1911 }
1912}
1913
1914template <typename RealType>
1915std::complex<double> StableDistribution<RealType>::cfNormal(double t) const
1916{
1917 double gammaT = gamma * t;
1918 std::complex<double> y(-gammaT * gammaT, mu * t);
1919 return std::exp(y);
1920}
1921
1922template <typename RealType>
1923std::complex<double> StableDistribution<RealType>::cfCauchy(double t) const
1924{
1925 std::complex<double> y(-gamma * t, mu * t);
1926 return std::exp(y);
1927}
1928
1929template <typename RealType>
1930std::complex<double> StableDistribution<RealType>::cfLevy(double t) const
1931{
1932 std::complex<double> y(0.0, -2 * gamma * t);
1933 y = -std::sqrt(y);
1934 y += std::complex<double>(0.0, mu * t);
1935 return std::exp(y);
1936}
1937
1938template <typename RealType>
1939std::complex<double> StableDistribution<RealType>::CFImpl(double t) const
1940{
1941 if (parameterization == Parameterization::S0) {
1942 const double scaledT = gamma * t;
1943 const double attenuation = std::pow(scaledT, alpha);
1944 double phase = locationParameter * t;
1945 if (beta != 0.0) {
1946 if (alpha == 1.0)
1947 phase -= beta * randlib::consts::two_inv_pi * scaledT * std::log(scaledT);
1948 else if (alpha != 2.0)
1949 phase -= beta * scaledT * std::expm1((alpha - 1.0) * std::log(scaledT)) /
1950 std::tan(randlib::consts::half_pi * (alpha - 1.0));
1951 }
1952 return std::exp(std::complex<double>(-attenuation, phase));
1953 }
1954 double x = 0;
1955 switch (distributionType) {
1956 case NORMAL:
1957 return cfNormal(t);
1958 case CAUCHY:
1959 return cfCauchy(t);
1960 case LEVY: {
1961 std::complex<double> phi = cfLevy(t);
1962 return (beta > 0) ? phi : std::conj(phi);
1963 }
1964 case UNITY_EXPONENT:
1965 x = beta * randlib::consts::two_inv_pi * std::log(t);
1966 break;
1967 default:
1968 x = zeta;
1969 }
1970 double re = std::pow(gamma * t, alpha);
1971 std::complex<double> psi = std::complex<double>(re, re * x - mu * t);
1972 return std::exp(-psi);
1973}
1974template <typename RealType>
1976{
1977 std::string name = "Stable(" + this->toStringWithPrecision(this->GetExponent()) + ", " +
1978 this->toStringWithPrecision(this->GetSkewness()) + ", " +
1979 this->toStringWithPrecision(this->GetScale()) + ", " +
1980 this->toStringWithPrecision(this->GetLocation());
1981 if (this->GetParameterization() == Parameterization::S0)
1982 name += ", S0";
1983 return name + ")";
1984}
1985
1986template <typename RealType>
1988{
1989 this->SetParameters(exponent, this->GetSkewness(), this->GetScale(), this->GetLocation());
1990}
1991
1992template <typename RealType>
1994{
1995 this->SetParameters(this->GetExponent(), skewness, this->GetScale(), this->GetLocation());
1996}
1997template <typename RealType>
1999{
2000 return "Holtsmark(" + this->toStringWithPrecision(this->GetScale()) + ", " +
2001 this->toStringWithPrecision(this->GetLocation()) + ")";
2002}
2003template <typename RealType>
2005{
2006 return "Landau(" + this->toStringWithPrecision(this->GetScale()) + ", " +
2007 this->toStringWithPrecision(this->GetLocation()) + ")";
2008}
2009
2010} // namespace randlib
static RealType StandardVariate(G &g)
Definition cauchy.hpp:101
The ContinuousDistribution class Abstract class for all continuous distributions.
Definition continuous.hpp:22
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
The HoltsmarkRand class Holtsmark distribution.
Definition stable.hpp:547
std::string Name() const override
Definition stable.hpp:1998
HoltsmarkRand(double scale=1, double location=0)
Definition stable.hpp:549
The LandauRand class Landau distribution.
Definition stable.hpp:564
LandauRand(double scale=1, double location=0)
Definition stable.hpp:566
std::string Name() const override
Definition stable.hpp:2004
static RealType StandardVariate(G &g)
Definition levy.hpp:115
virtual std::string Name() const =0
The StableDistribution class Abstract class for Stable distribution.
Definition stable.hpp:74
double cdfLevy(RealType x) const
Definition stable.hpp:1424
double gamma
scale γ
Definition stable.hpp:89
double logpdfCauchy(RealType x) const
Definition stable.hpp:769
double cdfNormal(RealType x) const
Definition stable.hpp:1391
RealType operator()(G &g) const
Definition stable.hpp:1722
double logpdfLevy(RealType x) const
Definition stable.hpp:784
double S(const RealType &x) const override
Definition stable.hpp:1640
RealType MinValue() const override
Definition stable.hpp:602
long double Skewness() const override
Definition stable.hpp:1826
void SetParameterization(Parameterization value) noexcept
Definition stable.hpp:736
void SetLocation(double location)
Definition stable.hpp:724
double GetLogScale() const noexcept
Definition stable.hpp:176
double logGamma
log(γ)
Definition stable.hpp:90
RealType Median() const override
Definition stable.hpp:1817
double GetScale() const noexcept
Definition stable.hpp:164
RealType quantileLevy(double p) const
Definition stable.hpp:1862
double f(const RealType &x) const override
Definition stable.hpp:1345
long double Variance() const override
Definition stable.hpp:1800
RealType quantileNormal1m(double p) const
Definition stable.hpp:1844
double GetExponent() const noexcept
Definition stable.hpp:156
double pdfCoef
hashed coefficient for faster pdf calculations
Definition stable.hpp:121
StableDistribution(double exponent, double skewness, double scale=1, double location=0, Parameterization selectedParameterization=Parameterization::S1)
Definition stable.hpp:582
double pdfCauchy(RealType x) const
Definition stable.hpp:759
RealType quantileCauchy(double p) const
Definition stable.hpp:1850
std::complex< double > cfNormal(double t) const
Definition stable.hpp:1915
double F(const RealType &x) const override
Definition stable.hpp:1617
std::complex< double > cfCauchy(double t) const
Definition stable.hpp:1923
RealType quantileLevy1m(double p) const
Definition stable.hpp:1869
RealType Mode() const override
Mode is the value, which has the largest probability to happen, or, in the case of continuous distrib...
Definition stable.hpp:1806
double cdfNormalCompl(RealType x) const
Definition stable.hpp:1399
RealType quantileCauchy1m(double p) const
Definition stable.hpp:1856
double cdfLevyCompl(RealType x) const
Definition stable.hpp:1436
RealType quantileNormal(double p) const
Definition stable.hpp:1838
void SetScale(double scale)
Definition stable.hpp:730
Support SupportType() const override
Definition stable.hpp:590
void SetParameters(double exponent, double skewness, double scale=1, double location=0)
Definition stable.hpp:698
Parameterization
Nolan parameterization used for the public location parameter.
Definition stable.hpp:81
@ S1
classical parameterization used by RandLib before v2.1
@ S0
continuous in all four parameters, including at alpha = 1
double GetLocation() const noexcept
Definition stable.hpp:168
virtual ~StableDistribution()
Definition stable.hpp:130
double logf(const RealType &x) const override
Definition stable.hpp:1368
double pdfLevy(RealType x) const
Definition stable.hpp:778
double pdfNormal(RealType x) const
Definition stable.hpp:743
long double Mean() const override
Definition stable.hpp:1790
RealType MaxValue() const override
Definition stable.hpp:608
double pdftailBound
boundary k such that for |x| > k we can use pdf tail approximation
Definition stable.hpp:123
double cdfCauchy(RealType x) const
Definition stable.hpp:1407
double GetSkewness() const noexcept
Definition stable.hpp:160
double cdfCauchyCompl(RealType x) const
Definition stable.hpp:1416
double cdftailBound
boundary k such that for |x| > k we can use cdf tail approximation
Definition stable.hpp:124
double alpha
characteristic exponent α
Definition stable.hpp:86
Parameterization GetParameterization() const noexcept
Definition stable.hpp:172
double logpdfNormal(RealType x) const
Definition stable.hpp:749
double beta
skewness β
Definition stable.hpp:87
long double ExcessKurtosis() const override
Definition stable.hpp:1832
double mu
location μ
Definition stable.hpp:88
std::complex< double > cfLevy(double t) const
Definition stable.hpp:1930
The StableRand class Stable distribution.
Definition stable.hpp:521
typename StableDistribution< RealType >::Parameterization Parameterization
Definition stable.hpp:523
std::string Name() const override
Definition stable.hpp:1975
void SetExponent(double exponent)
Definition stable.hpp:1987
void SetSkewness(double skewness)
Definition stable.hpp:1993
StableRand(double exponent=2, double skewness=0, double scale=1, double location=0, Parameterization selectedParameterization=Parameterization::S1)
Definition stable.hpp:525
virtual RealType Median() const
Definition univariate.hpp:396
The EngineRef class Non-owning, type-erased reference to any uniform random bit generator.
Definition engine/uniform.hpp:116
constexpr long double inv_pi
Definition constants.hpp:24
constexpr long double ln2
Definition constants.hpp:18
constexpr long double egamma
Definition constants.hpp:36
constexpr long double half_pi
Definition constants.hpp:23
constexpr long double pi
Definition constants.hpp:22
constexpr long double two_inv_pi
Definition constants.hpp:25
constexpr long double ln10
Definition constants.hpp:20
constexpr long double sqrt2
Definition constants.hpp:27
constexpr long double ln3
Definition constants.hpp:19
constexpr long double e
Definition constants.hpp:15
constexpr long double lnpi
Definition constants.hpp:21
double stableAtan(double value)
Definition stable.hpp:28
double stableTanHalfPi(double alpha)
Definition stable.hpp:20
Real standardNormal(G &g)
Ziggurat algorithm of Marsaglia and Tsang with 256 layers and an explicit sign bit.
Definition variates.hpp:45
Real factorial(Real n)
Calculate n! using table values for small n <= 255 and built-in gamma function for large n > 255.
Definition math/gamma.hpp:307
Real erfinv(Real p)
Definition functions.hpp:464
Real digamma(Real x)
Definition math/gamma.hpp:691
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
int sign(Real x)
Definition functions.hpp:232
Real integrateTanhSinh(const F &f, std::type_identity_t< Real > a, std::type_identity_t< Real > b, std::type_identity_t< Real > tolerance=1e-11, int maxRefinements=12)
Double-exponential tanh-sinh quadrature on a finite interval.
Definition quadrature.hpp:266
Real atan(Real x)
Definition functions.hpp:238
Real riemannZeta(Real s)
Definition special_functions.hpp:340
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
Real erfcinv(Real p)
Definition functions.hpp:485
Definition bivariate_normal.hpp:10
Support
Shape of the support of a univariate distribution.
Definition univariate.hpp:15
@ LeftSemiFinite
(-∞, b]
@ RightSemiFinite
[a, ∞)
@ Infinite
(-∞, ∞)
double stableTailQuantile(double p, bool upperTail, double alpha, double beta, double gamma, double mu)
Quantile of the power-law tails of a stable law with α < 2: P(X > x) ~ C (1 + β) γ^α x^(-α) and P(X <...
Definition stable.hpp:49
Pair< double > DoublePair
Definition fwd.hpp:39