Repository navigation
Conversation
…gamma_inc and loggamma(a, x) Add the public function gamma_inc_dx(a, x), the x-derivative of the regularized incomplete gamma functions, which avoids overflow and underflow of intermediate results for large arguments. Use it in the ChainRules rule of gamma_inc, and compute the derivative of loggamma(a, x) as -1/(x * expintx(1 - a, x)) without cancellation, sharing expintx with the primal. Promote the arguments of loggamma(a, x) with promotereal so that integer a does not widen Float32 x. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #568 +/- ##
==========================================
+ Coverage 94.61% 94.66% +0.05%
==========================================
Files 14 14
Lines 3025 3094 +69
==========================================
+ Hits 2862 2929 +67
- Misses 163 165 +2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Compute the a-derivative f * (log(x) - digamma(a)) without cancellation for large a by evaluating log(x) - digamma(a) as log(x/a) plus the asymptotic expansion of log(a) - digamma(a), which is factored out of digamma. At the poles of digamma(a), use the reflection formula, which is smooth in a so that nested derivatives are correct. Compute the x-derivative as f * (a - 1 - x) / x unless f underflows, and compute both derivatives in Float64 for Float16 and Float32 arguments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the asymptotic expansion of `log(a) - digamma(a)` for all real types, with a precision-dependent threshold, so that the derivatives of `gamma_inc_dx` with respect to `a` are accurate for, e.g., ForwardDiff duals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`exp((a - 1) * log(x) - x)` overflows the exponent range of MPFR on Windows for `a = x = 1e10`, which made the reference NaN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Julia < 1.10.9 fails to load on glibc >= 2.41 (JuliaLang/julia#57250). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
devmotion
marked this pull request as ready for review
November 4, 2026 15:29
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds
gamma_inc_dx(a, x), a public (not exported) function for the derivative of the regularized incomplete gamma functions with respect tox:For
a > 0it is the density of the gamma distribution with shapeaand unit scale.The PR also adds a ChainRules rule for
gamma_inc_dxwith derivatives in both arguments, and usesgamma_inc_dxto fix the ChainRules rules ofgamma_incandloggamma(a, x).Motivation
The naive formula
exp(-x) * x^(a - 1) / gamma(a)returnsNaNor0once an intermediate result over- or underflows, e.g.a = x = 150, even though the result is perfectly representable. The log-space form avoids this but suffers from cancellation for largea: its relative error is 7e-10 ata = 1e6and 1e-5 ata = 1e10in Float64.This came up in JuliaDiff/ForwardDiff.jl#862. ForwardDiff will call
gamma_inc_dxwhen it is defined.StatsFuns can reuse it too:
StatsFuns.gammapdfis computed asexp(gammalogpdf(...)), the log form above.gamma_inc_dx(k, x / θ) / θwould be accurate for large shapes.Since
gammapdfis commonly differentiated with respect to the shape, e.g. to fitkby maximum likelihood,gamma_inc_dxneeds an accurate derivative inaas well as inx.gamma_inc_dxx < 708,-170 < a < 171,|(a - 1) * log2(x)| < 1022), not by computing and discarding values.rgammax(a, x) / x(fora ≥ -0.5), which is accurate for largeaandx, or the log form for very negativea.x = 0,x = Inf, non-positive integera, andNaNfollow the limits of the formula.Accuracy against 512-bit references:
Timings:
gamma_inc_dxChainRules
gamma_inc: the rule usesgamma_inc_dx.gamma_inc_dx(new rule): this gives ChainRules-based AD accurate second derivatives ofgamma_incinx, and makes the new function differentiable in both arguments. The derivatives are computed by two internal helpers that ForwardDiff can share:a - 1 - xis exact near the peak, where(a - 1)/x - 1would cancel. Only whenfunderflows does it switch togamma_inc_dx(a - 1, x) - f, which is also exact atx = 0andx = ∞.a ≥ 10the factor is computed aslog1p((x - a)/a) + (log a − ψ(a)), with the asymptotic expansion oflog a − ψ(a). This avoids the cancellation oflog(x) - digamma(a), which loses 6e6 eps ata = x = 1e6and 8e10 eps ata = x = 1e10; the new form is within 1 eps there.a = 0, −1, −2, …, wheref = 0): the reflection formulaψ(a) = ψ(1 − a) − π cot(πa)gives a form that is smooth ina, so nested derivatives are correct there too.a(150, 1e6, 1e10), near the peak, and at the poles.gamma_inc_dxitself.loggamma(a, x):-exp(-(x + Ω)) * x^(a - 1)cancels. It has a relative error of 6e-7 at (2, 1e10) and 5e-11 at (30, 1e6).a*log(x) + log(expintx(1 - a, x)) - x, the derivative is-1 / (x * expintx(1 - a, x))with no cancellation. Its relative error is ≤ 4 eps in the tested cases.frule/rruleare now hand-written so that the primal and the derivative shareexpintx.loggamma(a, x)primal is unchanged and no slower.Other changes
loggamma(a, x)now promotes withmap(float, promotereal(a, x)).loggamma(2, 3f0)staysFloat32.loggamma(a::Integer, x)method redundant, so it is removed.xusesisinteger(a) && isodd(a).The asymptotic expansion at the end of
digammais factored out into_log_minus_digamma_asymptotic, so that ∂f/∂a can reuse it. The final step is now evaluated aslog(z) - muladd(t², P(t²), t/2). This changes about 25% of results forz ≥ 8in the last bit, all toward the correctly rounded value. Over 200 000 points, the maximum error drops from 1.59 to 1.32 ulps.The version is bumped to 2.10.0.
Notes
#550 touches the same
loggamma(a, x)code (removing theIntegermethod and the negative-xbranch). The two PRs will conflict and should be coordinated.ForwardDiff's Dual rules will call the internal helpers
_gamma_inc_dx_dxand_gamma_inc_dx_da, behind anisdefinedcheck. Nested Duals pass a Dualfto_gamma_inc_dx_dx, which callsfloatmin(f), so that relies on JuliaDiff/ForwardDiff.jl#855.🤖 Generated with Claude Code