Repository navigation
Conversation
|
I'll be away for two weeks, but did create this PR to avoid overlap in work. Similar PR should be made for incomplete beta gradient roots (mentioned in PR text) |
Jenkins Console Log Machine informationDistributor ID: Ubuntu Description: Ubuntu 20.04.3 LTS Release: 20.04 Codename: focal CPU: Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 43 bits physical, 48 bits virtual CPU(s): 256 On-line CPU(s) list: 0-255 Thread(s) per core: 2 Core(s) per socket: 64 Socket(s): 2 NUMA node(s): 2 Vendor ID: AuthenticAMD CPU family: 23 Model: 49 Model name: AMD EPYC 7742 64-Core Processor Stepping: 0 Frequency boost: enabled CPU MHz: 1494.753 CPU max MHz: 3416.0681 CPU min MHz: 1500.0000 BogoMIPS: 4491.85 Virtualization: AMD-V L1d cache: 4 MiB L1i cache: 4 MiB L2 cache: 64 MiB L3 cache: 512 MiB NUMA node0 CPU(s): 0-63,128-191 NUMA node1 CPU(s): 64-127,192-255 Vulnerability Gather data sampling: Not affected Vulnerability Indirect target selection: Not affected Vulnerability Itlb multihit: Not affected Vulnerability L1tf: Not affected Vulnerability Mds: Not affected Vulnerability Meltdown: Not affected Vulnerability Mmio stale data: Not affected Vulnerability Old microcode: Not affected Vulnerability Reg file data sampling: Not affected Vulnerability Retbleed: Mitigation; untrained return thunk; SMT enabled with STIBP protection Vulnerability Spec rstack overflow: Mitigation; Safe RET Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines; IBPB conditional; STIBP always-on; RSB filling; PBRSB-eIBRS Not affected; BHI Not affected Vulnerability Srbds: Not affected Vulnerability Tsa: Not affected Vulnerability Tsx async abort: Not affected Vulnerability Vmscape: Mitigation; IBPB before exit to userspace Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba ibrs ibpb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 cqm rdt_a rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clzero irperf xsaveerptr rdpru wbnoinvd amd_ppin arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif v_spec_ctrl umip rdpid overflow_recov succor smca sev sev_es G++: g++ (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Clang: clang version 10.0.0-4ubuntu1 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin |
|
@avehtari A colleague of mine noticed instability in the gradient of Stan's
|
@mjcarter95 There was no plan before your comment! Thanks for testing and reporting, I'll investigate a possible fix |
Thank you, Aki. Claude helped me to identify that the issue arises because Stan computes the shape gradient as dQ/dα divided by exp(log Q), and once log Q falls below about −745, both terms underflow to zero and the result is 0/0 = NaN. Are there guidelines for how to submit an AI-assisted PR? If so, I'd be happy to supervise Claude in coming up with a fix and submitting a PR that piggybacks off this one. |
|
I'm already fixing this and what you mentioned is only part of the problem. It's just that running the accuracy, speed and unit tests takes some time and also checking which other functions can be improved, so please wait a few hours. |
|
@mjcarter95 Just an intermediate report that I'm making fixes for 17 other functions in addition to |
Brilliant, thank you for following up so quickly! |
igamma_der_a for the incomplete gamma gradient roots|
@mjcarter95 The Here's my version of the figure (with probably more accurate reference as when using the same color scale as you used, it is very boring) |
|
Re-ran the tests my end, all looks good! I am comparing against mpmath. Thank you for fixing this @avehtari
|
I guess you are using lower accuracy in mpmath, as you report worst relative error 4.1e-6 and I have worst relative error 3.4e-12 (I used 40 to 80 digits accuracy) |
I used precision 60 for all cells. |
…ther elements of poisson_lccdf for negative counts
|
@avehtari can you split this PR into smaller batches? There's a bit too much to review properly, and doesn't all need to land in the one PR |
OK. I have worried that I have too many PRs, but I can do more. |
|
@andrjohns how many PRs would you prefer? There are 5 logical parts and thus it would be possible to split this in 5 PRs
G2 and G3 are independent of each other; G4 and G5 are independent of each other. Instead of 5 PRs it would possible to combine G1 and G2 in one PR and/or G3, G4 and G5 to one PR Let me know what you prefer, and I'll do the split |
|
Fact check, part 1 of 2. Action: Not Ready for Human Review Commit checked: SummaryThank you for this PR and for the detailed write-up of the defects on develop. The PR replaces the incomplete gamma gradient roots with Eigen's I read the source and the tests at 22d781f and ran the PR's new and changed tests in a container without network access. The output of each run is in this gist.
Claims and EvidenceEach section below lists facts from the PR summary and discussion that an agent was able to confirm, partially confirm, or not confirm at all, or that a run of the code in the PR contradicted. Confirmed FactsFACT: The reverse-mode gamma_p derivatives at (3, 36) are about 1e-13, not zeroQuote:
STATUS: Confirmed
Evidence:The PR adds a test with mpmath references for the three overloads and the case of #2006. What I ran: FACT: The forward-mode gamma_q derivative in z was NaN at (200, 150)Quote:
STATUS: Confirmed
Evidence:The PR adds a test at the named point, with the closed-form reference. What I read: Develop's What I ran: FACT: The second derivative of gamma_lccdf in alpha was 0 with nested fvar, and NaN with fvarQuote:
STATUS: Confirmed
Evidence:The PR adds a mix test that runs the 13 tail rows in four autodiff types. What I read: Develop's What I ran: FACT: Repairs the gamma_lccdf shape-gradient failures reported by @mjcarter95Quote:
STATUS: ConfirmedThe reported failure is the shape gradient of Evidence:The PR adds a tail reference test of 13 rows in five autodiff types. What I ran: FACT: The probability-scale cdfs return NaN gradients where the cdf underflows to 0Quote:
STATUS: ConfirmedIn the reference test on develop, Evidence:The reference test has rows where the cdf is below 1e-300. What I read: Develop's What I ran: FACT: 199 of 324 test points are non-finite on develop, and none with the PRQuote:
STATUS: ConfirmedThe reference test has 324 rows, 18 functions at 18 points each. Evidence:The reference test asserts that each value and gradient is finite. What I ran: FACT: Partly addresses #56 for poisson_lccdfQuote:
STATUS: Confirmed
Evidence:The PR adds a test with the example of #56. What I ran: FACT: The existing unit suites and the 90 generated test/prob cdf programs passQuote:
STATUS: ConfirmedAt the head, 24 existing unit test files pass, with 67 tests. Evidence:The suites and the generator are in the repository, and the PR names them. What I ran: Partially Confirmed FactsFACT: The error near z = a grows with a, to 5.2e-6 at a = z = 1e4Quote:
STATUS: Partially ConfirmedThe accuracy test checks Evidence:The accuracy test covers a = z up to 500 with a bound of 1e-10. What I ran: Request: Could you add the script that measures the error near FACT: grad_reg_inc_gamma on develop is wrong near z = a, by 4.4e-02 to 7.2e-01Quote:
STATUS: Partially Confirmed
Evidence:The PR does not include the mpmath code behind the expected values of the test. What I ran: Request: Could you add the mpmath script that produced the reference values in FACT: grad_reg_inc_gamma on develop returns NaN for a above about 171.6Quote:
STATUS: Partially Confirmed
Evidence:The test covers the tgamma overflow above 171.6. What I read: Develop's What I ran: Request: At FACT: grad_reg_lower_inc_gamma on develop returns rounding noise for z well above aQuote:
STATUS: Partially Confirmed
Evidence:The PR does not include the mpmath code behind the expected values of the test. What I ran: Request: Could you add the mpmath script behind the reference values in FACT: The 1e-6 precision of develop's series branch gives errors of 9.2e-08 at a = 0.5 and 9.0e-05 at a = 8Quote:
STATUS: Partially ConfirmedOn develop the series branch stops when a term falls below Evidence:The stopping rule follows from develop's code. What I read: Develop's What I ran: Request: Could you add the points FACT: The root defects reach the distribution gradients, as in the five-row tableQuote:
STATUS: Partially ConfirmedThe new tests check the roots at the points of the table's rows. Evidence:The roots under the table are tested. What I read: On develop the shape partial calls What I ran: Request: Could you add the five rows of this table as a test of the distribution functions with FACT: Reverse mode, forward mode and hessian() now agree on the first derivativeQuote:
STATUS: Partially Confirmed
Evidence:Two tests cover gamma_cdf and gamma_lccdf. What I ran: Request: Could you add the script behind the autodiff-order spread row, covering the ten regions? FACT: Closes #2006Quote:
STATUS: Partially Confirmed#2006 has two examples at Evidence:The gamma_p example of #2006 has a test. What I ran: Request: Could you add the FACT: The forward-mode gamma_q on develop does not terminate for z above about 700Quote:
STATUS: Partially ConfirmedThe develop loop Evidence:The non-termination follows from develop's code, and the test covers z up to 650. What I read: In develop's What I ran: Request: Could you add the call at FACT: The gamma_lccdf shape derivative on develop is NaN or 0 where log Q < -708Quote:
STATUS: Partially Confirmed
Evidence:The tail reference test is in the PR and covers the three named points. What I ran: Request: Could you check the FACT: The other log-scale cdfs underflow: chi_square_lccdf(2000 | 20) is -inf on developQuote:
STATUS: Partially ConfirmedThe develop code of 11 of the 12 log-scale functions takes Evidence:The reference test covers the named point and the subnormal rows. What I read: Develop's What I ran: Request: Could you add the script or the commit, 8d376d5 or another, at which the reference test shows the gradient errors of 0.1 to 1? FACT: The density factor overflows, so the y gradient is NaN in the bulk at large shapeQuote:
STATUS: Partially ConfirmedIn the reference test on develop, the Evidence:The reference test covers the named medians. What I read: Develop's What I ran: Request: Could you check the count of 13? FACT: The worst errors at the 324 points are 2e-13 for values, 8e-13 for gradients, and 2.7e-10 for one partialQuote:
STATUS: Partially ConfirmedAt the head the reference test passes with a relative tolerance of 1e-12 for values and 1e-9 for gradients, so the errors are below those bounds. Evidence:The test checks the bounds 1e-12 and 1e-9. What I ran: Request: Could you add the mpmath script that produced FACT: gamma_lccdf in all autodiff modes: first derivatives within 5.6e-12, second within 1.9e-10Quote:
STATUS: Partially ConfirmedThe mix tail test checks first derivatives with a relative tolerance of 1e-11 and second derivatives with 1e-9, or 5e-8 when Evidence:The test checks bounds that are looser than the stated numbers. What I ran: Request: Could you add the mpmath script behind FACT: The cdf values change only where they were -inf or lost digitsQuote:
STATUS: Partially ConfirmedIn the base run, 66 of the 324 rows fail on the value. Evidence:The reference test covers 324 points. What I ran: Request: If the statement rests on more than the 324 points, could you add the code that compared the values? FACT: The new tests fail without the changesQuote:
STATUS: Partially ConfirmedI put the PR's test files on develop's headers, all at once, and ran them. Evidence:The tests are in the PR. What I ran: The four Request: Could you say in the text that the FACT: The true d/da Q(0.5, 1.0) is 0.38983726432851057, and the old constant was offQuote:
STATUS: Partially ConfirmedIn Evidence:The test is in the PR. What I ran: Request: Could you change "Three" to "Four"? FACT: gamma_lcdf(20000 | 800, 0.1) is now about -1e-204, so log1m_exp(lcdf) equals lccdfQuote:
STATUS: Partially Confirmed
Evidence:The PR changes the prim and rev tests at this point. What I ran: Request: Could you assert or print the lcdf value of -1e-204 at this point? FACT: The generated third-order chi_square case failed with the stale gamma_q copy and passes without itQuote:
STATUS: Partially Confirmed
Evidence:The passing half is shown by the generated tests. What I ran: Request: Is b172d61 the state that shows the 2.4e-03 failure? |
|
Fact check, part 2 of 2. Claims and Evidence (continued)Not Verified FactsFACT: The 1 800-point sweep of the rootsQuote:
STATUS: Not VerifiedThe PR does not include the code of this sweep, so I could not rerun it. Evidence:The PR does not include the script of the table or the mpmath reference for the sweep. Request: Could you add the script that draws the 1 800 points, computes the mpmath reference, and prints the worst relative error per region, for example under a FACT: The 49 990-point sweep: 6 379 non-finite on develop and none with the PRQuote:
STATUS: Not VerifiedThe PR does not include the code of this sweep. Evidence:The PR does not include the script of the sweep. Request: Could you add the script of the 49 990-point sweep, with the reference it compares against? FACT: The roots are 2.2x to 4.4x faster with double, and the upper root is 0.72x with varQuote:
STATUS: Not VerifiedThe PR does not include the benchmark behind this table, so I could not rerun it. Evidence:The PR does not include a benchmark file or script. Request: Could you add the benchmark, for example a google-benchmark file that times both roots with FACT: The 18 cdfs are 1.9x to 12x faster with varQuote:
STATUS: Not VerifiedThe PR does not include the benchmark behind this table, so I could not rerun it. Evidence:The PR does not include a benchmark file or script. Request: Could you add the benchmark code for the cdf table, with the points it maps to each function's arguments? FACT: The gamma_lccdf shape derivative alone is 1.6x to 34.9x fasterQuote:
STATUS: Not VerifiedThe PR does not include the benchmark behind this table, so I could not rerun it. Evidence:The PR does not include a benchmark file or script. Request: Could you add the benchmark code for this table, with the points of each region and the autodiff types? FACT: On the grid of the plot by @mjcarter95, the worst relative error is 3.4e-12Quote:
STATUS: Not VerifiedThe PR does not include the grid or the script that computes the 266 cells. Evidence:The grid and the mpmath reference are not included. Request: Could you add the script that computes the 266 cells and the reference, or link it? Description vs CodeEach section below lists facts from the PR summary and discussion that an agent was able to confirm, partially confirm, or not confirm at all, or that a run of the code in the PR contradicted. Confirmed FactsFACT: The roots call Eigen's igamma_der_a, which is vendoredQuote:
STATUS: ConfirmedAt the reviewed commit FACT: Two new headers supply the three Eigen helpers for autodiff typesQuote:
STATUS: Confirmed
FACT: The forward-mode gamma_q calls grad_reg_inc_gamma and evaluates d/dz in log spaceQuote:
STATUS: ConfirmedOn develop, both FACT: The old root's asymptotic branch reached the fwd gamma_q loopQuote:
STATUS: ConfirmedOn develop the asymptotic branch of FACT: The z / a > 10 short cut is removed from all three gamma_p overloadsQuote:
STATUS: ConfirmedOn develop, FACT: log_gamma_tail chooses the continued fraction or gamma_p and gets d/dz from the densityQuote:
STATUS: ConfirmedIn FACT: Each of the 18 functions keeps its argument checks, and the cdfs multiply by exp(sum) at the endQuote:
STATUS: ConfirmedAll 18 headers call FACT: An element with probability 1 now contributes 0 instead of ending the loopQuote:
STATUS: ConfirmedOn develop, FACT: OpenCL code does not call these functionsQuote:
STATUS: ConfirmedA search of FACT: log P and log Q are internal, with no new public functionQuote:
STATUS: Confirmed
FACT: log_gamma_q_dgamma is no longer called by the libraryQuote:
STATUS: ConfirmedOn develop the only caller is FACT: Other discrete cdfs still let one element decide the whole resultQuote:
STATUS: ConfirmedAt the reviewed commit, FACT: The existing lower-root test grid has no point of defect 3Quote:
STATUS: Confirmed
FACT: On develop, every point with z > 42 and a >= 12 goes to the Gautschi branch of the lower rootQuote:
STATUS: ConfirmedDevelop's FACT: The Poisson cdfs treat n = INT_MAX as an infinite count, as three other families doQuote:
STATUS: ConfirmedAt the reviewed commit Partially Confirmed FactsFACT: The root signatures are unchanged and the extra arguments are ignoredQuote:
STATUS: Partially ConfirmedThe signatures match develop. Request: Could you say that FACT: There are two behavior changes at the edges of the supportQuote:
STATUS: Partially ConfirmedThe list under this sentence has three items, and the code has all three changes (see the fact check on elements with probability 1). Request: Could you change "Two" to "Three"? FACT: On develop, z >= a && z >= 8 selects a fixed asymptotic expansionQuote:
STATUS: Partially ConfirmedDevelop's Request: The loop adds 9 terms. FACT: The mix tests run expect_ad for all 18 functions at two points each, one on each branchQuote:
STATUS: Partially ConfirmedThe two mix files call Request: Could you move one point of the Not Verified FactsFACT: The poisson_log cdfs are now short wrappers of the log-space functionQuote:
STATUS: Not VerifiedThe reviewed commit does not have Request: Could you reword this to say that they can be written as short wrappers, and that #3476 adds them? Not Checked
|
# Conflicts: # stan/math/prim/fun/grad_reg_inc_gamma.hpp # stan/math/prim/prob/gamma_cdf.hpp
|
I updated the PR text, added tests and published the scripts behind the numbers. The PR had a conflict with ScriptsThe scripts are in https://github.com/avehtari/stan-pr-checks/tree/27838b5236/pr3408. The README gives one command per claim, the expected output and the commit I measured it on. The directory has:
I ran the accuracy measurements again on Numbers that changed in the rerun
Partially confirmed and not verified facts
Description vs code
Other changesI renamed On the merged head with the new tests (Triton, g++ 14.3), these pass: 35 unit suites, the 123 generated @SteveBronder, let me know if you want the responses for @stevieb-bot to have some specific structure |



EDIT: I updated this PR text on 2026-10-21 and on 2026-10-28, after the fact check by @stevieb-bot. A comment lists the changes.
This PR was Claude assisted
Issues
gamma_qwithfvarandgamma_pwithvarreturn wrong derivatives (Part 2).poisson_lccdfno longer drops the other elements of a vector when one count is negative. The same defect remains in other discrete cdfs; see "Not included".tgamma_lowerandtgamma(a,z)functions #2769:log Pandlog Qare now computed in log space inside the library, but no public function is added.gamma_lccdfshape-gradient failures that @mjcarter95 reported in this thread (Part 3).Summary
This PR repairs the derivatives of the regularized incomplete gamma functions and the 18 distribution functions that are built on them: the
cdf,lcdfandlccdfofchi_square,gamma,inv_chi_square,inv_gamma,scaled_inv_chi_squareandpoisson. It has three parts.grad_reg_inc_gammaandgrad_reg_lower_inc_gamma(the derivatives ofQ(a, z)andP(a, z)with respect toa) are replaced byEigen::numext::igamma_der_a, which is already vendored inlib/eigen_5.0.1. The old roots were wrong by tens of percent, or NaN, in regions that ordinary models reach.gamma_qhad its own copy of the old series, which did not terminate forzabove about 700. The copy is removed. The reverse-modegamma_preturned zero derivatives forz / a > 10; it now returns the derivatives. Closes gamma_p and gamma_q gradient problems #2006.log Porlog Qand their derivatives in log space, through one internal function. Before, they returned-infvalues or NaN gradients where the probability underflows, and 14 of them returned NaN or infinite gradients in the bulk once the shape is above about 150.The values of the distributions change only where they were
-infor lost digits; elsewhere only the gradients change.developz = agamma_q,z ≳ 700doublerootsvar)Part 1: the gradient roots
The defects on
developAll numbers in this part were measured against an mpmath reference at 80 digits, built through two independent routes that agree to at least 13 digits, with the real
developheaders at5252d51d47compiled and run.grad_reg_inc_gammatakes an invalid branch whenzis neara. The testz >= a && z >= 8selects a fixed 9-term asymptotic expansion (DLMF 8.11.2), which needsz >> a. In the whole banda <= z < 2athe terms do not decay. The relative error atz = ais 4.4e-02 ata = 20, 5.8e-01 ata = 100and 7.2e-01 ata = 171.grad_reg_inc_gammareturns NaN foraabove about 171.6. Every call site passesg = tgamma(a), which isinfthere, so the correction termexp(-z + (a-1) log z) * S / gisinf / inf. In the series branch,exp(a * log z)overflows whena log z > 709. The result is then-infor+inf. At(a, z) = (170, 100)it is-inf; the true value is 6.6e-11.grad_reg_lower_inc_gammaloses all accuracy forzwell abovea. Its delegation test sends every point withz > 42anda >= 12to the Gautschi branch, whereemz * (log_z * sum_a - sum_b)is a difference of two large, nearly equal sums. It returns about 1e-14 of rounding noise with arbitrary sign: at(50, 150)the reference is -8.29e-22 anddevelopreturns -4.70e-15; at(50, 500)the reference is -5.34e-148 anddevelopreturns +1.47e-14.precisionof 1e-6 sets the error of the plain series branch: 9.2e-08 at(a, z) = (0.5, 0.25)and 9.0e-05 at(8, 4), with an exactg. It is the smallest of the four problems.The defects reach the distribution functions. Measured with
varagainst the same reference (the cdf values are correct; only the gradients are wrong):developchi_square_lccdfchi_square_lccdfchi_square_lcdfgamma_cdfinv_gamma_lccdfThe gradients are NaN for
chi_square_lccdfandchi_square_lcdfat nu = 360,gamma_cdfandinv_gamma_lccdfat alpha = 180, andscaled_inv_chi_square_lcdfat nu = 360. With this PR, every row of the table agrees with the reference to all 12 printed digits. The rows of the table and the NaN points are in the reference test (rows markedroot_table).The change
Eigen::internal::igamma_generic_impldifferentiates the Cephes power series and continued fraction term by term, stops at machine epsilon, and branches onx > 1 && x > a, which is the well-conditioned split. It is generic in the scalar type except for three helpers,lgamma_impl,digamma_implandcephes_helper. This PR supplies those three for Stan's autodiff scalars in two new headers,stan/math/fwd/fun/Eigen_SpecialFunctions.hppandstan/math/rev/fun/Eigen_SpecialFunctions.hpp. Both roots then become thin wrappers, and the hand-written series, the asymptotic branch and the Gautschi branch are deleted.One algorithm now serves every scalar type and every autodiff order, so reverse mode, forward mode,
fvar<fvar<double>>,fvar<var>andhessian()agree. I tried thedoublepath alone first and rejected it:hessian()stayed on the old code, andgradient()andhessian()then returned first derivatives that differed by 5.8e-01 atalpha = 100and by NaN atalpha = 180.The signatures do not change. The roots ignore
precisionandmax_steps, and checkganddigonly for NaN. If one of them is NaN, the result is NaN, as before.Accuracy
A sweep of 1 800 points in nine regions chosen by the branch structure; worst relative error per region (200 points each), against the reference
ref_mpmath.dQda:developgrad_reg_inc_gammadevelopgrad_reg_lower_inc_gammaIn the two rows with a value in parentheses, the error comes from the reference. The value in parentheses is the worst error of this PR at the 200 points of the row against mpmath's numerical derivative
mp.diff.A second sweep compared 49 990 points with
aandzboth in [1e-3, 1e4], dense onz ≈ a, thez = 8seam,a = 171.62anda log z = 709.developreturns NaN or inf at 7 379 of them forgrad_reg_inc_gamma; this PR is finite at every one, with median relative errors between 1e-16 and 1e-14 in every region. Outside the band nearz = a, the worst error against the reference is 3e-11. At the worst point of each region, this PR is within 1e-13 ofmp.diff, so there the 3e-11 is the error of the reference.One known limit: near
z = athe error grows witha, to 2.9e-7 fora < 10 000and 5.2e-6 ata = z = 1e4, where Eigen's Cephes recurrence loses digits near the mode. Three independent references (numerical differentiation, a quadrature and a series) agree to 16 digits at the worst point, so the error comes from the algorithm. Fora < 1 000the worst error against the reference is 3e-11; at that point this PR is within 6e-14 ofmp.diff. In the far upper tail ata ≳ 1e4the reference is certified to 11 digits only.Speed
Function level, net ns per call, Xeon E5-2680 v3,
performancegovernor, two rounds (spread under 3 %), Triton job 20356756, onain [0.5, 60] andzin [0.1, 40], wheredevelopterminates and is finite:developgrad_reg_inc_gamma, double, withtgammaanddigammagrad_reg_inc_gamma, fvargrad_reg_inc_gamma, fvar<fvar>grad_reg_lower_inc_gamma, doublegrad_reg_lower_inc_gamma, fvargrad_reg_lower_inc_gamma, fvar<fvar>grad_reg_inc_gamma, var, value + gradgrad_reg_lower_inc_gamma, var, value + gradThe
varinstantiation of the upper root is slower: Eigen's series and continued fraction take more operations than the 9-term asymptotic branch where that branch runs, and withvarevery operation is a tape node. Reverse-mode code in the library calls the roots withdoublevalues; only thefvar<var>path of the cdfs passesvar.Part 2: the derivatives of
gamma_qandgamma_p#2006 shows both problems at
(a, z) = (3, 36):gamma_qwithfvar<double>returns -6.7e+12 for a derivative of -11.5, andgamma_pwithvarreturns zero derivatives.The reverse-mode
gamma_pinrev/fun/gamma_p.hppreturned without derivatives whenz / a > 10, with the comment thatgamma_pis flat to machine precision there. Its derivatives there are small but not zero (about 1e-13 at(3, 36)), andlog1m(gamma_p(...)), the case of #2006, divides them by1 - P, so its whole gradient was lost. With the new roots the derivatives are accurate in that region, and this PR removes the short-cut from all three overloads.stan/math/fwd/fun/gamma_q.hppondevelopheld its own copy of the series ingrad_reg_inc_gamma, withwhile (fabs(delta) > 1e-6)and no iteration cap. Forzabove about 700 the alternating terms(-z)^k / k!overflow before they decay,deltastaysinf, and the loop never exits; a call ata = 0.168, z = 4 748did not return in 15 minutes. The asymptotic branch of the old root callsgamma_qwithfvararguments, sograd_reg_inc_gammareached the loop too. The existing mix test forgamma_qcarried a commented-out case with the note "this still fails forward mode", which is the same defect. This PR enables it again.This PR replaces the copy with a call to
grad_reg_inc_gamma, asfwd/fun/gamma_p.hppalready callsgrad_reg_lower_inc_gamma. It also evaluates the derivative with respect toz,-z^(a-1) e^(-z) / Γ(a), in log space; it was NaN at(a, z) = (200, 150)becausepowandtgammaoverflow.The copy had been hiding the defect. The generated third-order case in
test/prob/chi_squarecompares the analytic partials against autodiff throughlog(gamma_q(nu * 0.5, y * 0.5)). Both routes used the same inaccurate series, so the errors canceled. With the accurate root and the stale copy (commit b172d61), the case fails by 2.4e-03; with the copy removed (commit af3205c), it passes.Part 3: the cdf functions in log space
What failed
gamma_lccdfcomputedlog Qin log space, but the shape derivative asdQ/dalpha / exp(log Q). That is NaN wherelog Q < -708, for example at(alpha, y) = (10, 1000)and(200, 1600). At(150, 1200),developreturns 2; the true value is 2.084. With only the roots of Part 1 (commit b172d61), it returns 0. @mjcarter95 shows this region in the plot in this thread. The same plot shows thatgamma_lccdfinherits defects 1 and 2 of Part 1 foralpha + 1 < y <= 2 alpha. Withfvar<fvar<double>>andfvar<fvar<var>>, the second derivative with respect toalphawas exactly 0 at every point: the code seeded a copy ofalphaand kept only a plain number as the partial. Withfvar<var>, ashessian()uses it, the second derivative was NaN at the tested pointsalpha = 700and1e4.The other 17 functions had three defects, measured at 18 points
(a, z)of the incomplete gamma function against mpmath, with the roots of Part 1 already in place (commit 8d376d5):log(gamma_p(...))orlog(gamma_q(...)). Where the probability is below 1e-308, the value is-infand the gradient is NaN. Where it is subnormal, the value has relative error 1.5e-4, and the gradient is NaN or has relative error 0.1 to 1. Example:chi_square_lccdf(2000 | 20)is-inf; the true value is -950.6.y, scale andlambdapartials overflows. 14 functions wrote it asexp(-z) pow(z, a - 1) / tgamma(a)(poisson_cdfasexp(-lambda) pow(lambda, n) / tgamma(n + 1)).tgammaoverflows above 171.6 andpowonce(a - 1) log z > 709, so the partial is NaN or infinite also in the bulk, for example theygradient ofchi_square_cdf,chi_square_lccdfandgamma_cdfat the median fornu = 400.The change
One internal function,
log_gamma_tail(a, z)inlog_gamma_q_dgamma.hpp, returnslog Porlog Qwith its derivatives with respect toaandz, all /in log space and generic in the scalar type:z > a + 1, the continued fraction giveslog Q, and with the derivative carried through the recurrence alsod/da log Q. It stops only when the value and the derivative have converged.log P = log1m_exp(log Q).gamma_pgivesP, andgrad_reg_lower_inc_gammaits derivative. WherePis belowDBL_MIN / EPSILON, a power series in log space giveslog Pand its derivative; it stops on a bound of the remaining terms.d/dzis the density divided by the probability,exp(log density - log prob).Each of the 18 functions keeps its argument checks and applies its own chain rule. The probability-scale
cdfsums the logs and partials and multiplies byexp(sum)at the end, so it has neither the density overflow nor the 0/0. A derivative that is differentiated again is computed in the autodiff type, so nested forward mode andhessian()get correct second derivatives.Three behavior changes at the edges of the support:
gamma_lcdfandchi_square_lcdfaty = inf, and thelccdfofchi_square,inv_gamma,inv_chi_squareandscaled_inv_chi_squareaty = 0, returned 0 for the whole vector when one element had probability 1, so the other elements were lost. Such an element now contributes 0.poisson_lccdfreturned 0 for the whole vector when one count was negative (Incorrect behaviour of CCDFs when there is a negative random variable #56). Such an element has probability 1 and now contributes 0.n = INT_MAXas an infinite count, asneg_binomial,beta_neg_binomialandyule_simonalready do. Before,poisson_lccdfreturned-infthere only becausegamma_punderflowed.Accuracy
At the 324 test points (the 18 functions at 18 points), no value or gradient is non-finite. The worst relative error is 2.4e-13 for values and 8e-13 for gradients, with one exception: the
nupartial ofscaled_inv_chi_squarenear the mode has a relative error up to 2.7e-10. Thereaandzboth grow withnu, and the two terms of the chain rule nearly cancel; the absolute error is of rounding size. Forgamma_lccdfin all autodiff modes, first derivatives are within 5.6e-12 and second derivatives within 1.9e-10 (7e-9 neary = alpha + 1atalpha = 5). On the grid of the plot by @mjcarter95, all 266 cells are finite and the worst relative error of the shape derivative is 3.4e-12.Speed
Value plus all gradients with
vararguments, ns per call, AMD EPYC 9965, g++ 11.5,-O3, medians of 4 alternating runs, Roihu job 2430495. Points witha = 5anda = 50andzfrom0.2 ato3 a, mapped to each function's arguments:develop, a = 5develop, a = 50gamma_cdfgamma_lcdfgamma_lccdfchi_square_cdfchi_square_lccdfinv_gamma_cdfinv_gamma_lcdfscaled_inv_chi_square_lcdfpoisson_lcdfMost of the gain comes from no longer calling
tgamma,powand a root withtgammaanddigammaper element.gamma_lccdfalone, shape derivative only, withalphafrom 5 to 1000 (ns per call):developvary > alpha + 1, normalQvarQunderflowsvary <= alpha + 1fvar<double>y > alpha + 1, normalQfvar<var>y > alpha + 1, normalQfvar<var>Qunderflowsfvar<var>y <= alpha + 1Where
Qunderflows,developreturns NaN or 0, so its time there is no baseline. One of the four runs of this PR was about 35 % slower in three rows of the cdf table; the medians do not depend on it.Testing
New tests, all with fixed references from mpmath;
expect_adcompares against finite differences and cannot see errors of this size:test/unit/math/prim/fun/grad_reg_inc_gamma_accuracy_test.cpp: the roots at the points of Part 1, including the series-branch points of defects 2 and 4, references at 80 digits.ProbInternalMath.gradRegIncGamma_gradient_matches_hessian: the first derivative ofgamma_cdfis the same from reverse mode and fromhessian().gammaQ_fwd_shape_derivative_large_zandgammaQ_fwd_z_derivative_large_aintest/unit/math/mix/fun/gamma_q_test.cpp: the forward-modegamma_qderivatives. A test that hangs is not usable in CI, so the first one uses(a, z) = (1.5, 300), where the old series terminates but returns1.2e+112for a derivative of5.7e-129.gammaQ_fwd_log_upper_tail_compositeis thefvar<double>example of gamma_p and gamma_q gradient problems #2006,log(gamma_q(alpha, beta * y))aty = 12, alpha = 3, beta = 3with all tangents 1.test/unit/math/rev/prob/incomplete_gamma_cdfs_reference.hppwithincomplete_gamma_cdfs_reference_test.cpp: 334 rows. They hold the value and gradient of the 18 functions at 18 points, and at the 10 points of the Part 1 table, at 60 digits by two routes (the incomplete gamma function with numerical differentiation, and a quadrature of the scaled density with the chain rule), which agree to 1e-25. Value to 1e-12, gradient to 1e-9 relative.test/unit/math/rev/prob/gamma_lccdf_tail_reference.hpp: 13 rows forgamma_lccdfwith second derivatives, used by avarcase inrev/prob/gamma_lccdf_test.cppand byfvar<double>,fvar<var>,fvar<fvar<double>>andfvar<fvar<var>>cases inmix/prob/gamma_lccdf_test.cpp.mix/prob/incomplete_gamma_cdfs_test.cppandmix/prob/inv_incomplete_gamma_cdfs_test.cpp:expect_adfor all 18 functions at two points each, one on each branch.gammaP_rev_far_upper_tailintest/unit/math/mix/fun/gamma_p_test.cpp: the reverse-mode derivatives ofgamma_p(3, 36)in all three overloads, and the gradient oflog1m(gamma_p(alpha, beta * y))from gamma_p and gamma_q gradient problems #2006.lccdf_negative_count_contributes_zerointest/unit/math/prim/prob/poisson_ccdf_log_test.cpp: the example of Incorrect behaviour of CCDFs when there is a negative random variable #56.Changed tests:
test/unit/math/mix/fun/grad_reg_inc_gamma_test.cppexpected 0.38984156 ford/da Q(0.5, 1.0); the true value is 0.38983726432851057. The old constants combined the roundedg = 1.77245that the test passes in and the 1e-6 series truncation. The two second- and third-derivative cases of that file were not changed and still pass.lccdf_log1m_exp_lcdf_rounds_to_inf(prim and rev) asserted thatgamma_lcdf(20000 | 800, 0.1)rounds to exactly 0.gamma_lcdfnow returns its true value, -1.48e-205, so the case checks this value and thatlog1m_exp(gamma_lcdf)equalsgamma_lccdfthere, and is renamed tolccdf_far_tail_matches_log1m_exp_lcdf.The new tests fail without the changes. I installed the
developversions of the 25 changed headers that exist ondevelop, all at once, and kept the new tests. Then I restored the headers of this PR; their checksums equal those before. 23 of the 24 new test cases fail with the old headers. The exception ismathMixScalFun_poisson_cdfs, anexpect_adcase that passes ondeveloptoo. The reference test fails with 588 expectations, in all 18 functions. The four changed constants ofgrad_reg_inc_gamma_test.cppfail too.The existing unit suites of the roots,
gamma_p,gamma_qand all 18 functions pass, and so do the 90 generatedtest/probcdf programs of the six families. The existinggrad_reg_lower_inc_gamma_testchecks 9 600 values from Mathematica with a 1e-10 absolute tolerance; Eigen satisfies all of them, but that grid (aup to 19.75,zup to 29.75) contains no point of defect 3. I tested the merge of this PR withdevelopat510d263078(Triton, g++ 14.3). I also tested the earlier commits with g++ 11.5.Reproduction
The scripts behind every number in this text are in https://github.com/avehtari/stan-pr-checks/tree/27838b5236/pr3408. They cover the mpmath generators of the test references (two routes each), the two sweeps of the roots, the survey of the 18 cdfs, the grid of the plot by @mjcarter95, the forward-mode hang, the swap test and the speed benchmarks. The README in that directory gives one command per claim, the expected output and the commit that I measured it on. On 2026-10-28 I ran the accuracy measurements again on
develop(5252d51d47) and on the merged head. I did not run the speed benchmarks again.Dependency on Eigen
The two new headers specialize
lgamma_impl,digamma_implandcephes_helperinsideEigen::internal. Stan Math already specializes inside that namespace, instan/math/rev/core/Eigen_NumTraits.hpp. An Eigen upgrade that renames those three helpers breaks the build; that failure is immediate, and the alternative was to copy about 300 lines of the Cephes algorithm into Stan Math. The headers belong to thefunlayer, notcore, because they needdigammaandlgamma; incorethey break the include order of every mix target.Not included
OpenCL is unaffected: no OpenCL code calls these functions, and the six families have OpenCL implementations only for their
lpdforlpmf.The incomplete beta gradient roots have their own defects; they are in #3409.
#2769 asks for log-scale incomplete gamma functions, because
gamma_lcdflost resolution at the boundaries. This PR computeslog Pandlog Qin log space inside the library (internal::log_gamma_tail), which meets that motivation for the cdfs, but it adds no public function such aslog_gamma_porlog_gamma_q.Other discrete cdfs have the defect of #56: one element with probability 1 decides the result for the whole vector.
bernoulli_lccdf,binomial_lccdf,beta_binomial_lccdf,neg_binomial_lccdf(and soneg_binomial_2_lccdf),beta_neg_binomial_lccdfandyule_simon_lccdfreturn 0 when one count is below the support;neg_binomial_lcdf,beta_neg_binomial_lcdfandyule_simon_lcdfreturn 0, andneg_binomial_cdf,neg_binomial_2_cdf,beta_neg_binomial_cdfandyule_simon_cdfreturn 1, when one count isINT_MAX. Most of these files are changed by other open PRs, so I'll make the PR for this after those other PRs have been merged.The missing
poisson_log_cdf,poisson_log_lcdfandpoisson_log_lccdf(#90, #2657) can be written as short wrappers of the same log-space function,log Q(n + 1, exp(eta)); #3476 adds them.g,dig,precisionandmax_stepsof the roots are now unused. Removing them changes the remaining call sites (gamma_qinrevandfwd, andlog_gamma_q_dgamma) and the public signature, so it is left for a separate PR.log_gamma_q_dgammaitself is no longer called by the library and is kept for compatibility.Release notes
Use Eigen's
igamma_der_afor the incomplete gamma gradient roots, and compute the cdf, lcdf and lccdf of chi_square, gamma, inv_chi_square, inv_gamma, scaled_inv_chi_square and poisson in log space, so that their values and gradients stay finite in the tails and for large shapes.Checklist
Copyright holder: Aalto University
The copyright holder is typically you or your assignee, such as a university or company. By submitting this pull request, the copyright holder is agreeing to the license the submitted work under the following licenses:
- Code: BSD 3-clause (https://opensource.org/licenses/BSD-3-Clause)
- Documentation: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
the basic tests are passing
./runTests.py test/unit)make test-headers)make test-math-dependencies)make doxygen)make cpplint)the code is written in idiomatic C++ and changes are documented in the doxygen
the new changes are tested