Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/issue 487 hypergeometric functions #488

Closed
wants to merge 36 commits into from

Conversation

sakrejda
Copy link
Contributor

@sakrejda sakrejda commented Feb 10, 2017

Submisison Checklist

  • Run unit tests: ./runTests.py test/unit
  • Run cpplint: make cpplint
  • Declare copyright holder and open-source license: see below

Summary:

There are currently bugs in F32 and ... I think it's grad_F32, and some potential bugs in grad_2F1 that I haven't demonstrated yet (it can terminate at an arbitrary point). See issue #487.

Intended Effect:

Fix bug in F32 and grad_F32, add some doc and tests.

How to Verify:

Run new tests versus develop to see them fail, they pass with this branch. On develop at least one of the tests hangs on my machine. Test values come from Mathematica and R's hypergeom package. The tests that fail against develop are:

cd ${STAN-MATH}
git checkout develop
git pull origin develop
git checkout bugfix/issue-487-hypergeometric-functions -- test/unit/math/prim/scal/fun/F32_test.cpp
make test/unit/math/prim/scal/fun/F32_test
./test/unit/math/prim/scal/fun/F32_test
git checkout bugfix/issue-487-hypergeometric-functions -- test/unit/math/prim/scal/prob/beta_binomial_cdf_log_test.cpp
make tests/unit/math/prim/scal/prob/beta_binomial_cdf_log_test
./test/unit/math/prim/scal/prob/beta_binomial_cdf_log_test

Side Effects:

betabinomial_cdf is now more correct and has a test that should fail with the code on develop but pass with this branch. The test seems to represent reasonable parameter values.

Documentation:

Added where it was missing.

Reviewer Suggestions:

  • The tests for F32 are done as far as I'm concerned and I was going to add parallel ones for grad_F32 and grad_2F1. If you want anymore let me know
  • The logic tracking minus signs seems bulky, suggestions appreciated but it is as hairy as it looks
  • F32 throws when the accumulator is infinite because that's a known non-convergence that sometimes occurs long before

Copyright and Licensing

Copyright holder: Krzysztof Sakrejda

By submitting this pull request, the copyright holder is agreeing to license the submitted work under the following licenses:

syclik and others added 10 commits December 1, 2016 00:31
…2, added tests, including some downstream for beta_binomial_cdf.
1. F32 and grad_F32 both assumed -1*log(x) == log(-x) which is not true, fixed
   and added a basic test.
2. From 1., the sign-flip does need to be applied after exponentiation.
3. F32 and grad_F32 both returned NaN when denominator of the increment reached
   zero but they should have been terminating successfully, fixed and
   added test comparing to Mathematica value.
4. grad_F32 could hang indefinitely, propagated Daniel's fix from F32.
5. these functions now throw when exceeding max_steps
6. grad_2F1 used to terminate after an arbitrary number of steps, now it
   throws if not converging after max_steps (and has new argument with default).
7. grad_2F1 would silently return wrong result of not converging, now throws.
@sakrejda sakrejda self-assigned this Feb 10, 2017
@sakrejda sakrejda added the bug label Feb 10, 2017
@sakrejda sakrejda added this to the 2.14.0++ milestone Feb 10, 2017
@sakrejda sakrejda removed their assignment Feb 11, 2017
@sakrejda
Copy link
Contributor Author

@syclik I screwed up the doxygen file in here, I'll revert it and then point you to the problem file.

@sakrejda
Copy link
Contributor Author

@syclik Could you take a look at how to make this pass make test-headers? It's grad_reg_inc_beta that triggers the failure because it includes the prim version of grad_2F1 and that function is prim so it only includes the is_nan from prim (and that fails when it gets a var. If I included the rev version of is_nan in either grad_reg_inc_beta or grad_2F1 it would pass, but both of those options seem wrong since grad_2F1 calls is_nan but it's prim and grad_reg_inc_beta doesn't call is_nan ...

* - <code>z</code> is less than 1
* This function will diverge if <code>z</code> is greater than 1.
* When <code>z</code> is 1, which is the case for the beta binomial
* cdf, it is hard to determine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the other hypergeometric functions, 3F2 should converge at z = 1 subject to some conditions on the other inputs. In particular, there should be a reflection symmetry that allows you to calculate near z = 1 by reflecting to 1 - z with corresponding changes to the other inputs. This is critical for ensuring robust calculations -- see, for example, https://github.com/stan-dev/math/blob/develop/stan/math/prim/scal/fun/inc_beta_dda.hpp.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 , I haven't fixed all the doc yet, I have a reference for when it does converge. Do you think we should be checking for convergence conditions at the front? The original function didn't do this and I was aiming for more of a bugfix here with the intention of improving the functions as a second PR. I'll either include the improvements here or make a specific second PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaving the reflection symmetry for a subsequent issue #511


