test_numerical_differentiation.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. // (C) Copyright Nick Thompson, 2018
  2. // Use, modification and distribution are subject to the
  3. // Boost Software License, Version 1.0. (See accompanying file
  4. // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  5. #define BOOST_TEST_MODULE numerical_differentiation_test
  6. #include <cmath>
  7. #include <limits>
  8. #include <iostream>
  9. #include <boost/type_index.hpp>
  10. #include <boost/test/included/unit_test.hpp>
  11. #include <boost/test/tools/floating_point_comparison.hpp>
  12. #include <boost/math/special_functions/bessel.hpp>
  13. #include <boost/math/special_functions/bessel_prime.hpp>
  14. #include <boost/math/special_functions/next.hpp>
  15. #include <boost/math/differentiation/finite_difference.hpp>
  16. using std::abs;
  17. using std::pow;
  18. using boost::math::differentiation::finite_difference_derivative;
  19. using boost::math::differentiation::complex_step_derivative;
  20. using boost::math::cyl_bessel_j;
  21. using boost::math::cyl_bessel_j_prime;
  22. using boost::math::constants::half;
  23. template<class Real, size_t order>
  24. void test_order(size_t points_to_test)
  25. {
  26. std::cout << "Testing order " << order << " derivative error estimate on type " << boost::typeindex::type_id<Real>().pretty_name() << "\n";
  27. std::cout << std::setprecision(std::numeric_limits<Real>::digits10);
  28. //std::cout << std::fixed << std::scientific;
  29. auto f = [](Real t) { return boost::math::cyl_bessel_j<Real>(1, t); };
  30. Real min = -100000.0;
  31. Real max = -min;
  32. Real x = min;
  33. Real max_error = 0;
  34. Real max_relative_error_in_error = 0;
  35. size_t j = 0;
  36. size_t failures = 0;
  37. while (j < points_to_test)
  38. {
  39. x = min + (Real) 2*j*max/ (Real) points_to_test;
  40. Real error_estimate;
  41. Real computed = finite_difference_derivative<decltype(f), Real, order>(f, x, &error_estimate);
  42. Real expected = (Real) cyl_bessel_j_prime<Real>(1, x);
  43. Real error = abs(computed - expected);
  44. // The error estimate is provided under the assumption that the function is evaluated to 1 ULP.
  45. // Presumably no one will be too offended by this estimate being off by a factor of 2 or so.
  46. if (error > 2*error_estimate)
  47. {
  48. ++failures;
  49. Real relative_error_in_error = abs(error - error_estimate)/ error;
  50. if (relative_error_in_error > max_relative_error_in_error)
  51. {
  52. max_relative_error_in_error = relative_error_in_error;
  53. }
  54. if (relative_error_in_error > 2)
  55. {
  56. throw std::logic_error("Relative error in error is too high!");
  57. }
  58. }
  59. if (error > max_error)
  60. {
  61. max_error = error;
  62. }
  63. ++j;
  64. }
  65. //std::cout << "Maximum error :" << max_error << "\n";
  66. //std::cout << "Error estimate failed " << failures << " times out of " << points_to_test << "\n";
  67. //std::cout << "Failure rate: " << (double) failures / (double) points_to_test << "\n";
  68. //std::cout << "Maximum error in estimated error = " << max_relative_error_in_error << "\n";
  69. //Real convergence_rate = (Real) order/ (Real) (order + 1);
  70. //std::cout << "eps^(order/order+1) = " << pow(std::numeric_limits<Real>::epsilon(), convergence_rate) << "\n\n\n";
  71. bool max_error_good = max_error < 2*sqrt(std::numeric_limits<Real>::epsilon());
  72. BOOST_TEST(max_error_good);
  73. bool error_estimate_good = max_relative_error_in_error < (Real) 2;
  74. BOOST_TEST(error_estimate_good);
  75. double failure_rate = (double) failures / (double) points_to_test;
  76. BOOST_CHECK_SMALL(failure_rate, 0.05);
  77. }
  78. template<class Real>
  79. void test_bessel()
  80. {
  81. std::cout << "Testing numerical derivatives of Bessel's function on type " << boost::typeindex::type_id<Real>().pretty_name() << "\n";
  82. std::cout << std::setprecision(std::numeric_limits<Real>::digits10);
  83. Real eps = std::numeric_limits<Real>::epsilon();
  84. Real x = static_cast<Real>(25.1);
  85. auto f = [](Real t) { return boost::math::cyl_bessel_j(12, t); };
  86. Real computed = finite_difference_derivative<decltype(f), Real, 1>(f, x);
  87. Real expected = cyl_bessel_j_prime(12, x);
  88. Real error_estimate = 4*abs(f(x))*sqrt(eps);
  89. //std::cout << std::setprecision(std::numeric_limits<Real>::digits10);
  90. //std::cout << "cyl_bessel_j_prime: " << expected << std::endl;
  91. //std::cout << "First order fd : " << computed << std::endl;
  92. //std::cout << "Error : " << abs(computed - expected) << std::endl;
  93. //std::cout << "a prior error est : " << error_estimate << std::endl;
  94. BOOST_CHECK_CLOSE_FRACTION(expected, computed, 10*error_estimate);
  95. computed = finite_difference_derivative<decltype(f), Real, 2>(f, x);
  96. expected = cyl_bessel_j_prime(12, x);
  97. error_estimate = abs(f(x))*pow(eps, boost::math::constants::two_thirds<Real>());
  98. //std::cout << std::setprecision(std::numeric_limits<Real>::digits10);
  99. //std::cout << "cyl_bessel_j_prime: " << expected << std::endl;
  100. //std::cout << "Second order fd : " << computed << std::endl;
  101. //std::cout << "Error : " << abs(computed - expected) << std::endl;
  102. //std::cout << "a prior error est : " << error_estimate << std::endl;
  103. BOOST_CHECK_CLOSE_FRACTION(expected, computed, 50*error_estimate);
  104. computed = finite_difference_derivative<decltype(f), Real, 4>(f, x);
  105. expected = cyl_bessel_j_prime(12, x);
  106. error_estimate = abs(f(x))*pow(eps, (Real) 4 / (Real) 5);
  107. //std::cout << std::setprecision(std::numeric_limits<Real>::digits10);
  108. //std::cout << "cyl_bessel_j_prime: " << expected << std::endl;
  109. //std::cout << "Fourth order fd : " << computed << std::endl;
  110. //std::cout << "Error : " << abs(computed - expected) << std::endl;
  111. //std::cout << "a prior error est : " << error_estimate << std::endl;
  112. BOOST_CHECK_CLOSE_FRACTION(expected, computed, 25*error_estimate);
  113. computed = finite_difference_derivative<decltype(f), Real, 6>(f, x);
  114. expected = cyl_bessel_j_prime(12, x);
  115. error_estimate = abs(f(x))*pow(eps, (Real) 6/ (Real) 7);
  116. //std::cout << std::setprecision(std::numeric_limits<Real>::digits10);
  117. //std::cout << "cyl_bessel_j_prime: " << expected << std::endl;
  118. //std::cout << "Sixth order fd : " << computed << std::endl;
  119. //std::cout << "Error : " << abs(computed - expected) << std::endl;
  120. //std::cout << "a prior error est : " << error_estimate << std::endl;
  121. BOOST_CHECK_CLOSE_FRACTION(expected, computed, 100*error_estimate);
  122. computed = finite_difference_derivative<decltype(f), Real, 8>(f, x);
  123. expected = cyl_bessel_j_prime(12, x);
  124. error_estimate = abs(f(x))*pow(eps, (Real) 8/ (Real) 9);
  125. //std::cout << std::setprecision(std::numeric_limits<Real>::digits10);
  126. //std::cout << "cyl_bessel_j_prime: " << expected << std::endl;
  127. //std::cout << "Eighth order fd : " << computed << std::endl;
  128. //std::cout << "Error : " << abs(computed - expected) << std::endl;
  129. //std::cout << "a prior error est : " << error_estimate << std::endl;
  130. BOOST_CHECK_CLOSE_FRACTION(expected, computed, 25*error_estimate);
  131. }
  132. // Example of a function which is subject to catastrophic cancellation using finite-differences, but is almost perfectly stable using complex step:
  133. template<class RealOrComplex>
  134. RealOrComplex moler_example(RealOrComplex x)
  135. {
  136. using std::sin;
  137. using std::cos;
  138. using std::exp;
  139. RealOrComplex cosx = cos(x);
  140. RealOrComplex sinx = sin(x);
  141. return exp(x)/(cosx*cosx*cosx + sinx*sinx*sinx);
  142. }
  143. template<class RealOrComplex>
  144. RealOrComplex moler_example_derivative(RealOrComplex x)
  145. {
  146. using std::sin;
  147. using std::cos;
  148. using std::exp;
  149. RealOrComplex expx = exp(x);
  150. RealOrComplex cosx = cos(x);
  151. RealOrComplex sinx = sin(x);
  152. RealOrComplex coscubed_sincubed = cosx*cosx*cosx + sinx*sinx*sinx;
  153. return (expx/coscubed_sincubed)*(1 - 3*(sinx*sinx*cosx - sinx*cosx*cosx)/ (coscubed_sincubed));
  154. }
  155. template<class Real>
  156. void test_complex_step()
  157. {
  158. using std::abs;
  159. using std::complex;
  160. using std::isfinite;
  161. using std::isnormal;
  162. std::cout << "Testing numerical derivatives of Bessel's function on type " << boost::typeindex::type_id<Real>().pretty_name() << "\n";
  163. std::cout << std::setprecision(std::numeric_limits<Real>::digits10);
  164. Real x = -100;
  165. while ( x < 100 )
  166. {
  167. if (!isfinite(moler_example(x)))
  168. {
  169. x += 1;
  170. continue;
  171. }
  172. Real expected = moler_example_derivative<Real>(x);
  173. Real computed = complex_step_derivative(moler_example<complex<Real>>, x);
  174. if (!isfinite(expected))
  175. {
  176. x += 1;
  177. continue;
  178. }
  179. if (abs(expected) <= std::numeric_limits<Real>::epsilon())
  180. {
  181. bool issmall = computed < std::numeric_limits<Real>::epsilon();
  182. BOOST_TEST(issmall);
  183. }
  184. else
  185. {
  186. BOOST_CHECK_CLOSE_FRACTION(expected, computed, 200*std::numeric_limits<Real>::epsilon());
  187. }
  188. x += 1;
  189. }
  190. }
  191. BOOST_AUTO_TEST_CASE(numerical_differentiation_test)
  192. {
  193. test_complex_step<float>();
  194. test_complex_step<double>();
  195. test_bessel<float>();
  196. test_bessel<double>();
  197. size_t points_to_test = 1000;
  198. test_order<float, 1>(points_to_test);
  199. test_order<double, 1>(points_to_test);
  200. test_order<float, 2>(points_to_test);
  201. test_order<double, 2>(points_to_test);
  202. test_order<float, 4>(points_to_test);
  203. test_order<double, 4>(points_to_test);
  204. test_order<float, 6>(points_to_test);
  205. test_order<double, 6>(points_to_test);
  206. test_order<float, 8>(points_to_test);
  207. test_order<double, 8>(points_to_test);
  208. }