/* 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 loggamma_stirling(std::complex 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 rz = 1.0 / z; std::complex rzz = rz / z; return (z - 0.5) * std::log(z) - z + loggamma_HLOG2PI + rz * cevalpoly(coeffs, 7, rzz); } XSF_HOST_DEVICE std::complex loggamma_recurrence(std::complex z) { /* Backward recurrence relation. * * See Proposition 2.2 in [1] and the Julia implementation [2]. * */ int signflips = 0; int sb = 0; std::complex 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(0, 1); } XSF_HOST_DEVICE std::complex loggamma_taylor(std::complex 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::quiet_NaN(); } return cephes::lgam(x); } XSF_HOST_DEVICE inline float loggamma(float x) { return loggamma(static_cast(x)); } XSF_HOST_DEVICE inline std::complex loggamma(std::complex z) { // Compute the principal branch of log-Gamma if (std::isnan(z.real()) || std::isnan(z.imag())) { return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; } if (z.real() <= 0 and z == std::floor(z.real())) { set_error("loggamma", SF_ERROR_SINGULAR, NULL); return {std::numeric_limits::quiet_NaN(), std::numeric_limits::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(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 loggamma(std::complex z) { return static_cast>(loggamma(static_cast>(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(z)); } XSF_HOST_DEVICE inline std::complex rgamma(std::complex 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 rgamma(std::complex z) { return static_cast>(rgamma(static_cast>(z))); } } // namespace xsf