Skip to content

Commit 1ab6db4

Browse files
authored
Merge pull request #389 from JuliaMath/an/backport
2 parents 9cc7a80 + acb7788 commit 1ab6db4

File tree

2 files changed

+67
-59
lines changed

2 files changed

+67
-59
lines changed

src/gamma_inc.jl

Lines changed: 44 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ end
179179
"""
180180
lambdaeta(eta)
181181
182-
Compute the value of eta satisfying ``eta^{2}/2 = \\lambda-1-\\ln{\\lambda}``.
182+
Compute the value of ``\\lambda`` satisfying ``\\eta^{2}/2 = \\lambda-1-\\log{\\lambda}``.
183183
"""
184184
function lambdaeta(eta::Float64)
185185
s = eta*eta*0.5
@@ -665,7 +665,7 @@ function gamma_inc_fsum(a::Float64, x::Float64)
665665
end
666666

667667
"""
668-
gamma_inc_inv_psmall(a,p)
668+
gamma_inc_inv_psmall(a, logr)
669669
670670
Compute `x0` - initial approximation when `p` is small.
671671
Here we invert the series in Eqn (2.20) in the paper and write the inversion problem as:
@@ -675,8 +675,7 @@ x = r\\left[1 + a\\sum_{k=1}^{\\infty}\\frac{(-x)^{n}}{(a+n)n!}\\right]^{-1/a},
675675
where ``r = (p\\Gamma(1+a))^{1/a}``
676676
Inverting this relation we obtain ``x = r + \\sum_{k=2}^{\\infty}c_{k}r^{k}``.
677677
"""
678-
function gamma_inc_inv_psmall(a::Float64, p::Float64)
679-
logr = (1.0/a)*(log(p) + logabsgamma(a + 1.0)[1])
678+
function gamma_inc_inv_psmall(a::Float64, logr::Float64)
680679
r = exp(logr)
681680
ap1 = a + 1.0
682681
ap1² = ap1*ap1
@@ -694,7 +693,7 @@ function gamma_inc_inv_psmall(a::Float64, p::Float64)
694693
end
695694

696695
"""
697-
gamma_inc_inv_qsmall(a,q)
696+
gamma_inc_inv_qsmall(a, q, qgammaxa)
698697
699698
Compute `x0` - initial approximation when `q` is small from ``e^{-x_{0}} x_{0}^{a} = q \\Gamma(a)``.
700699
Asymptotic expansions Eqn (2.29) in the paper is used here and higher approximations are obtained using
@@ -703,9 +702,9 @@ x \\sim x_{0} - L + b \\sum_{k=1}^{\\infty} d_{k}/x_{0}^{k}
703702
```
704703
where ``b = 1-a``, ``L = \\ln{x_0}``.
705704
"""
706-
function gamma_inc_inv_qsmall(a::Float64, q::Float64)
705+
function gamma_inc_inv_qsmall(a::Float64, q::Float64, qgammaxa::Float64)
707706
b = 1.0 - a
708-
eta = sqrt(-2/a*log(q*gammax(a)*sqrt(twoπ/a)))
707+
eta = sqrt(-2/a*log(qgammaxa))
709708
x0 = a*lambdaeta(eta)
710709
l = log(x0)
711710

@@ -716,7 +715,9 @@ function gamma_inc_inv_qsmall(a::Float64, q::Float64)
716715
ck3 = (@horner(l, @horner(b, -12, -24, -11), @horner(b, 12, 24, 6), @horner(b, -6, -9), 2))/6.0
717716
ck4 = (@horner(l, @horner(b, 72, 162, 120, 25), @horner(b, -72, -168, -114, -12), @horner(b, 36, 84, 36), @horner(b, -12, -22), 3))/12.0
718717
x0 = x0 - l + b*r*@horner(r, ck1, ck2, ck3, ck4)
719-
else
718+
elseif x0 > 1
719+
# The x0 > 1 condition isn't in the original version but without it
720+
# the update in the branch can cause negative initial x0
720721
r = 1.0/x0
721722
= l*l
722723
ck1 = l - 1.0
@@ -726,19 +727,7 @@ function gamma_inc_inv_qsmall(a::Float64, q::Float64)
726727
end
727728

