// (C) Copyright John Maddock 2006. // (C) Copyright Matt Borland 2024. // Use, modification and distribution are subject to the // 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_TOOLS_TEST_HPP #define BOOST_MATH_TOOLS_TEST_HPP #ifdef _MSC_VER #pragma once #endif #include #include #include #include #include #include #include #include #include namespace boost{ namespace math{ namespace tools{ template struct test_result { private: boost::math::tools::stats stat; // Statistics for the test. unsigned worst_case; // Index of the worst case test. public: test_result() { worst_case = 0; } void set_worst(int i){ worst_case = i; } void add(const T& point){ stat.add(point); } // accessors: unsigned worst()const{ return worst_case; } T min BOOST_MATH_PREVENT_MACRO_SUBSTITUTION()const{ return (stat.min)(); } T max BOOST_MATH_PREVENT_MACRO_SUBSTITUTION()const{ return (stat.max)(); } T total()const{ return stat.total(); } T mean()const{ return stat.mean(); } std::uintmax_t count()const{ return stat.count(); } T variance()const{ return stat.variance(); } T variance1()const{ return stat.variance1(); } T rms()const{ return stat.rms(); } test_result& operator+=(const test_result& t) { if((t.stat.max)() > (stat.max)()) worst_case = t.worst_case; stat += t.stat; return *this; } }; template struct calculate_result_type { typedef typename T::value_type row_type; typedef typename row_type::value_type value_type; }; template T relative_error(T a, T b) { return boost::math::relative_difference(a, b); } template void set_output_precision(T, std::ostream& os) { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4127) #endif if(std::numeric_limits::digits10) { os << std::setprecision(std::numeric_limits::digits10 + 2); } else os << std::setprecision(22); // and hope for the best! #ifdef _MSC_VER #pragma warning(pop) #endif } template void print_row(const Seq& row, std::ostream& os = std::cout) { try { set_output_precision(row[0], os); for (unsigned i = 0; i < row.size(); ++i) { if (i) os << ", "; os << row[i]; } os << std::endl; } catch (const std::exception&) {} } // // Function test accepts an matrix of input values (probably a 2D std::array) // and calls two functors for each row in the array - one calculates a value // to test, and one extracts the expected value from the array (or possibly // calculates it at high precision). The two functors are usually simple lambda // expressions. // template test_result::value_type> test(const A& a, F1 test_func, F2 expect_func) { typedef typename A::value_type row_type; typedef typename row_type::value_type value_type; test_result result; for(unsigned i = 0; i < a.size(); ++i) { const row_type& row = a[i]; value_type point; #ifndef BOOST_NO_EXCEPTIONS try { #endif point = test_func(row); #ifndef BOOST_NO_EXCEPTIONS } catch(const std::underflow_error&) { point = 0; } catch(const std::overflow_error&) { point = std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : tools::max_value(); } catch(const std::exception& e) { std::cerr << e.what() << std::endl; print_row(row, std::cerr); BOOST_ERROR("Unexpected exception."); // so we don't get further errors: point = expect_func(row); } #endif value_type expected = expect_func(row); value_type err = relative_error(point, expected); #ifdef BOOST_INSTRUMENT if(err != 0) { std::cout << row[0] << " " << err; if(std::numeric_limits::is_specialized) { std::cout << " (" << err / std::numeric_limits::epsilon() << "eps)"; } std::cout << std::endl; } #endif if(!(boost::math::isfinite)(point) && (boost::math::isfinite)(expected)) { std::cerr << "CAUTION: Found non-finite result, when a finite value was expected at entry " << i << "\n"; std::cerr << "Found: " << point << " Expected " << expected << " Error: " << err << std::endl; print_row(row, std::cerr); BOOST_ERROR("Unexpected non-finite result"); } if(err > 0.5f) { std::cerr << "CAUTION: Gross error found at entry " << i << ".\n"; std::cerr << "Found: " << point << " Expected " << expected << " Error: " << err << std::endl; print_row(row, std::cerr); BOOST_ERROR("Gross error"); } result.add(err); if((result.max)() == err) result.set_worst(i); } return result; } template test_result test_hetero(const A& a, F1 test_func, F2 expect_func) { typedef typename A::value_type row_type; typedef Real value_type; test_result result; for(unsigned i = 0; i < a.size(); ++i) { const row_type& row = a[i]; value_type point; #ifndef BOOST_NO_EXCEPTIONS try { #endif point = test_func(row); #ifndef BOOST_NO_EXCEPTIONS } catch(const std::underflow_error&) { point = 0; } catch(const std::overflow_error&) { point = std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : tools::max_value(); } catch(const std::exception& e) { std::cerr << "Unexpected exception at entry: " << i << "\n"; std::cerr << e.what() << std::endl; print_row(row, std::cerr); BOOST_ERROR("Unexpected exception."); // so we don't get further errors: point = expect_func(row); } #endif value_type expected = expect_func(row); value_type err = relative_error(point, expected); #ifdef BOOST_INSTRUMENT if(err != 0) { std::cout << row[0] << " " << err; if(std::numeric_limits::is_specialized) { std::cout << " (" << err / std::numeric_limits::epsilon() << "eps)"; } std::cout << std::endl; } #endif if(!(boost::math::isfinite)(point) && (boost::math::isfinite)(expected)) { std::cerr << "CAUTION: Found non-finite result, when a finite value was expected at entry " << i << "\n"; std::cerr << "Found: " << point << " Expected " << expected << " Error: " << err << std::endl; print_row(row, std::cerr); BOOST_ERROR("Unexpected non-finite result"); } if(err > 0.5f) { std::cerr << "CAUTION: Gross error found at entry " << i << ".\n"; std::cerr << "Found: " << point << " Expected " << expected << " Error: " << err << std::endl; print_row(row, std::cerr); BOOST_ERROR("Gross error"); } result.add(err); if((result.max)() == err) result.set_worst(i); } return result; } #ifndef BOOST_MATH_NO_EXCEPTIONS template void test_check_throw(Val, Exception) { BOOST_CHECK(errno); errno = 0; } template void test_check_throw(Val val, std::domain_error const*) { BOOST_CHECK(errno == EDOM); errno = 0; if(std::numeric_limits::has_quiet_NaN) { BOOST_CHECK((boost::math::isnan)(val)); } } template void test_check_throw(Val v, std::overflow_error const*) { BOOST_CHECK(errno == ERANGE); errno = 0; BOOST_CHECK((v >= boost::math::tools::max_value()) || (v <= -boost::math::tools::max_value())); } template void test_check_throw(Val v, boost::math::rounding_error const*) { BOOST_CHECK(errno == ERANGE); errno = 0; if(std::numeric_limits::is_specialized && std::numeric_limits::is_integer) { BOOST_CHECK((v == (std::numeric_limits::max)()) || (v == (std::numeric_limits::min)())); } else { BOOST_CHECK((v == boost::math::tools::max_value()) || (v == -boost::math::tools::max_value())); } } #endif } // namespace tools } // namespace math } // namespace boost // // exception-free testing support, ideally we'd only define this in our tests, // but to keep things simple we really need it somewhere that's always included: // #if defined(BOOST_MATH_NO_EXCEPTIONS) && defined(BOOST_MATH_HAS_GPU_SUPPORT) # define BOOST_MATH_CHECK_THROW(x, y) #elif defined(BOOST_MATH_NO_EXCEPTIONS) # define BOOST_MATH_CHECK_THROW(x, ExceptionType) boost::math::tools::test_check_throw(x, static_cast(nullptr)); #else # define BOOST_MATH_CHECK_THROW(x, y) BOOST_CHECK_THROW(x, y) #endif #endif