cardinal_quintic_b_spline_detail.hpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. // Copyright Nick Thompson, 2019
  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. #ifndef BOOST_MATH_INTERPOLATORS_CARDINAL_QUINTIC_B_SPLINE_DETAIL_HPP
  7. #define BOOST_MATH_INTERPOLATORS_CARDINAL_QUINTIC_B_SPLINE_DETAIL_HPP
  8. #include <cmath>
  9. #include <vector>
  10. #include <utility>
  11. #include <boost/math/special_functions/cardinal_b_spline.hpp>
  12. namespace boost{ namespace math{ namespace interpolators{ namespace detail{
  13. template <class Real>
  14. class cardinal_quintic_b_spline_detail
  15. {
  16. public:
  17. // If you don't know the value of the derivative at the endpoints, leave them as nans and the routine will estimate them.
  18. // y[0] = y(a), y[n -1] = y(b), step_size = (b - a)/(n -1).
  19. cardinal_quintic_b_spline_detail(const Real* const y,
  20. size_t n,
  21. Real t0 /* initial time, left endpoint */,
  22. Real h /*spacing, stepsize*/,
  23. std::pair<Real, Real> left_endpoint_derivatives,
  24. std::pair<Real, Real> right_endpoint_derivatives)
  25. {
  26. static_assert(!std::is_integral<Real>::value, "The quintic B-spline interpolator only works with floating point types.");
  27. if (h <= 0) {
  28. throw std::logic_error("Spacing must be > 0.");
  29. }
  30. m_inv_h = 1/h;
  31. m_t0 = t0;
  32. if (n < 8) {
  33. throw std::logic_error("The quntic B-spline interpolator requires at least 8 points.");
  34. }
  35. using std::isnan;
  36. // This interpolator has error of order h^6, so the derivatives should be estimated with the same error.
  37. // See: https://en.wikipedia.org/wiki/Finite_difference_coefficient
  38. if (isnan(left_endpoint_derivatives.first)) {
  39. Real tmp = -49*y[0]/20 + 6*y[1] - 15*y[2]/2 + 20*y[3]/3 - 15*y[4]/4 + 6*y[5]/5 - y[6]/6;
  40. left_endpoint_derivatives.first = tmp/h;
  41. }
  42. if (isnan(right_endpoint_derivatives.first)) {
  43. Real tmp = 49*y[n-1]/20 - 6*y[n-2] + 15*y[n-3]/2 - 20*y[n-4]/3 + 15*y[n-5]/4 - 6*y[n-6]/5 + y[n-7]/6;
  44. right_endpoint_derivatives.first = tmp/h;
  45. }
  46. if(isnan(left_endpoint_derivatives.second)) {
  47. Real tmp = 469*y[0]/90 - 223*y[1]/10 + 879*y[2]/20 - 949*y[3]/18 + 41*y[4] - 201*y[5]/10 + 1019*y[6]/180 - 7*y[7]/10;
  48. left_endpoint_derivatives.second = tmp/(h*h);
  49. }
  50. if (isnan(right_endpoint_derivatives.second)) {
  51. Real tmp = 469*y[n-1]/90 - 223*y[n-2]/10 + 879*y[n-3]/20 - 949*y[n-4]/18 + 41*y[n-5] - 201*y[n-6]/10 + 1019*y[n-7]/180 - 7*y[n-8]/10;
  52. right_endpoint_derivatives.second = tmp/(h*h);
  53. }
  54. // This is really challenging my mental limits on by-hand row reduction.
  55. // I debated bringing in a dependency on a sparse linear solver, but given that that would cause much agony for users I decided against it.
  56. std::vector<Real> rhs(n+4);
  57. rhs[0] = 20*y[0] - 12*h*left_endpoint_derivatives.first + 2*h*h*left_endpoint_derivatives.second;
  58. rhs[1] = 60*y[0] - 12*h*left_endpoint_derivatives.first;
  59. for (size_t i = 2; i < n + 2; ++i) {
  60. rhs[i] = 120*y[i-2];
  61. }
  62. rhs[n+2] = 60*y[n-1] + 12*h*right_endpoint_derivatives.first;
  63. rhs[n+3] = 20*y[n-1] + 12*h*right_endpoint_derivatives.first + 2*h*h*right_endpoint_derivatives.second;
  64. std::vector<Real> diagonal(n+4, 66);
  65. diagonal[0] = 1;
  66. diagonal[1] = 18;
  67. diagonal[n+2] = 18;
  68. diagonal[n+3] = 1;
  69. std::vector<Real> first_superdiagonal(n+4, 26);
  70. first_superdiagonal[0] = 10;
  71. first_superdiagonal[1] = 33;
  72. first_superdiagonal[n+2] = 1;
  73. // There is one less superdiagonal than diagonal; make sure that if we read it, it shows up as a bug:
  74. first_superdiagonal[n+3] = std::numeric_limits<Real>::quiet_NaN();
  75. std::vector<Real> second_superdiagonal(n+4, 1);
  76. second_superdiagonal[0] = 9;
  77. second_superdiagonal[1] = 8;
  78. second_superdiagonal[n+2] = std::numeric_limits<Real>::quiet_NaN();
  79. second_superdiagonal[n+3] = std::numeric_limits<Real>::quiet_NaN();
  80. std::vector<Real> first_subdiagonal(n+4, 26);
  81. first_subdiagonal[0] = std::numeric_limits<Real>::quiet_NaN();
  82. first_subdiagonal[1] = 1;
  83. first_subdiagonal[n+2] = 33;
  84. first_subdiagonal[n+3] = 10;
  85. std::vector<Real> second_subdiagonal(n+4, 1);
  86. second_subdiagonal[0] = std::numeric_limits<Real>::quiet_NaN();
  87. second_subdiagonal[1] = std::numeric_limits<Real>::quiet_NaN();
  88. second_subdiagonal[n+2] = 8;
  89. second_subdiagonal[n+3] = 9;
  90. for (size_t i = 0; i < n+2; ++i) {
  91. Real di = diagonal[i];
  92. diagonal[i] = 1;
  93. first_superdiagonal[i] /= di;
  94. second_superdiagonal[i] /= di;
  95. rhs[i] /= di;
  96. // Eliminate first subdiagonal:
  97. Real nfsub = -first_subdiagonal[i+1];
  98. // Superfluous:
  99. first_subdiagonal[i+1] /= nfsub;
  100. // Not superfluous:
  101. diagonal[i+1] /= nfsub;
  102. first_superdiagonal[i+1] /= nfsub;
  103. second_superdiagonal[i+1] /= nfsub;
  104. rhs[i+1] /= nfsub;
  105. diagonal[i+1] += first_superdiagonal[i];
  106. first_superdiagonal[i+1] += second_superdiagonal[i];
  107. rhs[i+1] += rhs[i];
  108. // Superfluous, but clarifying:
  109. first_subdiagonal[i+1] = 0;
  110. // Eliminate second subdiagonal:
  111. Real nssub = -second_subdiagonal[i+2];
  112. first_subdiagonal[i+2] /= nssub;
  113. diagonal[i+2] /= nssub;
  114. first_superdiagonal[i+2] /= nssub;
  115. second_superdiagonal[i+2] /= nssub;
  116. rhs[i+2] /= nssub;
  117. first_subdiagonal[i+2] += first_superdiagonal[i];
  118. diagonal[i+2] += second_superdiagonal[i];
  119. rhs[i+2] += rhs[i];
  120. // Superfluous, but clarifying:
  121. second_subdiagonal[i+2] = 0;
  122. }
  123. // Eliminate last subdiagonal:
  124. Real dnp2 = diagonal[n+2];
  125. diagonal[n+2] = 1;
  126. first_superdiagonal[n+2] /= dnp2;
  127. rhs[n+2] /= dnp2;
  128. Real nfsubnp3 = -first_subdiagonal[n+3];
  129. diagonal[n+3] /= nfsubnp3;
  130. rhs[n+3] /= nfsubnp3;
  131. diagonal[n+3] += first_superdiagonal[n+2];
  132. rhs[n+3] += rhs[n+2];
  133. m_alpha.resize(n + 4, std::numeric_limits<Real>::quiet_NaN());
  134. m_alpha[n+3] = rhs[n+3]/diagonal[n+3];
  135. m_alpha[n+2] = rhs[n+2] - first_superdiagonal[n+2]*m_alpha[n+3];
  136. for (int64_t i = int64_t(n+1); i >= 0; --i) {
  137. m_alpha[i] = rhs[i] - first_superdiagonal[i]*m_alpha[i+1] - second_superdiagonal[i]*m_alpha[i+2];
  138. }
  139. }
  140. Real operator()(Real t) const {
  141. using std::ceil;
  142. using std::floor;
  143. using boost::math::cardinal_b_spline;
  144. // tf = t0 + (n-1)*h
  145. // alpha.size() = n+4
  146. if (t < m_t0 || t > m_t0 + (m_alpha.size()-5)/m_inv_h) {
  147. const char* err_msg = "Tried to evaluate the cardinal quintic b-spline outside the domain of of interpolation; extrapolation does not work.";
  148. throw std::domain_error(err_msg);
  149. }
  150. Real x = (t-m_t0)*m_inv_h;
  151. // Support of B_5 is [-3, 3]. So -3 < x - j + 2 < 3, so x-1 < j < x+5.
  152. // TODO: Zero pad m_alpha so that only the domain check is necessary.
  153. int64_t j_min = std::max(int64_t(0), int64_t(ceil(x-1)));
  154. int64_t j_max = std::min(int64_t(m_alpha.size() - 1), int64_t(floor(x+5)) );
  155. Real s = 0;
  156. for (int64_t j = j_min; j <= j_max; ++j) {
  157. // TODO: Use Cox 1972 to generate all integer translates of B5 simultaneously.
  158. s += m_alpha[j]*cardinal_b_spline<5, Real>(x - j + 2);
  159. }
  160. return s;
  161. }
  162. Real prime(Real t) const {
  163. using std::ceil;
  164. using std::floor;
  165. using boost::math::cardinal_b_spline_prime;
  166. if (t < m_t0 || t > m_t0 + (m_alpha.size()-5)/m_inv_h) {
  167. const char* err_msg = "Tried to evaluate the cardinal quintic b-spline outside the domain of of interpolation; extrapolation does not work.";
  168. throw std::domain_error(err_msg);
  169. }
  170. Real x = (t-m_t0)*m_inv_h;
  171. // Support of B_5 is [-3, 3]. So -3 < x - j + 2 < 3, so x-1 < j < x+5
  172. int64_t j_min = std::max(int64_t(0), int64_t(ceil(x-1)));
  173. int64_t j_max = std::min(int64_t(m_alpha.size() - 1), int64_t(floor(x+5)) );
  174. Real s = 0;
  175. for (int64_t j = j_min; j <= j_max; ++j) {
  176. s += m_alpha[j]*cardinal_b_spline_prime<5, Real>(x - j + 2);
  177. }
  178. return s*m_inv_h;
  179. }
  180. Real double_prime(Real t) const {
  181. using std::ceil;
  182. using std::floor;
  183. using boost::math::cardinal_b_spline_double_prime;
  184. if (t < m_t0 || t > m_t0 + (m_alpha.size()-5)/m_inv_h) {
  185. const char* err_msg = "Tried to evaluate the cardinal quintic b-spline outside the domain of of interpolation; extrapolation does not work.";
  186. throw std::domain_error(err_msg);
  187. }
  188. Real x = (t-m_t0)*m_inv_h;
  189. // Support of B_5 is [-3, 3]. So -3 < x - j + 2 < 3, so x-1 < j < x+5
  190. int64_t j_min = std::max(int64_t(0), int64_t(ceil(x-1)));
  191. int64_t j_max = std::min(int64_t(m_alpha.size() - 1), int64_t(floor(x+5)) );
  192. Real s = 0;
  193. for (int64_t j = j_min; j <= j_max; ++j) {
  194. s += m_alpha[j]*cardinal_b_spline_double_prime<5, Real>(x - j + 2);
  195. }
  196. return s*m_inv_h*m_inv_h;
  197. }
  198. Real t_max() const {
  199. return m_t0 + (m_alpha.size()-5)/m_inv_h;
  200. }
  201. private:
  202. std::vector<Real> m_alpha;
  203. Real m_inv_h;
  204. Real m_t0;
  205. };
  206. }}}}
  207. #endif