Skip to content

Commit d18ff04

Browse files
authored
WIP: add loggamma(a,z) (#283)
* add loggamma(a,z) * note branch cut mismatch * test large args * more tests, fix a domainerror
1 parent e9fc8eb commit d18ff04

File tree

5 files changed

+79
-13
lines changed

5 files changed

+79
-13
lines changed

docs/src/functions_list.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,14 @@ SpecialFunctions.ellipe
5757
SpecialFunctions.eta
5858
SpecialFunctions.zeta
5959
SpecialFunctions.gamma(::Number)
60+
SpecialFunctions.loggamma(::Number)
61+
SpecialFunctions.logabsgamma
62+
SpecialFunctions.logfactorial
6063
SpecialFunctions.gamma(::Number,::Number)
64+
SpecialFunctions.loggamma(::Number,::Number)
6165
SpecialFunctions.gamma_inc
6266
SpecialFunctions.gamma_inc_inv
6367
SpecialFunctions.beta_inc
64-
SpecialFunctions.loggamma
65-
SpecialFunctions.logabsgamma
66-
SpecialFunctions.logfactorial
6768
SpecialFunctions.beta
6869
SpecialFunctions.logbeta
6970
SpecialFunctions.logabsbeta

docs/src/functions_overview.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@ Here the *Special Functions* are listed according to the structure of [NIST Digi
66
| Function | Description |
77
|:-------- |:----------- |
88
| [`gamma(z)`](@ref SpecialFunctions.gamma(::Number)) | [gamma function](https://en.wikipedia.org/wiki/Gamma_function) ``\Gamma(z)`` |
9-
| [`digamma(x)`](@ref SpecialFunctions.digamma) | [digamma function](https://en.wikipedia.org/wiki/Digamma_function) (i.e. the derivative of `lgamma` at `x`) |
9+
| [`loggamma(x)`](@ref SpecialFunctions.loggamma(::Number)) | accurate `log(gamma(x))` for large `x` |
10+
| [`logabsgamma(x)`](@ref SpecialFunctions.logabsgamma) | accurate `log(abs(gamma(x)))` for large `x` |
11+
| [`logfactorial(x)`](@ref SpecialFunctions.logfactorial) | accurate `log(factorial(x))` for large `x`; same as `loggamma(x+1)` for `x > 1`, zero otherwise |
12+
| [`digamma(x)`](@ref SpecialFunctions.digamma) | [digamma function](https://en.wikipedia.org/wiki/Digamma_function) (i.e. the derivative of `loggamma` at `x`) |
1013
| [`invdigamma(x)`](@ref SpecialFunctions.invdigamma) | [invdigamma function](http://bariskurt.com/calculating-the-inverse-of-digamma-function/) (i.e. inverse of `digamma` function at `x` using fixed-point iteration algorithm) |
1114
| [`trigamma(x)`](@ref SpecialFunctions.trigamma) | [trigamma function](https://en.wikipedia.org/wiki/Trigamma_function) (i.e the logarithmic second derivative of `gamma` at `x`) |
12-
| [`polygamma(m,x)`](@ref SpecialFunctions.polygamma) | [polygamma function](https://en.wikipedia.org/wiki/Polygamma_function) (i.e the (m+1)-th derivative of the `lgamma` function at `x`) |
15+
| [`polygamma(m,x)`](@ref SpecialFunctions.polygamma) | [polygamma function](https://en.wikipedia.org/wiki/Polygamma_function) (i.e the (m+1)-th derivative of the `loggamma` function at `x`) |
1316
| [`gamma(a,z)`](@ref SpecialFunctions.gamma(::Number,::Number)) | [upper incomplete gamma function ``\Gamma(a,z)``](https://en.wikipedia.org/wiki/Incomplete_gamma_function) |
17+
| [`loggamma(a,z)`](@ref SpecialFunctions.loggamma(::Number,::Number)) | accurate `log(gamma(a,x))` for large arguments |
1418
| [`gamma_inc(a,x,IND)`](@ref SpecialFunctions.gamma_inc) | [incomplete gamma function ratio P(a,x) and Q(a,x)](https://en.wikipedia.org/wiki/Incomplete_gamma_function) (i.e evaluates P(a,x) and Q(a,x)for accuracy specified by IND and returns tuple (p,q)) |
1519
| [`beta_inc(a,b,x,y)`](@ref SpecialFunctions.beta_inc) | [incomplete beta function ratio Ix(a,b) and Iy(a,b)](https://en.wikipedia.org/wiki/Beta_function#Incomplete_beta_function) (i.e evaluates Ix(a,b) and Iy(a,b) and returns tuple (p,q)) |
1620
| [`gamma_inc_inv(a,p,q)`](@ref SpecialFunctions.gamma_inc_inv) | [inverse of incomplete gamma function ratio P(a,x) and Q(a,x)](https://en.wikipedia.org/wiki/Incomplete_gamma_function) (i.e evaluates x given P(a,x)=p and Q(a,x)=q |
17-
| [`loggamma(x)`](@ref SpecialFunctions.loggamma) | accurate `log(gamma(x))` for large `x` |
18-
| [`logabsgamma(x)`](@ref SpecialFunctions.logabsgamma) | accurate `log(abs(gamma(x)))` for large `x` |
19-
| [`lgamma(x)`](@ref SpecialFunctions.lgamma) | accurate `log(gamma(x))` for large `x` |
20-
| [`logfactorial(x)`](@ref SpecialFunctions.logfactorial) | accurate `log(factorial(x))` for large `x`; same as `lgamma(x+1)` for `x > 1`, zero otherwise |
2121
| [`beta(x,y)`](@ref SpecialFunctions.beta) | [beta function](https://en.wikipedia.org/wiki/Beta_function) at `x,y` |
2222
| [`logbeta(x,y)`](@ref SpecialFunctions.logbeta) | accurate `log(beta(x,y))` for large `x` or `y` |
2323
| [`logabsbeta(x,y)`](@ref SpecialFunctions.logabsbeta) | accurate `log(abs(beta(x,y)))` for large `x` or `y` |

src/gamma.jl

+6-3
Original file line numberDiff line numberDiff line change
@@ -658,16 +658,19 @@ function logabsgamma end
658658
Computes the logarithm of [`gamma`](@ref) for given `x`. If `x` is a `Real`, then it
659659
throws a `DomainError` if `gamma(x)` is negative.
660660
661-
See also [`logabsgamma`](@ref).
661+
If `x` is complex, then `exp(loggamma(x))` matches `gamma(x)` (up to floating-point error),
662+
but `loggamma(x)` may differ from `log(gamma(x))` by an integer multiple of ``2\\pi i``
663+
(i.e. it may employ a different branch cut).
664+
665+
See also [`logabsgamma`](@ref) for real `x`.
662666
"""
663-
function loggamma end
667+
loggamma(x::Number) = loggamma(float(x))
664668

665669
function loggamma(x::Real)
666670
(y, s) = logabsgamma(x)
667671
s < 0.0 && throw(DomainError(x, "`gamma(x)` must be non-negative"))
668672
return y
669673
end
670-
loggamma(x::Number) = loggamma(float(x))
671674

672675
# asymptotic series for log(gamma(z)), valid for sufficiently large real(z) or |imag(z)|
673676
function loggamma_asymptotic(z::Complex{Float64})

src/gamma_inc.jl

+43-1
Original file line numberDiff line numberDiff line change
@@ -1079,7 +1079,7 @@ function _gamma(a::Number,x::Number)
10791079
end
10801080
end
10811081
end
1082-
return iszero(x) ? one(x)*gamma(a) : x^a * expint(1-a, x)
1082+
return iszero(x) ? gamma(one(x)*a) : x^a * expint(1-a, x)
10831083
end
10841084

10851085
_gamma(a::Integer, x::BigFloat) = _gamma_big(a, x)
@@ -1099,3 +1099,45 @@ function _gamma_big(a::Real,x::Real)
10991099
ccall((:mpfr_gamma_inc, :libmpfr), Int32 , (Ref{BigFloat} , Ref{BigFloat} , Ref{BigFloat} , Int32) , z , a , x , ROUNDING_MODE[])
11001100
return z
11011101
end
1102+
1103+
"""
1104+
loggamma(a,x)
1105+
1106+
Returns the log of the upper incomplete gamma function [`gamma(a,x)`](@ref):
1107+
```math
1108+
\\log \\Gamma(a,x) = \\log \\int_x^\\infty t^{a-1} e^{-t} dt \\, ,
1109+
```
1110+
supporting arbitrary real or complex `a` and `x`.
1111+
1112+
If `a` and/or `x` is complex, then `exp(loggamma(a,x))` matches `gamma(a,x)` (up to floating-point error),
1113+
but `loggamma(a,x)` may differ from `log(gamma(a,x))` by an integer multiple of ``2\\pi i``
1114+
(i.e. it may employ a different branch cut).
1115+
1116+
See also [`loggamma(x)`](@ref).
1117+
"""
1118+
loggamma(a::Number,x::Number) = _loggamma(promotereal(float(a),float(x))...)
1119+
loggamma(a::Integer,x::Number) = _loggamma(a, float(x))
1120+
function _loggamma(a::Number,x::Number)
1121+
if a isa Real && x isa Real && !isfinite(a*x)
1122+
if isinf(x) && isfinite(a)
1123+
if x > 0 # == +Inf
1124+
return -one(a)*x
1125+
elseif x < 0
1126+
throw(DomainError((a,x), "loggamma will only return a complex result if called with a complex argument"))
1127+
end
1128+
elseif isinf(a) && isfinite(x)
1129+
if a > 0 && x 0
1130+
return a*one(x) # +Inf
1131+
elseif a < 0
1132+
return a*one(x) # -Inf
1133+
end
1134+
end
1135+
end
1136+
# from gamma(a,x) = x^a * expintx(1-a, x) * exp(-x):
1137+
iszero(x) && return loggamma(one(x)*a)
1138+
if x isa Real && x < 0 && a isa Integer && isodd(a)
1139+
# minus signs in expintx and x^a may cancel
1140+
return a*log(-x) + log(-expintx(1-a, x)) - x
1141+
end
1142+
return a*log(x) + log(expintx(1-a, x)) - x
1143+
end

test/gamma_inc.jl

+20
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,23 @@ double(x::Complex) = ComplexF64(x)
100100
@test gamma(2, -Inf) == -Inf
101101
@test_throws DomainError gamma(2.2, -Inf)
102102
end
103+
104+
@testset "upper incomplete gamma function logarithm" begin
105+
for (a,z) in ((3,5), (3,-5), (3,5+4im), (3,-5+4im), (3,5-4im), (-3,5+4im), (-3,-5+4im))
106+
@test exp(loggamma(a,z)) gamma(a,z) rtol=1e-13
107+
end
108+
@test loggamma(50, 1e7) -9.9992102133082030308153473164868727954977460876571275797855e6
109+
@test real(loggamma(50, 1e7 + 1e8im)) -9.999097142860392e6
110+
@test cis(imag(loggamma(50, 1e7 + 1e8im))) cis(1.0275220422549918) rtol=1e-8
111+
@test real(loggamma(10+20im, -1e5 + 1e8im)) 100134.3502048662475864409896160245625409130538724704329203542339
112+
@test cis(imag(loggamma(10+20im, -1e5 + 1e8im))) cis(-2.6572071454623347) rtol=1e-8
113+
@test loggamma(-1e8, 1e9) -3.0723266045132171331933746054573197040165846554476396719312e9
114+
@test loggamma(3, Inf) == -Inf
115+
@test_throws DomainError loggamma(3, -Inf)
116+
@test loggamma(Inf, 3.2) == Inf
117+
@test loggamma(-Inf, 3.2) == -Inf
118+
@test_throws DomainError loggamma(Inf, -3.2)
119+
@test loggamma(117.3, 0) == loggamma(117.3)
120+
@test loggamma(7, -300.2) log(gamma(7, -300.2))
121+
@test_throws DomainError loggamma(6, -3.2)
122+
end

0 commit comments

Comments
 (0)