File size: 6,209 Bytes
7885a28 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
/* Translated from Cython into C++ by SciPy developers in 2024.
* Original header comment appears below.
*/
/* An implementation of the principal branch of the logarithm of
* Gamma. Also contains implementations of Gamma and 1/Gamma which are
* easily computed from log-Gamma.
*
* Author: Josh Wilson
*
* Distributed under the same license as Scipy.
*
* References
* ----------
* [1] Hare, "Computing the Principal Branch of log-Gamma",
* Journal of Algorithms, 1997.
*
* [2] Julia,
* https://github.com/JuliaLang/julia/blob/master/base/special/gamma.jl
*/
#pragma once
#include "cephes/gamma.h"
#include "cephes/rgamma.h"
#include "config.h"
#include "error.h"
#include "evalpoly.h"
#include "trig.h"
#include "zlog1.h"
namespace xsf {
namespace detail {
constexpr double loggamma_SMALLX = 7;
constexpr double loggamma_SMALLY = 7;
constexpr double loggamma_HLOG2PI = 0.918938533204672742; // log(2*pi)/2
constexpr double loggamma_LOGPI = 1.1447298858494001741434262; // log(pi)
constexpr double loggamma_TAYLOR_RADIUS = 0.2;
XSF_HOST_DEVICE std::complex<double> loggamma_stirling(std::complex<double> z) {
/* Stirling series for log-Gamma
*
* The coefficients are B[2*n]/(2*n*(2*n - 1)) where B[2*n] is the
* (2*n)th Bernoulli number. See (1.1) in [1].
*/
double coeffs[] = {-2.955065359477124183E-2, 6.4102564102564102564E-3, -1.9175269175269175269E-3,
8.4175084175084175084E-4, -5.952380952380952381E-4, 7.9365079365079365079E-4,
-2.7777777777777777778E-3, 8.3333333333333333333E-2};
std::complex<double> rz = 1.0 / z;
std::complex<double> rzz = rz / z;
return (z - 0.5) * std::log(z) - z + loggamma_HLOG2PI + rz * cevalpoly(coeffs, 7, rzz);
}
XSF_HOST_DEVICE std::complex<double> loggamma_recurrence(std::complex<double> z) {
/* Backward recurrence relation.
*
* See Proposition 2.2 in [1] and the Julia implementation [2].
*
*/
int signflips = 0;
int sb = 0;
std::complex<double> shiftprod = z;
z += 1.0;
int nsb;
while (z.real() <= loggamma_SMALLX) {
shiftprod *= z;
nsb = std::signbit(shiftprod.imag());
signflips += nsb != 0 && sb == 0 ? 1 : 0;
sb = nsb;
z += 1.0;
}
return loggamma_stirling(z) - std::log(shiftprod) - signflips * 2 * M_PI * std::complex<double>(0, 1);
}
XSF_HOST_DEVICE std::complex<double> loggamma_taylor(std::complex<double> z) {
/* Taylor series for log-Gamma around z = 1.
*
* It is
*
* loggamma(z + 1) = -gamma*z + zeta(2)*z**2/2 - zeta(3)*z**3/3 ...
*
* where gamma is the Euler-Mascheroni constant.
*/
double coeffs[] = {
-4.3478266053040259361E-2, 4.5454556293204669442E-2, -4.7619070330142227991E-2, 5.000004769810169364E-2,
-5.2631679379616660734E-2, 5.5555767627403611102E-2, -5.8823978658684582339E-2, 6.2500955141213040742E-2,
-6.6668705882420468033E-2, 7.1432946295361336059E-2, -7.6932516411352191473E-2, 8.3353840546109004025E-2,
-9.0954017145829042233E-2, 1.0009945751278180853E-1, -1.1133426586956469049E-1, 1.2550966952474304242E-1,
-1.4404989676884611812E-1, 1.6955717699740818995E-1, -2.0738555102867398527E-1, 2.7058080842778454788E-1,
-4.0068563438653142847E-1, 8.2246703342411321824E-1, -5.7721566490153286061E-1};
z -= 1.0;
return z * cevalpoly(coeffs, 22, z);
}
} // namespace detail
XSF_HOST_DEVICE inline double loggamma(double x) {
if (x < 0.0) {
return std::numeric_limits<double>::quiet_NaN();
}
return cephes::lgam(x);
}
XSF_HOST_DEVICE inline float loggamma(float x) { return loggamma(static_cast<double>(x)); }
XSF_HOST_DEVICE inline std::complex<double> loggamma(std::complex<double> z) {
// Compute the principal branch of log-Gamma
if (std::isnan(z.real()) || std::isnan(z.imag())) {
return {std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN()};
}
if (z.real() <= 0 and z == std::floor(z.real())) {
set_error("loggamma", SF_ERROR_SINGULAR, NULL);
return {std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN()};
}
if (z.real() > detail::loggamma_SMALLX || std::abs(z.imag()) > detail::loggamma_SMALLY) {
return detail::loggamma_stirling(z);
}
if (std::abs(z - 1.0) < detail::loggamma_TAYLOR_RADIUS) {
return detail::loggamma_taylor(z);
}
if (std::abs(z - 2.0) < detail::loggamma_TAYLOR_RADIUS) {
// Recurrence relation and the Taylor series around 1.
return detail::zlog1(z - 1.0) + detail::loggamma_taylor(z - 1.0);
}
if (z.real() < 0.1) {
// Reflection formula; see Proposition 3.1 in [1]
double tmp = std::copysign(2 * M_PI, z.imag()) * std::floor(0.5 * z.real() + 0.25);
return std::complex<double>(detail::loggamma_LOGPI, tmp) - std::log(sinpi(z)) - loggamma(1.0 - z);
}
if (std::signbit(z.imag()) == 0) {
// z.imag() >= 0 but is not -0.0
return detail::loggamma_recurrence(z);
}
return std::conj(detail::loggamma_recurrence(std::conj(z)));
}
XSF_HOST_DEVICE inline std::complex<float> loggamma(std::complex<float> z) {
return static_cast<std::complex<float>>(loggamma(static_cast<std::complex<double>>(z)));
}
XSF_HOST_DEVICE inline double rgamma(double z) { return cephes::rgamma(z); }
XSF_HOST_DEVICE inline float rgamma(float z) { return rgamma(static_cast<double>(z)); }
XSF_HOST_DEVICE inline std::complex<double> rgamma(std::complex<double> z) {
// Compute 1/Gamma(z) using loggamma.
if (z.real() <= 0 && z == std::floor(z.real())) {
// Zeros at 0, -1, -2, ...
return 0.0;
}
return std::exp(-loggamma(z));
}
XSF_HOST_DEVICE inline std::complex<float> rgamma(std::complex<float> z) {
return static_cast<std::complex<float>>(rgamma(static_cast<std::complex<double>>(z)));
}
} // namespace xsf
|