Skip to content

Commit dd59c01

Browse files
committed
Fixes for #1198.
Use asymptotic expansion for very large a,b and add test cases. Add some corrections to improve numeric stability. Add better error handling of cases that explode so we get evaluation_error's.
1 parent 4d58e93 commit dd59c01

File tree

7 files changed

+1539
-30
lines changed

7 files changed

+1539
-30
lines changed

include/boost/math/special_functions/beta.hpp

+98-9
Original file line numberDiff line numberDiff line change
@@ -240,21 +240,25 @@ BOOST_MATH_GPU_ENABLED T ibeta_power_terms(T a,
240240
T c = a + b;
241241

242242
// combine power terms with Lanczos approximation:
243-
T agh = static_cast<T>(a + Lanczos::g() - 0.5f);
244-
T bgh = static_cast<T>(b + Lanczos::g() - 0.5f);
245-
T cgh = static_cast<T>(c + Lanczos::g() - 0.5f);
243+
T gh = Lanczos::g() - 0.5f;
244+
T agh = static_cast<T>(a + gh);
245+
T bgh = static_cast<T>(b + gh);
246+
T cgh = static_cast<T>(c + gh);
246247
if ((a < tools::min_value<T>()) || (b < tools::min_value<T>()))
247248
result = 0; // denominator overflows in this case
248249
else
249250
result = Lanczos::lanczos_sum_expG_scaled(c) / (Lanczos::lanczos_sum_expG_scaled(a) * Lanczos::lanczos_sum_expG_scaled(b));
251+
BOOST_MATH_INSTRUMENT_VARIABLE(result);
250252
result *= prefix;
253+
BOOST_MATH_INSTRUMENT_VARIABLE(result);
251254
// combine with the leftover terms from the Lanczos approximation:
252255
result *= sqrt(bgh / boost::math::constants::e<T>());
253256
result *= sqrt(agh / cgh);
257+
BOOST_MATH_INSTRUMENT_VARIABLE(result);
254258

255259
// l1 and l2 are the base of the exponents minus one:
256-
T l1 = (x * b - y * agh) / agh;
257-
T l2 = (y * a - x * bgh) / bgh;
260+
T l1 = ((x * b - y * a) - y * gh) / agh;
261+
T l2 = ((y * a - x * b) - x * gh) / bgh;
258262
if((BOOST_MATH_GPU_SAFE_MIN(fabs(l1), fabs(l2)) < 0.2))
259263
{
260264
// when the base of the exponent is very near 1 we get really
@@ -809,9 +813,8 @@ struct ibeta_fraction2_t
809813

810814
BOOST_MATH_GPU_ENABLED result_type operator()()
811815
{
812-
T aN = (a + m - 1) * (a + b + m - 1) * m * (b - m) * x * x;
813816
T denom = (a + 2 * m - 1);
814-
aN /= denom * denom;
817+
T aN = (m * (a + m - 1) / denom) * ((a + b + m - 1) / denom) * (b - m) * x * x;
815818

816819
T bN = static_cast<T>(m);
817820
bN += (m * (b - m) * x) / (a + 2*m - 1);
@@ -844,7 +847,9 @@ BOOST_MATH_GPU_ENABLED inline T ibeta_fraction2(T a, T b, T x, T y, const Policy
844847
return result;
845848

846849
ibeta_fraction2_t<T> f(a, b, x, y);
847-
T fract = boost::math::tools::continued_fraction_b(f, boost::math::policies::get_epsilon<T, Policy>());
850+
boost::uintmax_t max_terms = boost::math::policies::get_max_series_iterations<Policy>();
851+
T fract = boost::math::tools::continued_fraction_b(f, boost::math::policies::get_epsilon<T, Policy>(), max_terms);
852+
boost::math::policies::check_series_iterations<T>("boost::math::ibeta", max_terms, pol);
848853
BOOST_MATH_INSTRUMENT_VARIABLE(fract);
849854
BOOST_MATH_INSTRUMENT_VARIABLE(result);
850855
return result / fract;
@@ -1118,6 +1123,60 @@ BOOST_MATH_GPU_ENABLED T binomial_ccdf(T n, T k, T x, T y, const Policy& pol)
11181123
return result;
11191124
}
11201125

1126+
template <class T, class Policy>
1127+
T ibeta_large_ab(T a, T b, T x, T y, bool invert, bool normalised, const Policy& pol)
1128+
{
1129+
//
1130+
// Large arguments, symetric case, see https://dlmf.nist.gov/8.18
1131+
//
1132+
BOOST_MATH_STD_USING
1133+
1134+
T x0 = a / (a + b);
1135+
T y0 = b / (a + b);
1136+
T nu = x0 * log(x / x0) + y0 * log(y / y0);
1137+
//
1138+
// Above compution is unstable, force nu to zero if
1139+
// something went wrong:
1140+
//
1141+
if ((nu > 0) || (x == x0) || (y == y0))
1142+
nu = 0;
1143+
nu = sqrt(-2 * nu);
1144+
//
1145+
// As per https://dlmf.nist.gov/8.18#E10 we need to make sure we have the correct root:
1146+
//
1147+
if ((nu != 0) && (nu / (x - x0) < 0))
1148+
nu = -nu;
1149+
//
1150+
// The correction term in https://dlmf.nist.gov/8.18#E9 is badly unstable, and often
1151+
// makes the compution worse not better, we exclude it for now:
1152+
/*
1153+
T c0 = 0;
1154+
1155+
if (nu != 0)
1156+
{
1157+
c0 = 1 / nu;
1158+
T lim = fabs(10 * tools::epsilon<T>() * c0);
1159+
c0 -= sqrt(x0 * y0) / (x - x0);
1160+
if(fabs(c0) < lim)
1161+
c0 = (1 - 2 * x0) / (3 * sqrt(x0 * y0));
1162+
else
1163+
c0 *= exp(a * log(x / x0) + b * log(y / y0));
1164+
c0 /= sqrt(constants::two_pi<T>() * (a + b));
1165+
}
1166+
else
1167+
{
1168+
c0 = (1 - 2 * x0) / (3 * sqrt(x0 * y0));
1169+
c0 /= sqrt(constants::two_pi<T>() * (a + b));
1170+
}
1171+
*/
1172+
T mul = 1;
1173+
if (!normalised)
1174+
mul = beta(a, b, pol);
1175+
1176+
return mul * ((invert ? (1 + boost::math::erf(-nu * sqrt((a + b) / 2), pol)) / 2 : boost::math::erfc(-nu * sqrt((a + b) / 2), pol) / 2));
1177+
}
1178+
1179+
11211180

11221181
//
11231182
// The incomplete beta function implementation:
@@ -1502,7 +1561,37 @@ BOOST_MATH_GPU_ENABLED T ibeta_imp(T a, T b, T x, const Policy& pol, bool inv, b
15021561
}
15031562
else
15041563
{
1505-
fract = ibeta_fraction2(a, b, x, y, pol, normalised, p_derivative);
1564+
// a and b both large:
1565+
bool use_asym = false;
1566+
T ma = (std::max)(a, b);
1567+
T xa = ma == a ? x : y;
1568+
T saddle = ma / (a + b);
1569+
T powers = 0;
1570+
if ((ma > 1e-5f / tools::epsilon<T>()) && (ma / (std::min)(a, b) < (xa < saddle ? 2 : 15)))
1571+
{
1572+
if (a == b)
1573+
use_asym = true;
1574+
else
1575+
{
1576+
powers = exp(log(x / (a / (a + b))) * a + log(y / (b / (a + b))) * b);
1577+
if (powers < tools::epsilon<T>())
1578+
use_asym = true;
1579+
}
1580+
}
1581+
if(use_asym)
1582+
{
1583+
fract = ibeta_large_ab(a, b, x, y, invert, normalised, pol);
1584+
if (fract * tools::epsilon<T>() < powers)
1585+
{
1586+
// Erf approximation failed, correction term is too large, fall back:
1587+
fract = ibeta_fraction2(a, b, x, y, pol, normalised, p_derivative);
1588+
}
1589+
else
1590+
invert = false;
1591+
}
1592+
else
1593+
fract = ibeta_fraction2(a, b, x, y, pol, normalised, p_derivative);
1594+
15061595
BOOST_MATH_INSTRUMENT_VARIABLE(fract);
15071596
}
15081597
}

test/Jamfile.v2

+7
Original file line numberDiff line numberDiff line change
@@ -260,20 +260,23 @@ test-suite special_fun :
260260
: # requirements
261261
<define>TEST_FLOAT
262262
<toolset>intel:<pch>off
263+
<toolset>gcc:<cxxflags>-Wno-overflow
263264
: test_ibeta_float ]
264265
[ run test_ibeta.cpp test_instances//test_instances pch_light /boost/test//boost_unit_test_framework
265266
: # command line
266267
: # input files
267268
: # requirements
268269
<define>TEST_DOUBLE
269270
<toolset>intel:<pch>off
271+
<toolset>gcc:<cxxflags>-Wno-overflow
270272
: test_ibeta_double ]
271273
[ run test_ibeta.cpp test_instances//test_instances pch_light /boost/test//boost_unit_test_framework
272274
: # command line
273275
: # input files
274276
: # requirements
275277
<define>TEST_LDOUBLE
276278
<toolset>intel:<pch>off
279+
<toolset>gcc:<cxxflags>-Wno-overflow
277280
: test_ibeta_long_double ]
278281
[ run test_ibeta.cpp test_instances//test_instances pch_light /boost/test//boost_unit_test_framework
279282
: # command line
@@ -282,6 +285,7 @@ test-suite special_fun :
282285
<define>TEST_REAL_CONCEPT
283286
<define>TEST_DATA=1
284287
<toolset>intel:<pch>off
288+
<toolset>gcc:<cxxflags>-Wno-overflow
285289
: test_ibeta_real_concept1 ]
286290
[ run test_ibeta.cpp test_instances//test_instances pch_light /boost/test//boost_unit_test_framework
287291
: # command line
@@ -290,6 +294,7 @@ test-suite special_fun :
290294
<define>TEST_REAL_CONCEPT
291295
<define>TEST_DATA=2
292296
<toolset>intel:<pch>off
297+
<toolset>gcc:<cxxflags>-Wno-overflow
293298
: test_ibeta_real_concept2 ]
294299
[ run test_ibeta.cpp test_instances//test_instances pch_light /boost/test//boost_unit_test_framework
295300
: # command line
@@ -298,6 +303,7 @@ test-suite special_fun :
298303
<define>TEST_REAL_CONCEPT
299304
<define>TEST_DATA=3
300305
<toolset>intel:<pch>off
306+
<toolset>gcc:<cxxflags>-Wno-overflow
301307
: test_ibeta_real_concept3 ]
302308
[ run test_ibeta.cpp test_instances//test_instances pch_light /boost/test//boost_unit_test_framework
303309
: # command line
@@ -306,6 +312,7 @@ test-suite special_fun :
306312
<define>TEST_REAL_CONCEPT
307313
<define>TEST_DATA=4
308314
<toolset>intel:<pch>off
315+
<toolset>gcc:<cxxflags>-Wno-overflow
309316
: test_ibeta_real_concept4 ]
310317

311318
[ run test_ibeta_derivative.cpp test_instances//test_instances pch_light /boost/test//boost_unit_test_framework

0 commit comments

Comments
 (0)