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