728729
"""
729-
gamma_inc_inv_asmall(a,p,q,pcase)
730-
731-
Compute `x0` - initial approximation when `a` is small.
732-
Here the solution `x` of ``P(a,x)=p`` satisfies ``x_{l} < x < x_{u}``
733-
where ``x_{l} = (p\\Gamma(a+1))^{1/a}`` and ``x_{u} = -\\log{(1 - p\\Gamma(a+1))}``, and is used as starting value for Newton iteration.
734-
"""
735-
function gamma_inc_inv_asmall(a::Float64, p::Float64, q::Float64, pcase::Bool)
736-
logp = (pcase) ? log(p) : log1p(-q)
737-
return exp((1.0/a)*(logp +loggamma1p(a)))
738-
end
739-
740-
"""
741-
gamma_inc_inv_alarge(a,porq,s)
730+
gamma_inc_inv_alarge(a, minpq, pcase)
742731
743732
Compute `x0` - initial approximation when `a` is large.
744733
The inversion problem is rewritten as :
@@ -753,9 +742,10 @@ and it is possible to expand:
753742
which is calculated by coeff1, coeff2 and coeff3 functions below.
754743
This returns a tuple `(x0,fp)`, where `fp` is computed since it's an approximation for the coefficient after inverting the original power series.
755744
"""
756-
function gamma_inc_inv_alarge(a::Float64, porq::Float64, s::Integer)
757-
r = erfcinv(2*porq)
758-
eta = s*r/sqrt(a*0.5)
745+
function gamma_inc_inv_alarge(a::Float64, minpq::Float64, pcase::Bool)
746+
r = erfcinv(2*minpq)
747+
s = r/sqrt(a*0.5)
748+
eta = pcase ? -s : s
759749
eta += (coeff1(eta) + (coeff2(eta) + coeff3(eta)/a)/a)/a
760750
x0 = a*lambdaeta(eta)
761751
fp = -sqrt(inv2π*a)*exp(-0.5*a*eta*eta)/gammax(a)
@@ -923,45 +913,50 @@ External links: [DLMF](https://dlmf.nist.gov/8.2.4), [Wikipedia](https://en.wiki
923913
924914
See also: [`gamma_inc(a,x,ind)`](@ref SpecialFunctions.gamma_inc).
925915
"""
926-
gamma_inc_inv(a::Real, p::Real, q::Real) = _gamma_inc_inv(promote(float(a), float(p), float(q))...)
927-
928-
function _gamma_inc_inv(a::Float64, p::Float64, q::Float64)
916+
function gamma_inc_inv(a::Real, p::Real, q::Real)
917+
return _gamma_inc_inv(map(float, promote(a, p, q))...)
918+
end
929919

920+
# `gamma inc_inv` ensures that arguments of `_gamma_inc_inv` are
921+
# floating point numbers of the same type
922+
function _gamma_inc_inv(a::T, p::T, q::T) where {T<:Real}
930923
if p + q != 1
931924
throw(ArgumentError("p + q must equal one but is $(p + q)"))
932925
end
933926

934927
if iszero(p)
935-
return 0.0
928+
return zero(T)
936929
elseif iszero(q)
937-
return Inf
930+
return T(Inf)
938931
end
939932

940-
if p < 0.5
941-
pcase = true
942-
porq = p
943-
s = -1
944-
else
945-
pcase = false
946-
porq = q
947-
s = 1
948-
end
933+
pcase = p < 0.5
934+
minpq = pcase ? p : q
935+
return __gamma_inc_inv(a, minpq, pcase)
936+
end
937+
938+
function __gamma_inc_inv(a::Float64, minpq::Float64, pcase::Bool)
949939
haseta = false
950940

951-
logr = (1.0/a)*(log(p) + logabsgamma(a + 1.0)[1])
941+
logp = pcase ? log(minpq) : log1p(-minpq)
942+
loggamma1pa = a <= 1.0 ? loggamma1p(a) : loggamma(a + 1.0)
943+
logr = (logp + loggamma1pa) / a
952944
if logr < log(0.2*(1 + a)) #small value of p
953-
x0 = gamma_inc_inv_psmall(a, p)
954-
elseif ((q < min(0.02, exp(-1.5*a)/gamma(a))) && (a < 10)) #small q
955-
x0 = gamma_inc_inv_qsmall(a, q)
956-
elseif abs(porq - 0.5) < 1.0e-05
945+
x0 = gamma_inc_inv_psmall(a, logr)
946+
elseif !pcase && a < 10 && minpq < 0.02 && (qgammaxa = minpq*gammax(a)*sqrt(twoπ/a)) < 1 #small q
947+
# This deviates from the original version. The qgammaxa variable
948+
# here ensures that the argument of sqrt in gamma_inc_inv_qsmall
949+
# is positive
950+
x0 = gamma_inc_inv_qsmall(a, minpq, qgammaxa)
951+
elseif abs(minpq - 0.5) < 1.0e-05
957952
x0 = a - 1.0/3.0 + (8.0/405.0 + 184.0/25515.0/a)/a
958953
elseif abs(a - 1.0) < 1.0e-4
959-
x0 = pcase ? -log1p(-p) : -log(q)
954+
x0 = pcase ? -log1p(-minpq) : -log(minpq)
960955
elseif a < 1.0 # small value of a
961-
x0 = gamma_inc_inv_asmall(a, p, q, pcase)
956+
x0 = exp(logr)
962957
else #large a
963958
haseta = true
964-
x0, fp = gamma_inc_inv_alarge(a, porq, s)
959+
x0, fp = gamma_inc_inv_alarge(a, minpq, pcase)
965960
end
966961

967962
t = 1
@@ -985,7 +980,7 @@ function _gamma_inc_inv(a::Float64, p::Float64, q::Float64)
985980

986981
px, qx = gamma_inc(a, x, 0)
987982

988-
ck1 = pcase ? -r*(px - p) : r*(qx - q)
983+
ck1 = pcase ? -r*(px - minpq) : r*(qx - minpq)
989984
if a > 0.05
990985
ck2 = (x - a + 1.0)/(2.0*x)
991986

@@ -1018,16 +1013,8 @@ function _gamma_inc_inv(a::Float64, p::Float64, q::Float64)
10181013
return x
10191014
end
10201015

1021-
function _gamma_inc_inv(a::T, p::T, q::T) where {T <: Union{Float16, Float32}}
1022-
if p + q != one(T)
1023-
throw(ArgumentError("p + q must equal one but was $(p + q)"))
1024-
end
1025-
p64, q64 = if p < q
1026-
(Float64(p), 1 - Float64(p))
1027-
else
1028-
(1 - Float64(q), Float64(q))
1029-
end
1030-
return T(_gamma_inc_inv(Float64(a), p64, q64))
1016+
function __gamma_inc_inv(a::T, minpq::T, pcase::Bool) where {T<:Union{Float16,Float32}}
1017+
return T(__gamma_inc_inv(Float64(a), Float64(minpq), pcase))
10311018
end
10321019

10331020
# like promote(x,y), but don't complexify real values

test/gamma_inc.jl

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,29 @@ end
177177
@testset "Low precision with Float64(p) + Float64(q) != 1" for T in (Float16, Float32)
178178
@test gamma_inc(T(1.0), gamma_inc_inv(T(1.0), T(0.1), T(0.9)))[1]::T T(0.1)
179179
@test gamma_inc(T(1.0), gamma_inc_inv(T(1.0), T(0.9), T(0.1)))[2]::T T(0.1)
180-
@test_throws ArgumentError("p + q must equal one but was 1.02") gamma_inc_inv(T(1.0), T(0.1), T(0.92))
181-
@test_throws ArgumentError("p + q must equal one but was 1.02") gamma_inc_inv(T(1.0), T(0.92), T(0.1))
180+
@test_throws ArgumentError("p + q must equal one but is 1.02") gamma_inc_inv(T(1.0), T(0.1), T(0.92))
181+
@test_throws ArgumentError("p + q must equal one but is 1.02") gamma_inc_inv(T(1.0), T(0.92), T(0.1))
182+
end
183+
184+
@testset "Promotion of arguments" begin
185+
@test @inferred(gamma_inc_inv(1//2, 0.3f0, 0.7f0)) isa Float32
186+
@test @inferred(gamma_inc_inv(1, 0.2f0, 0.8f0)) isa Float32
187+
end
188+
189+
@testset "Issue 385" begin
190+
a = 0.010316813105574363
191+
q = 0.010101010101010102
192+
@test last(gamma_inc(a, gamma_inc_inv(a, 1 - q, q))) q
193+
end
194+
195+
@testset "Issue 387" begin
196+
n = 1000
197+
@testset "a=$a" for a in exp10.(range(-20, stop=20, length=n))
198+
@testset "x=$x" for x = exp10.(range(-20, stop=2, length=n))
199+
p, q = gamma_inc(a, x)
200+
@test p < floatmin() || q < floatmin() || gamma_inc_inv(a, p, q) x
201+
end
202+
end
182203
end
183204
end
184205

0 commit comments

Comments
 (0)