26template <
typename RealType>
35 double halfDeltaSquared = 0;
40 long double integralPositiveTail(
long double x,
long double noncentrality,
bool upper)
const;
41 long double integralDensity(
long double x)
const;
42 static long double kummerM(
long double a,
long double b,
long double z);
43 long double logPositiveNormalMoment(
long double power,
long double noncentrality)
const;
44 long double asymptoticLogTail(
long double x,
long double noncentrality,
bool density)
const;
45 long double positiveTail(
long double x,
long double noncentrality,
bool upper,
bool allowIntegral)
const;
46 long double probability(
long double x,
bool upper,
bool allowIntegral =
true)
const;
47 long double inverseDenominatorMoment(
int order)
const;
52 [[nodiscard]] std::string
Name()
const override;
54 [[nodiscard]] RealType
MinValue()
const override {
return -INFINITY; }
55 [[nodiscard]] RealType
MaxValue()
const override {
return INFINITY; }
58 [[nodiscard]]
double GetDegree() const noexcept {
return nu; }
61 [[nodiscard]]
double f(
const RealType &x)
const override;
62 [[nodiscard]]
double logf(
const RealType &x)
const override;
63 [[nodiscard]]
double F(
const RealType &x)
const override;
64 [[nodiscard]]
double S(
const RealType &x)
const override;
66 template <std::uniform_random_bit_generator G>
69 [[nodiscard]]
long double Mean()
const override;
70 [[nodiscard]]
long double Variance()
const override;
71 [[nodiscard]]
long double Skewness()
const override;
75 RealType quantileImpl(
double p)
const override;
76 RealType quantileImpl1m(
double p)
const override;
77 std::complex<double> CFImpl(
double t)
const override;
87template <
typename RealType>
90 SetParameters(degree, noncentrality);
93template <
typename RealType>
96 return "Noncentral-t(" + this->toStringWithPrecision(nu) +
", " + this->toStringWithPrecision(delta) +
")";
99template <
typename RealType>
102 if (!(degree > 0) || !std::isfinite(degree))
103 throw std::invalid_argument(
"Noncentral-t distribution: degree should be finite and positive");
104 if (!std::isfinite(noncentrality))
105 throw std::invalid_argument(
"Noncentral-t distribution: noncentrality should be finite");
107 delta = noncentrality;
109 halfDeltaSquared = 0.5 * delta * delta;
110 seriesCenter = (std::isfinite(halfDeltaSquared) && halfDeltaSquared < 1000000)
111 ?
static_cast<int>(std::floor(halfDeltaSquared))
113 central.SetDegree(nu);
114 denominator.SetParameters(halfNu, 1.0);
117template <
typename RealType>
120 const long double shape = halfNu;
121 const long double logCoefficient =
randlib::consts::ln2 + shape * std::log(shape) - std::lgamma(shape);
122 const long double inverseSqrt2 = 1 / std::sqrt(2.0L);
123 return math::integrate<long double>(
127 const long double normalProbability =
128 0.5L * std::erfc((upper ? x * w - noncentrality : noncentrality - x * w) * inverseSqrt2);
129 if (normalProbability == 0)
131 const long double logDensity = logCoefficient + (2 * shape - 1) * std::log(w) - shape * w * w;
132 return normalProbability * std::exp(logDensity);
134 0.0L, std::numeric_limits<long double>::infinity(), 2e-13L, 2048);
137template <
typename RealType>
138long double NoncentralTRand<RealType>::integralDensity(
long double x)
const
140 const long double shape = halfNu;
141 const long double noncentrality = delta;
142 const long double logCoefficient =
144 const auto integrand = [=](
long double w) {
147 const long double normalArgument = x * w - noncentrality;
148 const long double logIntegrand =
149 logCoefficient + 2 * shape * std::log(w) - shape * w * w - 0.5L * normalArgument * normalArgument;
150 return std::exp(logIntegrand);
152 const long double quadratic = x * x + 2 * shape;
153 const long double saddle =
154 (x * noncentrality + std::sqrt(x * x * noncentrality * noncentrality + 8 * shape * quadratic)) /
156 return math::integrate<long double>(integrand, 0.0L, saddle, 2e-13L, 1024) +
157 math::integrate<long double>(integrand, saddle, std::numeric_limits<long double>::infinity(), 2e-13L, 1024);
160template <
typename RealType>
161long double NoncentralTRand<RealType>::kummerM(
long double a,
long double b,
long double z)
163 long double term = 1;
165 for (
int n = 1; n < 100000; ++n) {
166 term *= (a + n - 1) / (b + n - 1) * z / n;
168 if (std::fabs(term) <= 4 * std::numeric_limits<long double>::epsilon() * std::fabs(sum))
174template <
typename RealType>
175long double NoncentralTRand<RealType>::logPositiveNormalMoment(
long double power,
long double noncentrality)
const
177 const long double saddle = 0.5L * (noncentrality + std::sqrt(noncentrality * noncentrality + 4 * power));
178 const long double logPeak = power * std::log(saddle) - 0.5L * (saddle - noncentrality) * (saddle - noncentrality) -
180 const auto integrand = [=](
long double value) {
183 const long double difference = value - noncentrality;
184 const long double logValue =
185 power * std::log(value) - 0.5L * difference * difference - 0.5L * std::log(2 *
randlib::consts::pi);
186 return std::exp(logValue - logPeak);
188 const long double scaled =
189 math::integrate<long double>(integrand, 0.0L, saddle, 2e-13L, 512) +
190 math::integrate<long double>(integrand, saddle, std::numeric_limits<long double>::infinity(), 2e-13L, 512);
191 return logPeak + std::log(scaled);
194template <
typename RealType>
195long double NoncentralTRand<RealType>::asymptoticLogTail(
long double x,
long double noncentrality,
bool density)
const
197 const long double b = halfNu;
198 const long double logX = std::log(x);
199 std::array<long double, 8> logarithms{};
200 long double maximum = -INFINITY;
202 for (
int k = 0; k < static_cast<int>(logarithms.size()); ++k) {
203 const long double power = nu + 2 * k;
204 long double logarithm = (b + k) * std::log(b) - std::lgamma(b) - std::lgamma(k + 1.0L) - std::log(b + k) +
205 logPositiveNormalMoment(power, noncentrality) - power * logX;
207 logarithm += std::log(power) - logX;
208 logarithms[k] = logarithm;
209 maximum = std::max(maximum, logarithm);
211 if (k >= 2 && logarithm < maximum - 40)
214 long double scaled = 0;
215 for (
int k = 0; k < terms; ++k)
216 scaled += ((k % 2 == 0) ? 1 : -1) * std::exp(logarithms[k] - maximum);
217 return maximum + std::log(scaled);
220template <
typename RealType>
221long double NoncentralTRand<RealType>::positiveTail(
long double x,
long double noncentrality,
bool upper,
222 bool allowIntegral)
const
225 return 0.5L * std::erfc((upper ? -noncentrality : noncentrality) / std::sqrt(2.0L));
227 return upper ? 0.0L : 1.0L;
228 const long double lambda = 0.5L * noncentrality * noncentrality;
230 return upper ? central.S(
static_cast<RealType
>(x)) : central.F(
static_cast<RealType
>(x));
231 const long double asymptoticCrossover = std::max(100.0L, 10 * std::fabs(noncentrality) + 10);
232 if (x > asymptoticCrossover) {
233 const long double tail = std::exp(asymptoticLogTail(x, noncentrality,
false));
234 return upper ? tail : 1 - tail;
236 if (seriesCenter < 0)
237 return integralPositiveTail(x, noncentrality, upper);
239 const long double logRatio = 2 * std::log(x) - std::log(
static_cast<long double>(nu));
242 const long double y = std::exp(logY);
243 const long double oneMinusY = std::exp(log1mY);
244 const long double b = halfNu;
245 const int center = seriesCenter;
246 long double a1 = center + 0.5L;
247 long double a2 = center + 1.0L;
251 upper ?
math::ibeta(oneMinusY, b, a1, logBeta1, log1mY, logY) : math::
ibeta(y, a1, b, logBeta1, logY, log1mY);
253 upper ?
math::ibeta(oneMinusY, b, a2, logBeta2, log1mY, logY) : math::
ibeta(y, a2, b, logBeta2, logY, log1mY);
254 long double increment1 = std::exp(a1 * logY + b * log1mY - std::log(a1) - logBeta1);
255 long double increment2 = std::exp(a2 * logY + b * log1mY - std::log(a2) - logBeta2);
256 const auto centerValue =
static_cast<long double>(center);
257 const long double logPoisson = (center == 0) ? -lambda
258 : -math::
stirlingError<long double>(static_cast<std::size_t>(center)) -
259 math::
bd0<long double>(centerValue, lambda) -
260 0.5L * std::log(2 *
randlib::consts::
pi * centerValue);
261 long double poisson = std::exp(logPoisson);
263 long double odd = std::copysign(std::sqrt(lambda) * gammaRatio * poisson, noncentrality);
264 long double sum = poisson * beta1 + odd * beta2;
265 long double compensation = 0;
266 long double absoluteSum = std::fabs(poisson * beta1) + std::fabs(odd * beta2);
267 const auto addTerm = [&](
long double term) {
268 const long double corrected = term - compensation;
269 const long double next = sum + corrected;
270 compensation = (next - sum) - corrected;
274 long double lowerPoisson = poisson;
275 long double lowerOdd = odd;
276 long double lowerBeta1 = beta1;
277 long double lowerBeta2 = beta2;
278 long double lowerIncrement1 = increment1;
279 long double lowerIncrement2 = increment2;
280 long double lowerA1 = a1;
281 long double lowerA2 = a2;
282 for (
int j = center; j > 0; --j) {
283 const long double previousIncrement1 = lowerIncrement1 * lowerA1 / (y * (lowerA1 - 1 + b));
284 const long double previousIncrement2 = lowerIncrement2 * lowerA2 / (y * (lowerA2 - 1 + b));
285 lowerBeta1 += upper ? -previousIncrement1 : previousIncrement1;
286 lowerBeta2 += upper ? -previousIncrement2 : previousIncrement2;
289 lowerIncrement1 = previousIncrement1;
290 lowerIncrement2 = previousIncrement2;
291 lowerPoisson *= j / lambda;
292 lowerOdd *= (j + 0.5L) / lambda;
293 const long double term = lowerPoisson * lowerBeta1 + lowerOdd * lowerBeta2;
295 absoluteSum += std::fabs(lowerPoisson * lowerBeta1) + std::fabs(lowerOdd * lowerBeta2);
299 for (
int j = center + 1; j < center + 1000000; ++j) {
300 poisson *= lambda / j;
301 odd *= lambda / (j + 0.5L);
302 beta1 += upper ? increment1 : -increment1;
303 beta2 += upper ? increment2 : -increment2;
304 increment1 *= y * (a1 + b) / (a1 + 1);
305 increment2 *= y * (a2 + b) / (a2 + 1);
308 const long double term = poisson * beta1 + odd * beta2;
309 const long double bound = std::fabs(poisson * beta1) + std::fabs(odd * beta2);
311 absoluteSum += bound;
312 decreasing = (bound <= 8 * std::numeric_limits<long double>::epsilon() * absoluteSum) ? decreasing + 1 : 0;
317 long double result = 0.5L * sum;
319 result += 0.5L * std::erfc(noncentrality / std::sqrt(2.0L));
320 const long double integralCrossover = std::max(50.0L, 10 * std::fabs(noncentrality) + 10);
321 if (allowIntegral && upper && noncentrality < 0 &&
322 (result <= 0 || result <= 256 * std::numeric_limits<long double>::epsilon() * absoluteSum ||
323 (result < 1e-8L && x < integralCrossover)))
324 result = integralPositiveTail(x, noncentrality,
true);
325 if (!std::isfinite(result) || result < 0 || result > 1)
326 result = integralPositiveTail(x, noncentrality, upper);
327 return std::clamp(result, 0.0L, 1.0L);
330template <
typename RealType>
331long double NoncentralTRand<RealType>::probability(
long double x,
bool upper,
bool allowIntegral)
const
334 return 0.5L * std::erfc((upper ? -delta : delta) / std::sqrt(2.0L));
336 return positiveTail(x, delta, upper, allowIntegral);
337 return positiveTail(-x, -delta, !upper, allowIntegral);
340template <
typename RealType>
343 return static_cast<double>(probability(x,
false));
346template <
typename RealType>
349 return static_cast<double>(probability(x,
true));
352template <
typename RealType>
355 if (!std::isfinite(x))
360 return std::exp(logf(x));
361 const long double reflectedNoncentrality = (x > 0) ? delta : -delta;
362 const long double asymptoticCrossover = std::max(100.0L, 10 * std::fabs(reflectedNoncentrality) + 10);
363 if (std::fabs(
static_cast<long double>(x)) > asymptoticCrossover) {
364 return static_cast<double>(
365 std::exp(asymptoticLogTail(std::fabs(
static_cast<long double>(x)), reflectedNoncentrality,
true)));
367 const long double square =
static_cast<long double>(x) * x;
368 const long double halfDegree = 0.5L * nu;
369 const long double z = halfDeltaSquared * square / (square + nu);
370 const long double a = 0.5L * (nu + 1);
371 const long double summit = 0.5L * (std::sqrt(z * (4 * a + z)) + z);
373 const long double first = kummerM(a, 0.5L, z);
374 long double second = kummerM(halfDegree + 1, 1.5L, z);
375 second *= std::exp(std::lgamma(halfDegree + 1) - std::lgamma(a));
376 second *= std::sqrt(2.0L) * delta *
static_cast<long double>(x) / std::sqrt(square + nu);
377 const long double series = first + second;
378 if (series > 4 * std::sqrt(std::numeric_limits<long double>::epsilon()) * first) {
379 const long double logCoefficient = -halfDeltaSquared - a * std::log1p(square / nu) + std::lgamma(a) -
381 return static_cast<double>(std::exp(logCoefficient) * series);
384 if (
static_cast<long double>(x) * delta < 0)
385 return static_cast<double>(integralDensity(x));
386 const long double scaledX =
static_cast<long double>(x) * std::sqrt(1 + 2 / nu);
388 const long double first = (x > 0) ? probability(x,
true,
false) : shifted.probability(scaledX,
false,
false);
389 const long double second = (x > 0) ? shifted.probability(scaledX,
true,
false) : probability(x,
false,
false);
390 const long double difference = first - second;
391 if (!(difference > std::sqrt(std::numeric_limits<long double>::epsilon()) * (std::fabs(first) + std::fabs(second))))
392 return static_cast<double>(integralDensity(x));
393 return static_cast<double>(nu /
static_cast<long double>(x) * difference);
396template <
typename RealType>
399 if (!std::isfinite(x))
402 return central.logf(x);
404 return static_cast<double>(std::lgamma(0.5L * (nu + 1)) - std::lgamma(0.5L * nu) -
407 const long double reflectedNoncentrality = (x > 0) ? delta : -delta;
408 const long double asymptoticCrossover = std::max(100.0L, 10 * std::fabs(reflectedNoncentrality) + 10);
409 if (std::fabs(
static_cast<long double>(x)) > asymptoticCrossover) {
410 return static_cast<double>(
411 asymptoticLogTail(std::fabs(
static_cast<long double>(x)), reflectedNoncentrality,
true));
413 const double density = f(x);
414 return (density > 0) ? std::log(density) : -INFINITY;
417template <
typename RealType>
418template <std::uniform_random_bit_generator G>
424template <
typename RealType>
427 denominator.
Sample(g, outputData);
428 for (RealType &value : outputData)
429 value = (static_cast<RealType>(delta) + detail::standardNormal<RealType>(g)) / value;
432template <
typename RealType>
433long double NoncentralTRand<RealType>::inverseDenominatorMoment(
int order)
const
435 const long double halfOrder = 0.5L * order;
436 const long double halfDegree = halfNu;
438 return halfDegree / (halfDegree - 1);
440 return halfDegree * halfDegree / ((halfDegree - 1) * (halfDegree - 2));
441 const long double logRatio =
math::logBeta(halfDegree - halfOrder, halfOrder) - std::lgamma(halfOrder);
442 return std::exp(halfOrder * std::log(halfDegree) + logRatio);
445template <
typename RealType>
448 return (nu > 1) ? delta * inverseDenominatorMoment(1) : NAN;
451template <
typename RealType>
455 return (nu > 1) ? INFINITY : NAN;
456 const long double first = inverseDenominatorMoment(1);
457 const long double second = inverseDenominatorMoment(2);
458 const long double dispersion = std::fma(-first, first, second);
459 return std::fma(
static_cast<long double>(delta) * delta, dispersion, second);
462template <
typename RealType>
467 const long double first = inverseDenominatorMoment(1);
468 const long double second = inverseDenominatorMoment(2);
469 const long double third = inverseDenominatorMoment(3);
470 const long double dispersion = std::fma(-first, first, second);
471 const long double thirdDifference = std::fma(-first, second, third);
472 const long double thirdCoefficient = std::fma(-2 * first, dispersion, thirdDifference);
473 const long double squaredDelta =
static_cast<long double>(delta) * delta;
474 const long double variance = std::fma(squaredDelta, dispersion, second);
475 const long double centralThird = delta * std::fma(squaredDelta, thirdCoefficient, 3 * thirdDifference);
476 return centralThird / std::pow(variance, 1.5L);
479template <
typename RealType>
483 return (nu > 2) ? INFINITY : NAN;
484 const long double first = inverseDenominatorMoment(1);
485 const long double second = inverseDenominatorMoment(2);
486 const long double third = inverseDenominatorMoment(3);
487 const long double fourth = inverseDenominatorMoment(4);
488 const long double dispersion = std::fma(-first, first, second);
489 const long double thirdDifference = std::fma(-first, second, third);
490 const long double fourthDifference = std::fma(-first, third, fourth);
491 const long double squaredCoefficient = std::fma(-first, thirdDifference, fourthDifference);
492 const long double fourthCoefficient =
493 std::fma(-3 * first, std::fma(-first, dispersion, thirdDifference), fourthDifference);
494 const long double squaredDelta =
static_cast<long double>(delta) * delta;
495 const long double variance = std::fma(squaredDelta, dispersion, second);
496 const long double centralFourth = std::fma(squaredDelta * squaredDelta, fourthCoefficient,
497 std::fma(6 * squaredDelta, squaredCoefficient, 3 * fourth));
498 return centralFourth / (variance * variance) - 3;
501template <
typename RealType>
506 if (p ==
static_cast<double>(probability(0,
false)))
508 const long double mean = Mean();
509 const RealType guess = std::isfinite(mean) ?
static_cast<RealType
>(mean) : static_cast<RealType>(delta);
513template <
typename RealType>
514RealType NoncentralTRand<RealType>::quantileImpl1m(
double p)
const
517 return central.Quantile1m(p);
518 if (p ==
static_cast<double>(probability(0,
true)))
520 const long double mean = Mean();
521 const RealType guess = std::isfinite(mean) ?
static_cast<RealType
>(mean) : static_cast<RealType>(delta);
525template <
typename RealType>
526std::complex<double> NoncentralTRand<RealType>::CFImpl(
double t)
const
529 return central.CF(t);
530 const long double frequency = t;
531 const long double noncentrality = delta;
532 const long double shape = halfNu;
533 const long double logCoefficient =
randlib::consts::ln2 + shape * std::log(shape) - std::lgamma(shape);
534 const long double linear = 2 * shape - 1;
535 const long double squaredMode =
536 (linear + std::sqrt(linear * linear + 8 * shape * frequency * frequency)) / (4 * shape);
537 const long double mode = std::sqrt(squaredMode);
538 const auto envelope = [logCoefficient, linear, shape, frequency](
long double w) {
541 return std::exp(logCoefficient + linear * std::log(w) - shape * w * w - 0.5L * frequency * frequency / (w * w));
543 const auto integratePart = [envelope, frequency, noncentrality, mode](
bool imaginary) {
544 const auto integrand = [envelope, frequency, noncentrality, imaginary](
long double w) {
545 const long double amplitude = envelope(w);
548 const long double phase = frequency * noncentrality / w;
549 return amplitude * (imaginary ? std::sin(phase) : std::cos(phase));
551 return math::integrate<long double>(integrand, 0.0L, mode, 2e-12L, 1024) +
552 math::integrate<long double>(integrand, mode, std::numeric_limits<long double>::infinity(), 2e-12L,
555 return {
static_cast<double>(integratePart(
false)),
static_cast<double>(integratePart(
true))};
The ContinuousDistribution class Abstract class for all continuous distributions.
Definition continuous.hpp:22
RealType quantileImpl(double p, RealType initValue) const override
Definition continuous.hpp:140
RealType quantileImpl1m(double p, RealType initValue) const override
Definition continuous.hpp:152
The NakagamiRand class Nakagami distribution.
Definition nakagami.hpp:96
Noncentral Student's t-distribution.
Definition noncentral_t.hpp:28
double S(const RealType &x) const override
Definition noncentral_t.hpp:347
long double ExcessKurtosis() const override
Definition noncentral_t.hpp:480
RealType MinValue() const override
Definition noncentral_t.hpp:54
long double Variance() const override
Definition noncentral_t.hpp:452
double GetDegree() const noexcept
Definition noncentral_t.hpp:58
RealType MaxValue() const override
Definition noncentral_t.hpp:55
RealType operator()(G &g) const
Definition noncentral_t.hpp:419
Support SupportType() const override
Definition noncentral_t.hpp:53
double F(const RealType &x) const override
Definition noncentral_t.hpp:341
double GetNoncentrality() const noexcept
Definition noncentral_t.hpp:59
long double Mean() const override
Definition noncentral_t.hpp:446
std::string Name() const override
Definition noncentral_t.hpp:94
NoncentralTRand(double degree=1, double noncentrality=0)
Definition noncentral_t.hpp:88
void SetParameters(double degree, double noncentrality)
Definition noncentral_t.hpp:100
double logf(const RealType &x) const override
Definition noncentral_t.hpp:397
long double Skewness() const override
Definition noncentral_t.hpp:463
double f(const RealType &x) const override
Definition noncentral_t.hpp:353
void Sample(std::span< T > outputData) const
Fills the output with variates drawn with the engine of this object.
Definition distribution.hpp:139
The StudentTRand class Student's t-distribution.
Definition student_t.hpp:22
T Quantile(double p) const
Definition univariate.hpp:307
The EngineRef class Non-owning, type-erased reference to any uniform random bit generator.
Definition engine/uniform.hpp:116
constexpr long double ln2
Definition constants.hpp:18
constexpr long double pi
Definition constants.hpp:22
constexpr long double lnpi
Definition constants.hpp:21
Real standardNormal(G &g)
Ziggurat algorithm of Marsaglia and Tsang with 256 layers and an explicit sign bit.
Definition variates.hpp:45
Real logBeta(Real a, std::type_identity_t< Real > b)
Calculate logarithm of beta function.
Definition math/beta.hpp:72
Real bd0(std::type_identity_t< Real > x, Real m)
Deviance term of the saddle-point expansions of Poisson and binomial probabilities (C.
Definition math/gamma.hpp:620
Real stirlingError(size_t n)
Definition math/gamma.hpp:598
Real softplus(Real x)
Definition functions.hpp:252
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
Definition bivariate_normal.hpp:10
Support
Shape of the support of a univariate distribution.
Definition univariate.hpp:15