| // (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 <boost/math/tools/config.hpp> |
| #include <boost/math/tools/stats.hpp> |
| #include <boost/math/special_functions/fpclassify.hpp> |
| #include <boost/math/special_functions/relative_difference.hpp> |
| #include <boost/math/policies/error_handling.hpp> |
| #include <boost/test/test_tools.hpp> |
| #include <stdexcept> |
| #include <iostream> |
| #include <iomanip> |
| |
| namespace boost{ namespace math{ namespace tools{ |
| |
| template <class T> |
| struct test_result |
| { |
| private: |
| boost::math::tools::stats<T> 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 <class T> |
| struct calculate_result_type |
| { |
| typedef typename T::value_type row_type; |
| typedef typename row_type::value_type value_type; |
| }; |
| |
| template <class T> |
| T relative_error(T a, T b) |
| { |
| return boost::math::relative_difference(a, b); |
| } |
| |
| |
| template <class T> |
| void set_output_precision(T, std::ostream& os) |
| { |
| #ifdef _MSC_VER |
| #pragma warning(push) |
| #pragma warning(disable:4127) |
| #endif |
| if(std::numeric_limits<T>::digits10) |
| { |
| os << std::setprecision(std::numeric_limits<T>::digits10 + 2); |
| } |
| else |
| os << std::setprecision(22); // and hope for the best! |
| |
| #ifdef _MSC_VER |
| #pragma warning(pop) |
| #endif |
| } |
| |
| template <class Seq> |
| 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 <class A, class F1, class F2> |
| test_result<typename calculate_result_type<A>::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<value_type> 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<value_type>::has_infinity ? |
| std::numeric_limits<value_type>::infinity() |
| : tools::max_value<value_type>(); |
| } |
| 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<value_type>::is_specialized) |
| { |
| std::cout << " (" << err / std::numeric_limits<value_type>::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 <class Real, class A, class F1, class F2> |
| test_result<Real> test_hetero(const A& a, F1 test_func, F2 expect_func) |
| { |
| typedef typename A::value_type row_type; |
| typedef Real value_type; |
| |
| test_result<value_type> 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<value_type>::has_infinity ? |
| std::numeric_limits<value_type>::infinity() |
| : tools::max_value<value_type>(); |
| } |
| 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<value_type>::is_specialized) |
| { |
| std::cout << " (" << err / std::numeric_limits<value_type>::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 <class Val, class Exception> |
| void test_check_throw(Val, Exception) |
| { |
| BOOST_CHECK(errno); |
| errno = 0; |
| } |
| |
| template <class Val> |
| void test_check_throw(Val val, std::domain_error const*) |
| { |
| BOOST_CHECK(errno == EDOM); |
| errno = 0; |
| if(std::numeric_limits<Val>::has_quiet_NaN) |
| { |
| BOOST_CHECK((boost::math::isnan)(val)); |
| } |
| } |
| |
| template <class Val> |
| void test_check_throw(Val v, std::overflow_error const*) |
| { |
| BOOST_CHECK(errno == ERANGE); |
| errno = 0; |
| BOOST_CHECK((v >= boost::math::tools::max_value<Val>()) || (v <= -boost::math::tools::max_value<Val>())); |
| } |
| |
| template <class Val> |
| void test_check_throw(Val v, boost::math::rounding_error const*) |
| { |
| BOOST_CHECK(errno == ERANGE); |
| errno = 0; |
| if(std::numeric_limits<Val>::is_specialized && std::numeric_limits<Val>::is_integer) |
| { |
| BOOST_CHECK((v == (std::numeric_limits<Val>::max)()) || (v == (std::numeric_limits<Val>::min)())); |
| } |
| else |
| { |
| BOOST_CHECK((v == boost::math::tools::max_value<Val>()) || (v == -boost::math::tools::max_value<Val>())); |
| } |
| } |
| #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<ExceptionType const*>(nullptr)); |
| #else |
| # define BOOST_MATH_CHECK_THROW(x, y) BOOST_CHECK_THROW(x, y) |
| #endif |
| |
| #endif |
| |
| |