cardinal_cubic_b_spline_test.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. // Copyright Nick Thompson, 2017
  2. // Use, modification and distribution are subject to the
  3. // Boost Software License, Version 1.0.
  4. // (See accompanying file LICENSE_1_0.txt
  5. // or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. #define BOOST_TEST_MODULE test_cubic_b_spline
  7. #include <random>
  8. #include <functional>
  9. #include <boost/random/uniform_real_distribution.hpp>
  10. #include <boost/type_index.hpp>
  11. #include <boost/test/included/unit_test.hpp>
  12. #include <boost/test/tools/floating_point_comparison.hpp>
  13. #include <boost/math/interpolators/cardinal_cubic_b_spline.hpp>
  14. #include <boost/math/interpolators/detail/cardinal_cubic_b_spline_detail.hpp>
  15. #include <boost/multiprecision/cpp_bin_float.hpp>
  16. using boost::multiprecision::cpp_bin_float_50;
  17. using boost::math::constants::third;
  18. using boost::math::constants::half;
  19. template<class Real>
  20. void test_b3_spline()
  21. {
  22. std::cout << "Testing evaluation of spline basis functions on type " << boost::typeindex::type_id<Real>().pretty_name() << "\n";
  23. // Outside the support:
  24. Real eps = std::numeric_limits<Real>::epsilon();
  25. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline<Real>(2.5), (Real) 0);
  26. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline<Real>(-2.5), (Real) 0);
  27. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline_prime<Real>(2.5), (Real) 0);
  28. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline_prime<Real>(-2.5), (Real) 0);
  29. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline_double_prime<Real>(2.5), (Real) 0);
  30. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline_double_prime<Real>(-2.5), (Real) 0);
  31. // On the boundary of support:
  32. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline<Real>(2), (Real) 0);
  33. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline<Real>(-2), (Real) 0);
  34. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline_prime<Real>(2), (Real) 0);
  35. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline_prime<Real>(-2), (Real) 0);
  36. // Special values:
  37. BOOST_CHECK_CLOSE(boost::math::interpolators::detail::b3_spline<Real>(-1), third<Real>()*half<Real>(), eps);
  38. BOOST_CHECK_CLOSE(boost::math::interpolators::detail::b3_spline<Real>( 1), third<Real>()*half<Real>(), eps);
  39. BOOST_CHECK_CLOSE(boost::math::interpolators::detail::b3_spline<Real>(0), 2*third<Real>(), eps);
  40. BOOST_CHECK_CLOSE(boost::math::interpolators::detail::b3_spline_prime<Real>(-1), half<Real>(), eps);
  41. BOOST_CHECK_CLOSE(boost::math::interpolators::detail::b3_spline_prime<Real>( 1), -half<Real>(), eps);
  42. BOOST_CHECK_SMALL(boost::math::interpolators::detail::b3_spline_prime<Real>(0), eps);
  43. // Properties: B3 is an even function, B3' is an odd function.
  44. for (size_t i = 1; i < 200; ++i)
  45. {
  46. Real arg = i*0.01;
  47. BOOST_CHECK_CLOSE(boost::math::interpolators::detail::b3_spline<Real>(arg), boost::math::interpolators::detail::b3_spline<Real>(arg), eps);
  48. BOOST_CHECK_CLOSE(boost::math::interpolators::detail::b3_spline_prime<Real>(-arg), -boost::math::interpolators::detail::b3_spline_prime<Real>(arg), eps);
  49. BOOST_CHECK_CLOSE(boost::math::interpolators::detail::b3_spline_double_prime<Real>(-arg), boost::math::interpolators::detail::b3_spline_double_prime<Real>(arg), eps);
  50. }
  51. }
  52. /*
  53. * This test ensures that the interpolant s(x_j) = f(x_j) at all grid points.
  54. */
  55. template<class Real>
  56. void test_interpolation_condition()
  57. {
  58. using std::sqrt;
  59. std::cout << "Testing interpolation condition for cubic b splines on type " << boost::typeindex::type_id<Real>().pretty_name() << "\n";
  60. std::random_device rd;
  61. std::mt19937 gen(rd());
  62. boost::random::uniform_real_distribution<Real> dis(1, 10);
  63. std::vector<Real> v(5000);
  64. for (size_t i = 0; i < v.size(); ++i)
  65. {
  66. v[i] = dis(gen);
  67. }
  68. Real step = 0.01;
  69. Real a = 5;
  70. boost::math::interpolators::cardinal_cubic_b_spline<Real> spline(v.data(), v.size(), a, step);
  71. for (size_t i = 0; i < v.size(); ++i)
  72. {
  73. Real y = spline(i*step + a);
  74. // This seems like a very large tolerance, but I don't know of any other interpolators
  75. // that will be able to do much better on random data.
  76. BOOST_CHECK_CLOSE(y, v[i], 10000*sqrt(std::numeric_limits<Real>::epsilon()));
  77. }
  78. }
  79. template<class Real>
  80. void test_constant_function()
  81. {
  82. std::cout << "Testing that constants are interpolated correctly by cubic b splines on type " << boost::typeindex::type_id<Real>().pretty_name() << "\n";
  83. std::vector<Real> v(500);
  84. Real constant = 50.2;
  85. for (size_t i = 0; i < v.size(); ++i)
  86. {
  87. v[i] = 50.2;
  88. }
  89. Real step = 0.02;
  90. Real a = 5;
  91. boost::math::interpolators::cardinal_cubic_b_spline<Real> spline(v.data(), v.size(), a, step);
  92. for (size_t i = 0; i < v.size(); ++i)
  93. {
  94. // Do not test at interpolation point; we already know it works there:
  95. Real y = spline(i*step + a + 0.001);
  96. BOOST_CHECK_CLOSE(y, constant, 10*std::numeric_limits<Real>::epsilon());
  97. Real y_prime = spline.prime(i*step + a + 0.002);
  98. BOOST_CHECK_SMALL(y_prime, 5000*std::numeric_limits<Real>::epsilon());
  99. Real y_double_prime = spline.double_prime(i*step + a + 0.002);
  100. BOOST_CHECK_SMALL(y_double_prime, 5000*std::numeric_limits<Real>::epsilon());
  101. }
  102. // Test that correctly specified left and right-derivatives work properly:
  103. spline = boost::math::interpolators::cardinal_cubic_b_spline<Real>(v.data(), v.size(), a, step, 0, 0);
  104. for (size_t i = 0; i < v.size(); ++i)
  105. {
  106. Real y = spline(i*step + a + 0.002);
  107. BOOST_CHECK_CLOSE(y, constant, std::numeric_limits<Real>::epsilon());
  108. Real y_prime = spline.prime(i*step + a + 0.002);
  109. BOOST_CHECK_SMALL(y_prime, std::numeric_limits<Real>::epsilon());
  110. }
  111. //
  112. // Again with iterator constructor:
  113. //
  114. boost::math::interpolators::cardinal_cubic_b_spline<Real> spline2(v.begin(), v.end(), a, step);
  115. for (size_t i = 0; i < v.size(); ++i)
  116. {
  117. // Do not test at interpolation point; we already know it works there:
  118. Real y = spline2(i*step + a + 0.001);
  119. BOOST_CHECK_CLOSE(y, constant, 10 * std::numeric_limits<Real>::epsilon());
  120. Real y_prime = spline2.prime(i*step + a + 0.002);
  121. BOOST_CHECK_SMALL(y_prime, 5000 * std::numeric_limits<Real>::epsilon());
  122. }
  123. // Test that correctly specified left and right-derivatives work properly:
  124. spline2 = boost::math::interpolators::cardinal_cubic_b_spline<Real>(v.begin(), v.end(), a, step, 0, 0);
  125. for (size_t i = 0; i < v.size(); ++i)
  126. {
  127. Real y = spline2(i*step + a + 0.002);
  128. BOOST_CHECK_CLOSE(y, constant, std::numeric_limits<Real>::epsilon());
  129. Real y_prime = spline2.prime(i*step + a + 0.002);
  130. BOOST_CHECK_SMALL(y_prime, std::numeric_limits<Real>::epsilon());
  131. }
  132. }
  133. template<class Real>
  134. void test_affine_function()
  135. {
  136. using std::sqrt;
  137. std::cout << "Testing that affine functions are interpolated correctly by cubic b splines on type " << boost::typeindex::type_id<Real>().pretty_name() << "\n";
  138. std::vector<Real> v(500);
  139. Real a = 10;
  140. Real b = 8;
  141. Real step = 0.005;
  142. auto f = [a, b](Real x) { return a*x + b; };
  143. for (size_t i = 0; i < v.size(); ++i)
  144. {
  145. v[i] = f(i*step);
  146. }
  147. boost::math::interpolators::cardinal_cubic_b_spline<Real> spline(v.data(), v.size(), 0, step);
  148. for (size_t i = 0; i < v.size() - 1; ++i)
  149. {
  150. Real arg = i*step + 0.0001;
  151. Real y = spline(arg);
  152. BOOST_CHECK_CLOSE(y, f(arg), sqrt(std::numeric_limits<Real>::epsilon()));
  153. Real y_prime = spline.prime(arg);
  154. BOOST_CHECK_CLOSE(y_prime, a, 100*sqrt(std::numeric_limits<Real>::epsilon()));
  155. }
  156. // Test that correctly specified left and right-derivatives work properly:
  157. spline = boost::math::interpolators::cardinal_cubic_b_spline<Real>(v.data(), v.size(), 0, step, a, a);
  158. for (size_t i = 0; i < v.size() - 1; ++i)
  159. {
  160. Real arg = i*step + 0.0001;
  161. Real y = spline(arg);
  162. BOOST_CHECK_CLOSE(y, f(arg), sqrt(std::numeric_limits<Real>::epsilon()));
  163. Real y_prime = spline.prime(arg);
  164. BOOST_CHECK_CLOSE(y_prime, a, 100*sqrt(std::numeric_limits<Real>::epsilon()));
  165. }
  166. }
  167. template<class Real>
  168. void test_quadratic_function()
  169. {
  170. using std::sqrt;
  171. std::cout << "Testing that quadratic functions are interpolated correctly by cubic b splines on type " << boost::typeindex::type_id<Real>().pretty_name() << "\n";
  172. std::vector<Real> v(500);
  173. Real a = 1.2;
  174. Real b = -3.4;
  175. Real c = -8.6;
  176. Real step = 0.01;
  177. auto f = [a, b, c](Real x) { return a*x*x + b*x + c; };
  178. for (size_t i = 0; i < v.size(); ++i)
  179. {
  180. v[i] = f(i*step);
  181. }
  182. boost::math::interpolators::cardinal_cubic_b_spline<Real> spline(v.data(), v.size(), 0, step);
  183. for (size_t i = 0; i < v.size() -1; ++i)
  184. {
  185. Real arg = i*step + 0.001;
  186. Real y = spline(arg);
  187. BOOST_CHECK_CLOSE(y, f(arg), 0.1);
  188. Real y_prime = spline.prime(arg);
  189. BOOST_CHECK_CLOSE(y_prime, 2*a*arg + b, 2.0);
  190. }
  191. }
  192. template<class Real>
  193. void test_trig_function()
  194. {
  195. std::cout << "Testing that sine functions are interpolated correctly by cubic b splines on type " << boost::typeindex::type_id<Real>().pretty_name() << "\n";
  196. std::mt19937 gen;
  197. std::vector<Real> v(500);
  198. Real x0 = 1;
  199. Real step = 0.125;
  200. for (size_t i = 0; i < v.size(); ++i)
  201. {
  202. v[i] = sin(x0 + step * i);
  203. }
  204. boost::math::interpolators::cardinal_cubic_b_spline<Real> spline(v.data(), v.size(), x0, step);
  205. boost::random::uniform_real_distribution<Real> absissa(x0, x0 + 499 * step);
  206. for (size_t i = 0; i < v.size(); ++i)
  207. {
  208. Real x = absissa(gen);
  209. Real y = spline(x);
  210. BOOST_CHECK_CLOSE(y, sin(x), 1.0);
  211. auto y_prime = spline.prime(x);
  212. BOOST_CHECK_CLOSE(y_prime, cos(x), 2.0);
  213. }
  214. }
  215. template<class Real>
  216. void test_copy_move()
  217. {
  218. std::cout << "Testing that copy/move operation succeed on cubic b spline\n";
  219. std::vector<Real> v(500);
  220. Real x0 = 1;
  221. Real step = 0.125;
  222. for (size_t i = 0; i < v.size(); ++i)
  223. {
  224. v[i] = sin(x0 + step * i);
  225. }
  226. boost::math::interpolators::cardinal_cubic_b_spline<Real> spline(v.data(), v.size(), x0, step);
  227. // Default constructor should compile so that splines can be member variables:
  228. boost::math::interpolators::cardinal_cubic_b_spline<Real> d;
  229. d = boost::math::interpolators::cardinal_cubic_b_spline<Real>(v.data(), v.size(), x0, step);
  230. BOOST_CHECK_CLOSE(d(x0), sin(x0), 0.01);
  231. // Passing to lambda should compile:
  232. auto f = [=](Real x) { return d(x); };
  233. // Make sure this variable is used.
  234. BOOST_CHECK_CLOSE(f(x0), sin(x0), 0.01);
  235. // Move operations should compile.
  236. auto s = std::move(spline);
  237. // Copy operations should compile:
  238. boost::math::interpolators::cardinal_cubic_b_spline<Real> c = d;
  239. BOOST_CHECK_CLOSE(c(x0), sin(x0), 0.01);
  240. // Test with std::bind:
  241. auto h = std::bind(&boost::math::interpolators::cardinal_cubic_b_spline<double>::operator(), &s, std::placeholders::_1);
  242. BOOST_CHECK_CLOSE(h(x0), sin(x0), 0.01);
  243. }
  244. template<class Real>
  245. void test_outside_interval()
  246. {
  247. std::cout << "Testing that the spline can be evaluated outside the interpolation interval\n";
  248. std::vector<Real> v(400);
  249. Real x0 = 1;
  250. Real step = 0.125;
  251. for (size_t i = 0; i < v.size(); ++i)
  252. {
  253. v[i] = sin(x0 + step * i);
  254. }
  255. boost::math::interpolators::cardinal_cubic_b_spline<Real> spline(v.data(), v.size(), x0, step);
  256. // There's no test here; it simply does it's best to be an extrapolator.
  257. //
  258. std::ostream cnull(0);
  259. cnull << spline(0);
  260. cnull << spline(2000);
  261. }
  262. BOOST_AUTO_TEST_CASE(test_cubic_b_spline)
  263. {
  264. test_b3_spline<float>();
  265. test_b3_spline<double>();
  266. test_b3_spline<long double>();
  267. test_b3_spline<cpp_bin_float_50>();
  268. test_interpolation_condition<float>();
  269. test_interpolation_condition<double>();
  270. test_interpolation_condition<long double>();
  271. test_interpolation_condition<cpp_bin_float_50>();
  272. test_constant_function<float>();
  273. test_constant_function<double>();
  274. test_constant_function<long double>();
  275. test_constant_function<cpp_bin_float_50>();
  276. test_affine_function<float>();
  277. test_affine_function<double>();
  278. test_affine_function<long double>();
  279. test_affine_function<cpp_bin_float_50>();
  280. test_quadratic_function<float>();
  281. test_quadratic_function<double>();
  282. test_quadratic_function<long double>();
  283. test_affine_function<cpp_bin_float_50>();
  284. test_trig_function<float>();
  285. test_trig_function<double>();
  286. test_trig_function<long double>();
  287. test_trig_function<cpp_bin_float_50>();
  288. test_copy_move<double>();
  289. test_outside_interval<double>();
  290. }