From 920f4affe52beef059a680b694cbae7e7cd26785 Mon Sep 17 00:00:00 2001 From: jzmaddock Date: Sat, 4 Mar 2023 18:31:23 +0000 Subject: [PATCH 1/2] Improve error handling in non-central distributions. --- .../detail/common_error_handling.hpp | 7 ++- .../math/distributions/non_central_beta.hpp | 10 ++-- .../distributions/non_central_chi_squared.hpp | 24 ++++---- .../math/distributions/non_central_f.hpp | 14 ++--- .../math/distributions/non_central_t.hpp | 58 +++++++++---------- test/test_nc_beta.cpp | 16 +++++ test/test_nc_chi_squared.hpp | 19 ++++++ test/test_nc_f.cpp | 15 +++++ test/test_nc_t.hpp | 18 ++++++ 9 files changed, 125 insertions(+), 56 deletions(-) diff --git a/include/boost/math/distributions/detail/common_error_handling.hpp b/include/boost/math/distributions/detail/common_error_handling.hpp index b91d2036f9..f03f2c49b8 100644 --- a/include/boost/math/distributions/detail/common_error_handling.hpp +++ b/include/boost/math/distributions/detail/common_error_handling.hpp @@ -185,11 +185,12 @@ inline bool check_non_centrality( RealType* result, const Policy& pol) { - if((ncp < 0) || !(boost::math::isfinite)(ncp)) - { // Assume scale == 0 is NOT valid for any distribution. + static const RealType upper_limit = static_cast((std::numeric_limits::max)()) - boost::math::policies::get_max_root_iterations(); + if((ncp < 0) || !(boost::math::isfinite)(ncp) || ncp > upper_limit) + { *result = policies::raise_domain_error( function, - "Non centrality parameter is %1%, but must be > 0 !", ncp, pol); + "Non centrality parameter is %1%, but must be > 0, and a countable value such that x+1 != x", ncp, pol); return false; } return true; diff --git a/include/boost/math/distributions/non_central_beta.hpp b/include/boost/math/distributions/non_central_beta.hpp index 9d1eedecca..9266c9e576 100644 --- a/include/boost/math/distributions/non_central_beta.hpp +++ b/include/boost/math/distributions/non_central_beta.hpp @@ -430,7 +430,7 @@ namespace boost static_cast(p), &r, Policy())) - return (RealType)r; + return static_cast(r); // // Special cases first: // @@ -624,7 +624,7 @@ namespace boost static_cast(x), &r, Policy())) - return (RealType)r; + return static_cast(r); if(l == 0) return pdf(boost::math::beta_distribution(dist.alpha(), dist.beta()), x); @@ -761,7 +761,7 @@ namespace boost l, &r, Policy())) - return (RealType)r; + return static_cast(r); RealType c = a + b + l / 2; RealType mean = 1 - (b / c) * (1 + l / (2 * c * c)); return detail::generic_find_mode_01( @@ -872,7 +872,7 @@ namespace boost x, &r, Policy())) - return (RealType)r; + return static_cast(r); if(l == 0) return cdf(beta_distribution(a, b), x); @@ -909,7 +909,7 @@ namespace boost x, &r, Policy())) - return (RealType)r; + return static_cast(r); if(l == 0) return cdf(complement(beta_distribution(a, b), x)); diff --git a/include/boost/math/distributions/non_central_chi_squared.hpp b/include/boost/math/distributions/non_central_chi_squared.hpp index 5a84c734f2..c85c1d08d1 100644 --- a/include/boost/math/distributions/non_central_chi_squared.hpp +++ b/include/boost/math/distributions/non_central_chi_squared.hpp @@ -88,7 +88,7 @@ namespace boost // stable direction for the gamma function // recurrences: // - int i; + long long i; for(i = k; static_cast(i-k) < max_iter; ++i) { T term = poisf * gamf; @@ -299,7 +299,7 @@ namespace boost if(pois == 0) return 0; T poisb = pois; - for(int i = k; ; ++i) + for(long long i = k; ; ++i) { sum += pois; if(pois / sum < errtol) @@ -310,7 +310,7 @@ namespace boost "Series did not converge, closest value was %1%", sum, pol); pois *= l2 * x2 / ((i + 1) * (n2 + i)); } - for(int i = k - 1; i >= 0; --i) + for(long long i = k - 1; i >= 0; --i) { poisb *= (i + 1) * (n2 + i) / (l2 * x2); sum += poisb; @@ -428,7 +428,7 @@ namespace boost static_cast(p), &r, Policy())) - return (RealType)r; + return static_cast(r); // // Special cases get short-circuited first: // @@ -519,7 +519,7 @@ namespace boost (value_type)x, &r, Policy())) - return (RealType)r; + return static_cast(r); if(l == 0) return pdf(boost::math::chi_squared_distribution(dist.degrees_of_freedom()), x); @@ -821,7 +821,7 @@ namespace boost l, &r, Policy())) - return r; + return static_cast(r); return k + l; } // mean @@ -842,7 +842,7 @@ namespace boost l, &r, Policy())) - return (RealType)r; + return static_cast(r); bool asymptotic_mode = k < l/4; RealType starting_point = asymptotic_mode ? k + l - RealType(3) : RealType(1) + k; return detail::generic_find_mode(dist, starting_point, function); @@ -864,7 +864,7 @@ namespace boost l, &r, Policy())) - return r; + return static_cast(r); return 2 * (2 * l + k); } @@ -887,7 +887,7 @@ namespace boost l, &r, Policy())) - return r; + return static_cast(r); BOOST_MATH_STD_USING return pow(2 / (k + 2 * l), RealType(3)/2) * (k + 3 * l); } @@ -908,7 +908,7 @@ namespace boost l, &r, Policy())) - return r; + return static_cast(r); return 12 * (k + 4 * l) / ((k + 2 * l) * (k + 2 * l)); } // kurtosis_excess @@ -946,7 +946,7 @@ namespace boost x, &r, Policy())) - return r; + return static_cast(r); return detail::non_central_chi_squared_cdf(x, k, l, false, Policy()); } // cdf @@ -975,7 +975,7 @@ namespace boost x, &r, Policy())) - return r; + return static_cast(r); return detail::non_central_chi_squared_cdf(x, k, l, true, Policy()); } // ccdf diff --git a/include/boost/math/distributions/non_central_f.hpp b/include/boost/math/distributions/non_central_f.hpp index bb122029b8..e93d03e597 100644 --- a/include/boost/math/distributions/non_central_f.hpp +++ b/include/boost/math/distributions/non_central_f.hpp @@ -106,7 +106,7 @@ namespace boost l, &r, Policy())) - return r; + return r; if(v2 <= 2) return policies::raise_domain_error( function, @@ -137,7 +137,7 @@ namespace boost l, &r, Policy())) - return r; + return r; RealType guess = m > 2 ? RealType(m * (n + l) / (n * (m - 2))) : RealType(1); return detail::generic_find_mode( dist, @@ -166,7 +166,7 @@ namespace boost l, &r, Policy())) - return r; + return r; if(m <= 4) return policies::raise_domain_error( function, @@ -203,7 +203,7 @@ namespace boost l, &r, Policy())) - return r; + return r; if(m <= 6) return policies::raise_domain_error( function, @@ -240,7 +240,7 @@ namespace boost l, &r, Policy())) - return r; + return r; if(m <= 8) return policies::raise_domain_error( function, @@ -309,7 +309,7 @@ namespace boost dist.non_centrality(), &r, Policy())) - return r; + return r; if((x < 0) || !(boost::math::isfinite)(x)) { @@ -350,7 +350,7 @@ namespace boost c.dist.non_centrality(), &r, Policy())) - return r; + return r; if((c.param < 0) || !(boost::math::isfinite)(c.param)) { diff --git a/include/boost/math/distributions/non_central_t.hpp b/include/boost/math/distributions/non_central_t.hpp index 9468a56c5e..cb9af7e2e5 100644 --- a/include/boost/math/distributions/non_central_t.hpp +++ b/include/boost/math/distributions/non_central_t.hpp @@ -307,9 +307,9 @@ namespace boost function, v, &r, Policy()) || - !detail::check_finite( + !detail::check_non_centrality( function, - delta, + static_cast(delta * delta), &r, Policy()) || @@ -730,9 +730,9 @@ namespace boost detail::check_df_gt0_to_inf( function, v, &r, Policy()); - detail::check_finite( + detail::check_non_centrality( function, - lambda, + static_cast(lambda * lambda), &r, Policy()); } // non_central_t_distribution constructor. @@ -874,12 +874,12 @@ namespace boost function, v, &r, Policy()) || - !detail::check_finite( + !detail::check_non_centrality( function, - l, + static_cast(l * l), &r, Policy())) - return (RealType)r; + return static_cast(r); BOOST_MATH_STD_USING @@ -912,12 +912,12 @@ namespace boost function, v, &r, Policy()) || - !detail::check_finite( + !detail::check_non_centrality( function, - l, + static_cast(l * l), &r, Policy())) - return (RealType)r; + return static_cast(r); if(v <= 1) return policies::raise_domain_error( function, @@ -947,12 +947,12 @@ namespace boost function, v, &r, Policy()) || - !detail::check_finite( + !detail::check_non_centrality( function, - l, + static_cast(l * l), &r, Policy())) - return (RealType)r; + return static_cast(r); if(v <= 2) return policies::raise_domain_error( function, @@ -982,12 +982,12 @@ namespace boost function, v, &r, Policy()) || - !detail::check_finite( + !detail::check_non_centrality( function, - l, + static_cast(l * l), &r, Policy())) - return (RealType)r; + return static_cast(r); if(v <= 3) return policies::raise_domain_error( function, @@ -1014,12 +1014,12 @@ namespace boost function, v, &r, Policy()) || - !detail::check_finite( + !detail::check_non_centrality( function, - l, + static_cast(l * l), &r, Policy())) - return (RealType)r; + return static_cast(r); if(v <= 4) return policies::raise_domain_error( function, @@ -1053,9 +1053,9 @@ namespace boost function, v, &r, Policy()) || - !detail::check_finite( + !detail::check_non_centrality( function, - l, + static_cast(l * l), // we need l^2 to be countable. &r, Policy()) || @@ -1064,7 +1064,7 @@ namespace boost t, &r, Policy())) - return (RealType)r; + return static_cast(r); return policies::checked_narrowing_cast( detail::non_central_t_pdf(static_cast(v), static_cast(l), @@ -1093,9 +1093,9 @@ namespace boost function, v, &r, Policy()) || - !detail::check_finite( + !detail::check_non_centrality( function, - l, + static_cast(l * l), &r, Policy()) || @@ -1104,8 +1104,8 @@ namespace boost x, &r, Policy())) - return (RealType)r; - if ((boost::math::isinf)(v)) + return static_cast(r); + if ((boost::math::isinf)(v)) { // Infinite degrees of freedom, so use normal distribution located at delta. normal_distribution n(l, 1); cdf(n, x); @@ -1147,9 +1147,9 @@ namespace boost function, v, &r, Policy()) || - !detail::check_finite( + !detail::check_non_centrality( function, - l, + static_cast(l * l), &r, Policy()) || @@ -1158,7 +1158,7 @@ namespace boost x, &r, Policy())) - return (RealType)r; + return static_cast(r); if ((boost::math::isinf)(v)) { // Infinite degrees of freedom, so use normal distribution located at delta. diff --git a/test/test_nc_beta.cpp b/test/test_nc_beta.cpp index a2623e731b..3e7c08d0f3 100644 --- a/test/test_nc_beta.cpp +++ b/test/test_nc_beta.cpp @@ -257,6 +257,22 @@ void test_spots(RealType) BOOST_MATH_CHECK_THROW(skewness(dist), boost::math::evaluation_error); BOOST_MATH_CHECK_THROW(kurtosis(dist), boost::math::evaluation_error); BOOST_MATH_CHECK_THROW(kurtosis_excess(dist), boost::math::evaluation_error); + // + // Some special error handling tests, if the non-centrality param is too large + // then we have no evaluation method and should get a domain_error: + // + using std::ldexp; + using distro1 = boost::math::non_central_beta_distribution; + using distro2 = boost::math::non_central_beta_distribution>>; + using de = std::domain_error; + BOOST_MATH_CHECK_THROW(distro1(2, 3, ldexp(RealType(1), 100)), de); + if (std::numeric_limits::has_quiet_NaN) + { + distro2 d2(2, 3, ldexp(RealType(1), 100)); + BOOST_CHECK(boost::math::isnan(pdf(d2, 0.5))); + BOOST_CHECK(boost::math::isnan(cdf(d2, 0.5))); + BOOST_CHECK(boost::math::isnan(cdf(complement(d2, 0.5)))); + } } // template void test_spots(RealType) diff --git a/test/test_nc_chi_squared.hpp b/test/test_nc_chi_squared.hpp index d65d1b628f..b2fa6d75be 100644 --- a/test/test_nc_chi_squared.hpp +++ b/test/test_nc_chi_squared.hpp @@ -3,7 +3,10 @@ // Boost Software License, Version 1.0. (See accompanying file // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_MATH_OVERFLOW_ERROR_POLICY #define BOOST_MATH_OVERFLOW_ERROR_POLICY ignore_error +#endif + #include #define BOOST_TEST_MAIN #include @@ -271,6 +274,22 @@ void test_spots(RealType) BOOST_MATH_CHECK_THROW(pdf(boost::math::non_central_chi_squared_distribution(1, -1), 0), std::domain_error); BOOST_MATH_CHECK_THROW(quantile(boost::math::non_central_chi_squared_distribution(1, 1), -1), std::domain_error); BOOST_MATH_CHECK_THROW(quantile(boost::math::non_central_chi_squared_distribution(1, 1), 2), std::domain_error); + // + // Some special error handling tests, if the non-centrality param is too large + // then we have no evaluation method and should get a domain_error: + // + using std::ldexp; + using distro1 = boost::math::non_central_chi_squared_distribution; + using distro2 = boost::math::non_central_chi_squared_distribution>>; + using de = std::domain_error; + BOOST_MATH_CHECK_THROW(distro1(2, ldexp(RealType(1), 100)), de); + if (std::numeric_limits::has_quiet_NaN) + { + distro2 d2(2, ldexp(RealType(1), 100)); + BOOST_CHECK(boost::math::isnan(pdf(d2, 0.5))); + BOOST_CHECK(boost::math::isnan(cdf(d2, 0.5))); + BOOST_CHECK(boost::math::isnan(cdf(complement(d2, 0.5)))); + } #endif } // template void test_spots(RealType) diff --git a/test/test_nc_f.cpp b/test/test_nc_f.cpp index b3123980ba..1cf411516d 100644 --- a/test/test_nc_f.cpp +++ b/test/test_nc_f.cpp @@ -293,6 +293,21 @@ void test_spots(RealType) BOOST_MATH_CHECK_THROW(pdf(boost::math::non_central_f_distribution(1, -1, 1), 0), std::domain_error); BOOST_MATH_CHECK_THROW(quantile(boost::math::non_central_f_distribution(1, 1, 1), -1), std::domain_error); BOOST_MATH_CHECK_THROW(quantile(boost::math::non_central_f_distribution(1, 1, 1), 2), std::domain_error); + // + // Some special error handling tests, if the non-centrality param is too large + // then we have no evaluation method and should get a domain_error: + // + using std::ldexp; + using distro1 = boost::math::non_central_f_distribution; + using distro2 = boost::math::non_central_f_distribution>>; + using de = std::domain_error; + BOOST_MATH_CHECK_THROW(distro1(2, 3, ldexp(RealType(1), 100)), de); + if (std::numeric_limits::has_quiet_NaN) + { + distro2 d2(2, 3, ldexp(RealType(1), 100)); + BOOST_CHECK(boost::math::isnan(pdf(d2, 0.5))); + BOOST_CHECK(boost::math::isnan(cdf(d2, 0.5))); + } } // template void test_spots(RealType) BOOST_AUTO_TEST_CASE( test_main ) diff --git a/test/test_nc_t.hpp b/test/test_nc_t.hpp index 42f10f9b3e..8fb6accdf3 100644 --- a/test/test_nc_t.hpp +++ b/test/test_nc_t.hpp @@ -3,7 +3,10 @@ // Boost Software License, Version 1.0. (See accompanying file // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_MATH_OVERFLOW_ERROR_POLICY #define BOOST_MATH_OVERFLOW_ERROR_POLICY ignore_error +#endif + #include #define BOOST_TEST_MAIN #include @@ -302,6 +305,21 @@ void test_spots(RealType) BOOST_MATH_CHECK_THROW(pdf(boost::math::non_central_t_distribution(-1, 1), 0), std::domain_error); BOOST_MATH_CHECK_THROW(quantile(boost::math::non_central_t_distribution(1, 1), -1), std::domain_error); BOOST_MATH_CHECK_THROW(quantile(boost::math::non_central_t_distribution(1, 1), 2), std::domain_error); + // + // Some special error handling tests, if the non-centrality param is too large + // then we have no evaluation method and should get a domain_error: + // + using std::ldexp; + using distro1 = boost::math::non_central_t_distribution; + using distro2 = boost::math::non_central_t_distribution>>; + using de = std::domain_error; + BOOST_MATH_CHECK_THROW(distro1(2, ldexp(RealType(1), 100)), de); + if (std::numeric_limits::has_quiet_NaN) + { + distro2 d2(2, ldexp(RealType(1), 100)); + BOOST_CHECK(boost::math::isnan(pdf(d2, 0.5))); + BOOST_CHECK(boost::math::isnan(cdf(d2, 0.5))); + } } // template void test_spots(RealType) template From d8005d3df4e026b1f53d94dd7f946626d930a17e Mon Sep 17 00:00:00 2001 From: jzmaddock Date: Mon, 6 Mar 2023 17:19:02 +0000 Subject: [PATCH 2/2] Try turning debug symbols off for msvc-14.0. So we don't run out of disk space on CI. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2abccdcf6a..388a06c0b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -214,7 +214,7 @@ jobs: run: config_info_travis working-directory: ../boost-root/libs/config/test - name: Test - run: ..\..\..\b2 --hash %ARGS% define=CI_SUPPRESS_KNOWN_ISSUES ${{ matrix.suite }} + run: ..\..\..\b2 --hash %ARGS% define=CI_SUPPRESS_KNOWN_ISSUES debug-symbols=off ${{ matrix.suite }} working-directory: ../boost-root/libs/math/test windows_gcc: runs-on: windows-2019