JuliaMath / SpecialFunctions.jl

Special mathematical functions in Julia

Home Page:https://specialfunctions.juliamath.org/stable/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

More gamma_inc_inv Failures

Deduction42 opened this issue · comments

Hello,

The last patch fixed a lot of the issues I was running into with Distributions.jl. However, I was still able to trigger two more (unique) failure cases. When calling via Distributions.jl, I was able to trigger one case with this

dG = Gamma{Float64}(0.0016546512046778552, 0.21745036229505915)
q = 1-0.7070707070707071
quantile(dG,q)

ERROR: DomainError with -9.5893e-320:
log will only return a complex result if called with a complex argument. Try log(Complex(x)).
Stacktrace:
 [1] throw_complex_domainerror(f::Symbol, x::Float64)
   @ Base.Math .\math.jl:33
 [2] _log(x::Float64, base::Val{:ℯ}, func::Symbol)
   @ Base.Math .\special\log.jl:304
 [3] log
   @ .\special\log.jl:269 [inlined]
 [4] __gamma_inc_inv(a::Float64, minpq::Float64, pcase::Bool)
   @ SpecialFunctions C:\Users\usr\.julia\packages\SpecialFunctions\rNt4H\src\gamma_inc.jl:971
 [5] _gamma_inc_inv
   @ C:\Users\usr\.julia\packages\SpecialFunctions\rNt4H\src\gamma_inc.jl:935 [inlined]
 [6] gamma_inc_inv
   @ C:\Users\usr\.julia\packages\SpecialFunctions\rNt4H\src\gamma_inc.jl:917 [inlined]
 [7] gammainvcdf
   @ C:\Users\usr\.julia\packages\StatsFuns\dTYga\src\distrs\gamma.jl:98 [inlined]
 [8] quantile(d::Gamma{Float64}, q::Float64)
   @ Distributions C:\Users\usr\.julia\packages\Distributions\T2SAc\src\univariates.jl:627
 [9] top-level scope
   @ REPL[4]:1

The second failure case was triggered with this:

dG = Gamma{Float64}(1.0309015068677239, 0.03724188228734454)
q = 1-0.020202020202020204

quantile(dG, q)
ERROR: DomainError with (1.0309015068677239, -380.7187661907244, 0):
`a` and `x` must be greater than 0 ---- Domain : (0, Inf)
Stacktrace:
 [1] _gamma_inc(a::Float64, x::Float64, ind::Int64)
   @ SpecialFunctions C:\Users\usr\.julia\packages\SpecialFunctions\rNt4H\src\gamma_inc.jl:787
 [2] gamma_inc
   @ C:\Users\usr\.julia\packages\SpecialFunctions\rNt4H\src\gamma_inc.jl:781 [inlined]
 [3] __gamma_inc_inv(a::Float64, minpq::Float64, pcase::Bool)
   @ SpecialFunctions C:\Users\usr\.julia\packages\SpecialFunctions\rNt4H\src\gamma_inc.jl:981
 [4] _gamma_inc_inv
   @ C:\Users\usr\.julia\packages\SpecialFunctions\rNt4H\src\gamma_inc.jl:935 [inlined]
 [5] gamma_inc_inv
   @ C:\Users\usr\.julia\packages\SpecialFunctions\rNt4H\src\gamma_inc.jl:917 [inlined]
 [6] gammainvcdf
   @ C:\Users\usr\.julia\packages\StatsFuns\dTYga\src\distrs\gamma.jl:98 [inlined]
 [7] quantile(d::Gamma{Float64}, q::Float64)
   @ Distributions C:\Users\usr\.julia\packages\Distributions\T2SAc\src\univariates.jl:627
 [8] top-level scope
   @ REPL[10]:1

Thanks for reporting these. It looks gamma_inc_inv isn't as robust as you could hope for. For easier reference in this package, the two error cases are:

julia> gamma_inc_inv(0.0016546512046778552, 1-0.7070707070707071, 0.7070707070707071)
x = x0 = 3.0e-323
x = x0 = 1.7944e-320
x = x0 = -9.5893e-320
ERROR: DomainError with -9.5893e-320:
log will only return a complex result if called with a complex argument. Try log(Complex(x)).
Stacktrace:
 [1] throw_complex_domainerror(f::Symbol, x::Float64)
   @ Base.Math ./math.jl:33
 [2] _log(x::Float64, base::Val{:ℯ}, func::Symbol)
   @ Base.Math ./special/log.jl:304
 [3] log
   @ ./special/log.jl:269 [inlined]
 [4] __gamma_inc_inv(a::Float64, minpq::Float64, pcase::Bool)
   @ SpecialFunctions ~/.julia/dev/SpecialFunctions/src/gamma_inc.jl:971
 [5] _gamma_inc_inv
   @ ~/.julia/dev/SpecialFunctions/src/gamma_inc.jl:935 [inlined]
 [6] gamma_inc_inv(a::Float64, p::Float64, q::Float64)
   @ SpecialFunctions ~/.julia/dev/SpecialFunctions/src/gamma_inc.jl:917
 [7] top-level scope
   @ REPL[54]:1

