Fixes ibeta for large arguments#1363
Conversation
|
@jzmaddock is there a way to see if the continued fraction is converging quickly or not? Maybe a ratio of subsequent terms or a change in the value of the continued fraction? In this spirit, the easiest think would just be to add a try/catch statement as done here try
{
fract = ibeta_fraction2(a, b, x, y, pol, normalised, p_derivative);
}
// If series converges slowly, fall back to erf approximation
catch (boost::math::evaluation_error& e)
{
fract = ibeta_large_ab(a, b, x, y, invert, normalised, pol);
}Maybe this will have unintended consequences though? |
Non-programatically you can define
We test and run on a number of BOOST_MATH_GPU_ENABLED inline T ibeta_fraction2(T a, T b, T x, T y, const Policy& pol, bool normalised, T* p_derivative, boost::math::uintmax_t iters = nullptr);Since uintmax_t iters;
fract = ibeta_fraction2(a, b, x, y, pol, normalised, p_derivative, &iters);
If (too many iters)
{
fract = ibeta_large_ab(a, b, x, y, invert, normalised, pol);
}Defaulting to, and checking the |
The issue is that before returning, boost::math::policies::check_series_iterations<T>("boost::math::ibeta", max_terms, pol);which throws the evaluation error. I think that your solution would work, but then we would have to get rid of the check above in Maybe there's an easier solution I'm not seeing though. Here's the function in question: template <class T, class Policy>
BOOST_MATH_GPU_ENABLED inline T ibeta_fraction2(T a, T b, T x, T y, const Policy& pol, bool normalised, T* p_derivative)
{
typedef typename lanczos::lanczos<T, Policy>::type lanczos_type;
BOOST_MATH_STD_USING
T result = ibeta_power_terms(a, b, x, y, lanczos_type(), normalised, pol);
if(p_derivative)
{
*p_derivative = result;
BOOST_MATH_ASSERT(*p_derivative >= 0);
}
if(result == 0)
return result;
ibeta_fraction2_t<T> f(a, b, x, y);
boost::math::uintmax_t max_terms = boost::math::policies::get_max_series_iterations<Policy>();
T fract = boost::math::tools::continued_fraction_b(f, boost::math::policies::get_epsilon<T, Policy>(), max_terms);
boost::math::policies::check_series_iterations<T>("boost::math::ibeta", max_terms, pol); // Want to catch this error!
BOOST_MATH_INSTRUMENT_VARIABLE(fract);
BOOST_MATH_INSTRUMENT_VARIABLE(result);
return result / fract;
} |
|
In this case non-convergence isn't exceptional, it's somewhat expected right? Passing a modified policy to template <class T, class Policy>
BOOST_MATH_GPU_ENABLED inline T ibeta_fraction2(T a, T b, T x, T y, const Policy& pol, bool normalised, T* p_derivative, uintmax_t* iters = nullptr)
{
typedef typename lanczos::lanczos<T, Policy>::type lanczos_type;
BOOST_MATH_STD_USING
T result = ibeta_power_terms(a, b, x, y, lanczos_type(), normalised, pol);
if(p_derivative)
{
*p_derivative = result;
BOOST_MATH_ASSERT(*p_derivative >= 0);
}
if(result == 0)
return result;
ibeta_fraction2_t<T> f(a, b, x, y);
boost::math::uintmax_t max_terms = boost::math::policies::get_max_series_iterations<Policy>();
T fract = boost::math::tools::continued_fraction_b(f, boost::math::policies::get_epsilon<T, Policy>(), max_terms);
boost::math::policies::check_series_iterations<T>("boost::math::ibeta", max_terms, pol); // Nothing happens
if(iters && max_iter >= policies::get_max_series_iterations<Policy>()) *iters = max_iter;
BOOST_MATH_INSTRUMENT_VARIABLE(fract);
BOOST_MATH_INSTRUMENT_VARIABLE(result);
return result / fract;
} |
I think that should work! I'm not very familiar with policies though. How would I modify the policy to |
using ignore_iterations_policy = typename boost::math::policies::normalise<
Policy,
boost::math::policies::max_series_iterations_error<boost::math::policies::ignore_error>
>::type;
ignore_iterations_policy new_policy;And then use the |
I implemented this, but am getting a ton of errors when running all the I'm not sure what the solution here should be. However, the try/catch statement seems the simplest and all of the test cases pass with this implementation. |
|
I'll try and look into the policy thing later. |
|
It's a bit of a cop-out, but this patch passes tests locally for me: |
If I'm understanding your code correctly, you've simply just written out all the logic from This is kind of what I had in mind, but wasn't implementing well. I think this is a reasonable solution! We've copied the code from |
|
As far as test data goes, how should I go about testing this region since Mathematica hasn't been able to produce results. The simplest test is to make sure that I could also implement the erf approximation in Mathematica and test that we're close to these values. We wouldn't be comparing to "true" data, but maybe this is the closest that anyone can get? |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #1363 +/- ##
===========================================
+ Coverage 95.32% 95.43% +0.11%
===========================================
Files 825 761 -64
Lines 67994 62344 -5650
===========================================
- Hits 64815 59498 -5317
+ Misses 3179 2846 -333
... and 82 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
|
Hey everyone, If you get the feeling that we start playing whack-a-mole in the sense that one fix causes trouble in other cases in this regime, it should be fine if you declare certain inputs infeasible @jzmaddock . |
Correct, we can stop a call to ibeta_fraction2 from throwing at this location, but we don't get back the iteration count only the result so far, so a bit of cut and paste seemed the best solution.
That's sensible, at least we should do a bit of due diligence. I suspect the error rates are so large in this area that we'll get a lot of noise, and a monotonically increasing result is probably too much to ask for. What we could do is generate a scatter plot for a handful of values of a and b, and check things look sensible as x goes through the saddle point and we switch methods.
Hmm, the problem with that is we don't necessarily have a good handle on what the error rate inherent to the approximation is?
For sure, and we're definitely on the limit with this one, certainly any results in this domain are likely to be very approximate, but I also think we can do a little bit better than at present. |
Was this made with the files in the current PR? I'd say that looks pretty good too! I can try to write up a test to ensure the values are first off not |
This PR with the patch I suggested applied. It's just one data point of course, but it's encouraging at least. The values do seem to be monotonically increasing too, though I suspect if you zoomed in on a small enough interval that might change as you see more "noise". This is probably true of all the special functions though. |
I added some very basic testing to see if // Bug testing for large a,b and x close to a / (a+b). See PR 1363.
// The values for a,b are just too large for floats to handle.
if (!std::is_same<T, float>::value)
{
T a_values[2] = {10000000272564224, 3.1622776601699636e+16};
T b_values[2] = {9965820922822656, 3.130654883566682e+18};
T delta[8] = {0, 1e-15, 1e-14, 1e-13, 1e-12, 1e-11, 1e-10, 1e-9};
T a, b, x;
for (unsigned int i=0; i<2; i++){
a = a_values[i];
b = b_values[i];
x = a / (a+b); // roughly the median of ibeta
for (unsigned int j=0; j < 7; j++)
{
BOOST_CHECK(boost::math::ibeta(a, b, x + delta[j+1]) > boost::math::ibeta(a, b, x + delta[j]));
BOOST_CHECK(boost::math::ibeta(a, b, x - delta[j]) > boost::math::ibeta(a, b, x - delta[j+1]));
}
}
} |

Closes #1361 and scipy/scipy#24566.
Only the expansion for$\eta$ has been added. When to use the erf expansion and relevant tests need to be added.