Repository navigation
Conversation
…sed 3F2 gradients
Open
4 tasks done
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.
Fixes #2508.
This PR is based on #3434 and must be merged after it.
This PR improves stability of
beta_binomial_cdf,beta_binomial_lcdfandbeta_binomial_lccdfThis was assisted by Claude
Summary
beta_binomial_cdf,beta_binomial_lcdfandbeta_binomial_lccdfreturnNaNor throw at ordinary arguments. The comment by @wds15 in #2508 givesbeta_binomial_lcdf(3 | 10, 298.30333970, 146.75306521):developand #3434 returnNaN, and the exact value is-3.9333500266686489.beta_binomial_lcdf(8 | 10, 3, 1)throws; an existing test expected this throw, with a FIXME that the point should be defined.The three functions evaluate
F = 3F2({1, mu, 1 - (N - n)}; {n + 2, 1 - nu}; 1)withmu = alpha + n + 1andnu = beta + N - n - 1, and the derivatives ofFwithgrad_F32. Because1 - (N - n)is a non-positive integer,Fis a polynomial withN - nterms. #3434 adds the mirrored series for thelcdf, which is a polynomial of the same kind.Most of the code changes are in
prim/fun/hypergeometric_3F2_tail_bound.hpp(new)prim/fun/grad_F32.hppprim/fun/hypergeometric_3F2.hppprim/err/check_3F2_converges.hppIn addition there are 2-6 lines changed per distribution file (except in beta_binomial_cdf also removing 90+ lines). Tests add 471 lines.
1. Four defects at the end of a terminating series.
hypergeometric_3F2_infsum(used atz = 1whensum(b) < sum(a)) did not stop when a numerator parameter reached zero. It set the sign of the term to 0 and continued. The term magnitudes then overflowed, and0 * infgaveNaN; or they did not overflow, and the loop ran up tomax_steps = 1e5steps. This is the case of beta_binomial_lpmf is unstable for large shape arguments #2508:3F2({1, 302.30333970, -6}; {5, -151.75306521}; 1), exact17.629673941225640. The loop now returns the sum when a numerator parameter is zero.z = 1withsum(b) == sum(a),hypergeometric_3F2called Boost'shypergeometric_pFq, which throws there. In the cdfs this isalpha + beta = 1. These cases now also usehypergeometric_3F2_infsum.check_3F2_convergestreated a denominator parameterb = -mas a pole when the polynomial ends at the termk = m.(b)_kis zero only fork > m, so the polynomial is defined. In the cdfs this isbeta = 1. For example,3F2({1, 12, -1}; {10, -1}; 1)is1 + 1.2 = 2.2;developthrew. The Mathematica gradient for this case in the comment ofgrad_F32_test.cppis already finite.grad_F32computed the term ratio0 / 0at the step where a numerator and a denominator parameter both reach zero. It now returns before the ratio when a numerator parameter is zero.2. Smaller defects of the same code.
check_3F2_convergestook the end of a polynomial from the largest non-positive integer numerator instead of the smallest, so it threw for some defined polynomials (3F2({-1, -5, 1}; {-2, 1}; z)); and for a series that does not terminate it counted onlyb = 0as a pole, notb = -1, -2, ....hypergeometric_3F2_infsumreturned its partial sum without an error when the steps ran out (the checkk == max_stepsafter the loopwhile (k <= max_steps ...)was never true), and it omitted the sign ofzfrom every term after the first (no caller in Stan Math usesz < 0).3. A stop that cannot cut the series.
hypergeometric_3F2_infsumandgrad_F32stopped at a term below1e-6in absolute value. This cut the polynomial before its end:Fhad relative errors up to1e-6and its derivatives up to3e-5. A plain relative stop is not safe either. In3F2({1e-23, 300, -100}; {1, -150.5}; 1)all terms are positive, the term after 1 is2e-21, and the sum is5.8e36; both stops return 1, andgrad_F32with only the derivatives ina2andb2requested stops in the same way. A terminating series now stops only when the current term and a bound of the sum of all remaining terms are below1e-17of the partial sum (forgrad_F32: also the bounds of every requested gradient). The bound is in a new internal header: the term ratio is a product of three factors|a_i + j| / |d_l + j|; where neither parameter changes sign, such a factor is monotone inj, so its maximum over the remaining terms is at an end of the range, and geometric sums then bound the tail. It is computed only at steps where the current term is already negligible. For an infinite seriesgrad_F32keeps itsprecisionargument; the documentation says so.4. The
lcdfandlccdf. They askgrad_F32for the two gradients they use (dF/da2anddF/db2) instead of all six, which provides significant speed-up. #3434'slcdfkeptlog(1 - C)foralpha = 1, because its mirrored series then hasb2 = a3and threw by defect 3. With the repair, thelcdfuses the mirror for everyalpha.5. The
cdf.beta_binomial_cdfmultiplied the values1 - C. Where the cdf is small this keeps no digits, and below about1e-16it rounds to 0, so the gradient (which divides by it) wasNaN. It now returnsexp(beta_binomial_lcdf(...)), after its own argument checks; the file shrinks from 159 to 66 lines.Over a grid of 3801 points per function (
Nfrom 1 to 117, alln, shapes from 0.1 to 1e4), values and both shape gradients, against mpmath (sums of the pmf). Each cell: throws /NaNorinf/ worst absolute error of the value or a shape gradient over the finite points.beta_binomial_cdfbeta_binomial_lcdfbeta_binomial_lccdfNo point that was finite on #3434 is less accurate with this PR.
Speed. Time per call with
varshapes (value and both gradients), atNin {10, 100, 1000, 10000},nin {0.1, 0.5, 0.9}N, and 5 shape pairs from(0.5, 2)to(1000, 3000)(180 points; Xeon E5-2680 v4; two interleaved rounds). At 95 of the 180 points #3434 runs the series past its end (defect 1), up to1e5steps: there a call takes 7.1 ms to 15.5 ms, and with this PR 0.5 µs to 1.6 ms. At the other 85 points, geometric mean of the ratio to #3434 [min, max]:Per change (geometric means per
N): the termination repair costs 2 % to 7 % fromN = 100; the stop with the tail bound costs 3 % atN = 10and 16 % to 25 % fromN = 100(most calls already summed nearly the whole polynomial; the largest increases, up to 3.4 times, are short series at shapes(1000, 3000)); asking for two gradients saves 14 % to 31 % in thelcdfand 24 % to 43 % in thelccdf; computing thecdffrom thelcdfsaves 21 % to 56 % in thecdf. Binaries that differ only in an unchanged function differ by up to about 6 % (code layout). The slowest point relative to #3434 is 1.7 times slower (14.9 µs to 25.8 µs,lccdf(5000 | 10000, 1000, 3000)).Limits that remain
hypergeometric_3F2_infsumnow throws when a polynomial needs more thanmax_steps = 1e5terms, asgrad_F32already did; before, it returned a truncated sum. In the cdfs this needsN - nabove1e5and terms that do not become negligible.grad_F32still stops at a term below itsprecisionargument (thebeta_neg_binomialcdfs pass1e-8). This PR does not change thebeta_neg_binomialcdfs.Tests
test/unit/math/prim/fun/hypergeometric_3F2_test.cpp:{1, 12, -1}; {10, -1}; 1is now the value2.2instead of a throw; the pole test uses{1, 12, -2}; {10, -1}; 1. New tests against exact finite sums: the series of beta_binomial_lpmf is unstable for large shape arguments #2508, a zero numerator parameter,b2 = a3 = -6, equal parameter sums atz = 1, the smallest of two non-positive integer numerators,z = -0.5and the step limit ofhypergeometric_3F2_infsum, a 117-term series whose terms fall below1e-6before its end, and the series with a tiny term before large terms.test/unit/math/prim/fun/grad_F32_test.cpp: the same2.2case as gradients (the Mathematica values from the file comment); the pole test usesa3 = -2. New tests:b2 = a3 = -6, the series of beta_binomial_lpmf is unstable for large shape arguments #2508, the smallest numerator, the 117-term series, and the tiny-term series with only two gradients requested.test/unit/math/prim/err/check_3F2_converges_test.cpp: the smallest numerator ends a polynomial; non-positive integer denominators of an infinite series are poles.test/unit/math/prim/fun/hypergeometric_3F2_tail_bound_test.cpp(new): the bound is not below the largest term ratio for 6 series with sign changes and a negativez, equals it for a cdf series, and the tail sums bound the exact sums.test/unit/math/prim/prob/beta_binomial_cdf_log_test.cpp:lcdf_matches_mathematicanow checks the valuelog(15 / 26)instead of the throw.test/unit/math/rev/prob/beta_binomial_cdfs_test.cpp(new): values and shape gradients of the three cdfs at 7 points (the point of beta_binomial_lpmf is unstable for large shape arguments #2508,beta = 1twice,alpha + beta = 1,N - n = 1, a 117-term series and its mirror); thelcdfat two points withalpha = 1and a small cdf; thecdfat three points where it is below1e-20. Relative tolerance1e-12, against sums of the pmf in mpmath.The tests find the defects. With the seven headers of #3434 swapped in, the 23 new or changed test cases fail and all other cases pass. Each commit's new tests fail with the headers of the previous commit.
Full suites. All unit tests of
hypergeometric_3F2, the new tail bound,grad_F32,grad_pFq,check_3F2_converges,hypergeometric_pFq,inv_inc_beta,beta_binomialandbeta_neg_binomialpass, and so does #3434'slarge_shapes_test(20 files:prim,rev,fwd,mix), and the generatedtest/probtests ofbeta_binomialandbeta_neg_binomial(31 600 and 69 498 cases).Release notes
Improved stability of
beta_binomial_cdf,beta_binomial_lcdfandbeta_binomial_lccdfChecklist
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