and

julia> gamma_inc_inv(1.0309015068677239, 1-0.020202020202020204, 0.020202020202020204)
x = x0 = 4.997466292519298
x = x0 = 2.905035646142073
x = x0 = 19.7726175242108
x = x0 = -380.71876619072816
ERROR: DomainError with (1.0309015068677239, -380.71876619072816, 0):
`a` and `x` must be greater than 0 ---- Domain : (0, Inf)
Stacktrace:
 [1] _gamma_inc(a::Float64, x::Float64, ind::Int64)
   @ SpecialFunctions ~/.julia/dev/SpecialFunctions/src/gamma_inc.jl:787
 [2] gamma_inc
   @ ~/.julia/dev/SpecialFunctions/src/gamma_inc.jl:781 [inlined]
 [3] __gamma_inc_inv(a::Float64, minpq::Float64, pcase::Bool)
   @ SpecialFunctions ~/.julia/dev/SpecialFunctions/src/gamma_inc.jl:981
 [4] _gamma_inc_inv
   @ ~/.julia/dev/SpecialFunctions/src/gamma_inc.jl:935 [inlined]
 [5] gamma_inc_inv(a::Float64, p::Float64, q::Float64)
   @ SpecialFunctions ~/.julia/dev/SpecialFunctions/src/gamma_inc.jl:917
 [6] top-level scope
   @ REPL[55]:1

I've printed the value of x during the Newton iterations to make it visible how it fails. I'm wondering if something is wrong in

fp = -sqrt(inv2π*a)*exp(-0.5*a*eta*eta)/gammax(a)

After a lot of testing (more than a million runs) I think I can find the two areas where things tend to fail. One case is where alpha is small and p is close to 0.5, and in the other area alpha is close to one as well as the p value. If these cases are taken care of, I'm pretty sure gamma_inc_inv will be quite robust.

for ii in 1:100000
       a = rand(Gamma(0.5,20))
       th = 1.0
       dG = Gamma(a,th)
       p = rand()
       
    try
       quantile(dG,p)
    catch
       display("Failure occurred with dG=$(dG), p = $(p)")
    end
end
"Failure occurred with dG=Gamma{Float64}(α=0.0012092790074495242, θ=1.0), p = 0.4082421355705157"
"Failure occurred with dG=Gamma{Float64}(α=1.0159294646390604, θ=1.0), p = 0.979133588311139"
"Failure occurred with dG=Gamma{Float64}(α=0.0008500643209799257, θ=1.0), p = 0.5328646109345743"
"Failure occurred with dG=Gamma{Float64}(α=1.0067186164460882, θ=1.0), p = 0.9783064756058487"
"Failure occurred with dG=Gamma{Float64}(α=1.0197172128374965, θ=1.0), p = 0.9782999473835832"
"Failure occurred with dG=Gamma{Float64}(α=1.0021613258145359, θ=1.0), p = 0.9796635761410678"
"Failure occurred with dG=Gamma{Float64}(α=0.0015145166954209457, θ=1.0), p = 0.32601660850450453"

At the moment, I have to wrap my quantile calls with a Try-Catch statement, return NaN, and try to interpolate out any NaNs that occur.

At the moment, I have to wrap my quantile calls with a Try-Catch statement, return NaN, and try to interpolate out any NaNs that occur.

A possibly easier solution until gamma_inc_inv is more robust is to pin StatsFuns to 0.9.15 and to not use 0.9.16. Then Rmath will be used instead of SpecialFunctions for computing the quantiles.

Oh, thanks! I didn't even have StatsFuns installed, I basically just had Distributions.jl and SpecialFuns.jl. I didn't know that installing a different version of StatsFuns even affected Distributions.jl.

Oh, thanks! I didn't even have StatsFuns installed, I basically just had Distributions.jl and SpecialFuns.jl.

If you install Distributions, StatsFuns is installed automatically as an indirect dependency. You can see the explicitly installed packages if you run ] st in the Julia REPL, and all, also automatically installed, packages with ] st --manifest.

So the second example here was a simple bug, see #391. The first example is more tricky. When evaluating the incomplete gamma function during the Newton iteration,

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

the first time, the input values are a=0.0016546512046778552 and x=3.0e-323. Our incomplete gamma function has this branch

elseif a*x == 0.0
if x <= a
return (0.0, 1.0)
else
return (1.0, 0.0)
end

so the result ends up as (0.0, 1.0) which eventually leads to a Newton step that makes x slightly negative. In contrast, https://arxiv.org/pdf/1306.1754.pdf is able to evaluate the first inputs to (0.29291549720279125, 0.70708450279720880). We could consider changing the implementation of the incomplete gamma function but I suspect it's just not reliable to compute results for subnormal inputs. Alternatively, we should figure out a way to modify the Newton iteration to stop if the step causes a negative x.

I reached out to Amparo Gil, and she gave us permission to MIT license Julia translations of their Fortran code so we should look into translating those.