tNew = exp(logT);
if (is_nan(p) || p == 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little iffy about this is_nan check. When would p be NaN? Poles, such as b1 = -k, can be checked beforehand and overflow can be avoided by computing and factoring partial terms is necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if b1=-k or b2=-k this is triggered as the exit condition and I get answers within floating point precision of Mathematica as the answer. I don't think we need an alternative calculation, although maybe checking for b1=-k || b2=-k would be clearer. Suggestions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@betanalpha many sources seem to say the function is undefined when any denominator is a negative integer, mathematica gives an answer indistinguishable from terminating (as above) at the last defined term of the sum. For this PR I'll either leave the code matching Mathematica or throw an error, I'll leave improving this situation for issue #511 as the current implementation doesn't do any better than this. Sound ok?

bool T_is_negative = false;
T p = 0.0;
do {
p = (a1 + k) / (b1 + k) * (a2 + k) / (b2 + k) * (a3 + k) / (k + 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line definitely needs parentheses. I much prefer the older version as it makes the possible poles much more explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a victim of too many git commits while looking for the bug, I'll revert this line to the original which was fine.

while (fabs(tDak * (a + (k - 1)) ) > precision || k == 0) {
const T r = ( (a + k) / (c + k) ) * ( (b + k) / (T)(k + 1) ) * z;
tDak = r * tDak * (a + (k - 1)) / (a + k);
do {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The do/while construction is somewhat awkward and certainly uncommon in C++. If you're worried that the while loop needs to be executed at least once then you can just do a while(true) and break on the termination condition at the end of the loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems really clear for algorithms that hit the loop at least once but I'm fine changing it.

@seantalts
Copy link
Member

I'm not sure about the link to kill a build, but can you click on the build "Details" and be brought to the Jenkins page and then kill it from there?

@sakrejda
Copy link
Contributor Author

@syclik I think I said this above but there's no (remaining) merge conflict. Maybe it's screwed up because I merged in develop and fixed a merge conflict with develop at some point. For the next few days I'll just leave this PR so I can address the comments already on it but then I'll do a fresh PR---it looks like when I do that as a test the new PR doesn't have any of the messy history.

@syclik
Copy link
Member

syclik commented Mar 20, 2017

Yup, the merge conflict isn't the problem now. It looks like cpplint to start.

@bob-carpenter
Copy link
Contributor

@sakrejda This is failing due to cpplint errors.

@sakrejda
Copy link
Contributor Author

sakrejda commented Mar 20, 2017 via email

@sakrejda
Copy link
Contributor Author

This now passes lint locally, I addressed the comments above except for the ones I punted to issue #511. The tests that call the modified functions directly or indirectly have been run AFAIK and I'm waiting for the rest to complete. The upshot was that I needed to move the gradient functions to log-scale power series as well and that lets me get EXPECT_NEAR to pass with 1e-8 error threshold while setting the precision to something like 1e-10. Previously some of the old tests were passing but with a threshold of 1e-5/6 and some of the tests I added had errors up to 1e-4. It seems like it's an open question what the default accuracy should be but now that it seems to achieve accuracy as requested I can look at some of the other gradient functions as a guide. A second round of comments would be useful at this point (feel free to wait until the tests pass) for me to figure out what needs to happen to finish this off. Keep in mind that I'd like to punt overall improvements to a separate issue and keep this to bug fixes and better defined behavior.

…diffs grad_2F1. grad_2F1 might need a non-autodiff gradient for this to be more accurate in the future.
@sakrejda
Copy link
Contributor Author

@seantalts for some reason the Jenkins build fails and I can't even pull up the log, any chance you coudl look at it?

@bob-carpenter
Copy link
Contributor

bob-carpenter commented Mar 31, 2017 via email

@sakrejda
Copy link
Contributor Author

sakrejda commented Apr 3, 2017

I have no idea why everyone else can see the Jenkins builds on this PR and I can't I'm going to close it and start a new one.

@sakrejda sakrejda closed this Apr 3, 2017
@seantalts seantalts modified the milestones: 2.14.0++, v2.15.0 Apr 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants