JuliaMath / SpecialFunctions.jl

Special mathematical functions in Julia
https://specialfunctions.juliamath.org/stable/
Other
359 stars 100 forks source link

Derivatives of the incomplete gamma functions with respect to parameter `a` are missing #317

Open devmotion opened 3 years ago

devmotion commented 3 years ago

Copied from https://github.com/JuliaMath/SpecialFunctions.jl/pull/305:

I added ChainRules definitions for gamma(a, x), loggamma(a, x), and gamma_inc according to https://functions.wolfram.com/GammaBetaErf/GammaRegularized/introductions/Gammas/ShowAll.html . Similar to the Bessel functions, derivatives with respect to the first argument a are not implemented since they are given in terms of a (regularized) hypergeometric function.

This issue is a reminder that these derivatives are missing. Moreover, the issue can be linked in the error message displayed to users when they try to access and perform computations with these missing derivatives.

lrnv commented 1 year ago

Looks like the error message does not link this issue yet, I had to find it by hand after hitting the "no derivative w.r.t a" problem in my code.

From https://en.wikipedia.org/wiki/Incomplete_gamma_function#Lower_incomplete_gamma_function it looks like there exists a way to compute this derivative through the Meijer G function. Is this funciton implmeented somewhere ? Is there a way to patch this even a little dirty ?

Edit : Might have to do with #265. Also, the package HypergeometricFunctions.jl might help if the regularized hypergeometric function proposed by wolframalpha for the derivative is indeed in this package.

Edit: Link to discussion in discourse : https://discourse.julialang.org/t/gamma-inc-derivatives/93148

Edit: I got it :

using SpecialFunctions, HypergeometricFunctions
function Γ(a,x)
    return gamma_inc(a,x)[2]
end
function ∂Γ_∂a(a,x)
    g = gamma(a)
    dg = digamma(a)
    G = Γ(a,x)
    lx = log(x)
    r = pFq([a,a],[a+1,a+1],-x)
    return a^(-2) * x^a * r/g + (1 - G)*(dg - lx)
end
function emp(a,x)
    ϵ = 1e-5
    return (Γ(a+ϵ,x) - Γ(a-ϵ,x))/2ϵ
end

a = 1.9
x = 1.8
Γ(a,x)
(∂Γ_∂a(a,x), emp(a,x))

# works correctly for x = 1 or a=1 or a=2. Does not work correclty for other valyes i tried. 
# which might mean that my formula is somewhat wrong. 
a = 0.1:0.1:5
x = 0.1:0.1:5
diff(a,x) = abs(∂Γ_∂a(a,x) - emp(a,x))
maximum(diff.(a,x')) # 2e-10 on my machine. 

Is there a chance that something like this could be added to the package ?

sethaxen commented 1 year ago

With package extensions, perhaps it would be possible to add a SpecialFunctionsChainRulesCoreHypergeometricFunctionsExt that defines the rules with respect to the first argument. Then either a fallback rule could be added in the SpecialFunctionsChainRulesCoreExt extension to inform the user they need to manually load HypergeometricFunctions to use that functionality, or perhaps Base.Experimental.register_error_hint could be used to warn the user.

devmotion commented 1 year ago

Is this really possible? HypergeometricFunctions depends on SpecialFunctions, so I wonder if it can be a weak dependency at all (I am sure it can't be a proper dependency at least since it would create circular dependencies?). Maybe it could be an extension of HypergeometricFunctions but this seems a bit more far fetched.

sethaxen commented 1 year ago

Is this really possible? HypergeometricFunctions depends on SpecialFunctions, so I wonder if it can be a weak dependency at all (I am sure it can't be a proper dependency at least since it would create circular dependencies?).

Ah, no, it seems circularity is not okay even for weak deps:

┌ Warning: Circular dependency detected. Precompilation will be skipped for:
│   DualNumbers [fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74]
│   SpecialFunctionsChainRulesCoreHypergeometricFunctionsExt [58512b41-50c9-55ec-9df1-0539e334355e]
│   HypergeometricFunctions [34004b35-14d8-5ef3-9330-4cdb6864b03a]
└ @ Pkg.API ~/.julia/juliaup/julia-1.9.1+0.x64.linux.gnu/share/julia/stdlib/v1.9/Pkg/src/API.jl:1234
Precompiling project...
  1 dependency successfully precompiled in 1 seconds. 19 already precompiled. 3 skipped due to circular dependency.

Maybe it could be an extension of HypergeometricFunctions but this seems a bit more far fetched.

I agree, this is strange.

All we really need is $_2F_2$, which in HypergeometricFunctions uses the default pFq, which requires only 3 methods:

I wonder if these 3 methods could be duplicated here as internal functions to break the circularity.

lrnv commented 1 year ago

I remember that at the time, in this issue https://github.com/JuliaMath/HypergeometricFunctions.jl/issues/50 , @MikaelSlevinsky gave a faster version for this derivative (much faster than the upper code). Still depending on HypergeometricFunctions.jl though so I do not really know where it should be implemented.

sethaxen commented 1 year ago

Thanks for the pointer, @lrnv . Agreed it is likely faster to differentiate through a hypergeometric function if supported than to implement the derivative separately.

@devmotion any objections to the plan proposed in https://github.com/JuliaMath/SpecialFunctions.jl/issues/317#issuecomment-1598303124?

devmotion commented 1 year ago

Generally, I'm not a fan of duplicating code, in particular not in dependent packages. But I don't have a better idea right now (and, even though a bit weird, the duplication issue could be resolved if HypergeometricFunctions starts re-exporting the functionality in SpecialFunctions). I'm not sure if e.g. breaking up SpecialFunctions is smaller packages (which are bundled by SpecialFunctions) would help in this case - if HypergeometricFunctions continues depending on SpecialFunctions I assume we would still run into these circular dependency issues.

Based on the previous comment, I think we should benchmark your suggestion against using HypergeometricFunctions.M (performance-wise and numerically) as suggested in https://github.com/JuliaMath/HypergeometricFunctions.jl/issues/50. This alternative approach would also only require to copy a few lines.

oschulz commented 1 year ago

Just curious, any news on this? (I recently ran into a case that needs both partial derivatives of incomplete gamma.)

lrnv commented 1 year ago

@oschulz you should use the proposal from https://github.com/JuliaMath/HypergeometricFunctions.jl/issues/50#issuecomment-1397363491 that's the best options right now.

oschulz commented 1 year ago

Thanks @lrnv !

mleprovost commented 6 months ago

Hello,

I have a related question: I would like to perform AD for the function gamma_inc_inv at https://github.com/JuliaMath/SpecialFunctions.jl/blob/master/src/gamma_inc.jl: ForwardDiff.derivative(x -> gamma_inc_inv(x, 0.1, 0.9), 0.1) Are there routines in HypergeometricFunctions.jl to make this calculation that support AD?

Thank you for your help,

MikaelSlevinsky commented 6 months ago

Hi @mleprovost, it looks like the inverse incomplete gamma function uses series reversion-like techniques (e.g. https://github.com/JuliaMath/SpecialFunctions.jl/blob/ff76c3ac50d50993d838c737ff7efeb987acec71/src/gamma_inc.jl#L744-L770) for small or large arguments. Unless there's a hypergeometric structure to those reverted series, then I'm not sure how the HypergeometricFunctions package could help.

mleprovost commented 6 months ago

Thank you for your help. Should I open a separate issue?