cubic_hermite_detail.hpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. // Copyright Nick Thompson, 2020
  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_DETAIL_CUBIC_HERMITE_DETAIL_HPP
  7. #define BOOST_MATH_INTERPOLATORS_DETAIL_CUBIC_HERMITE_DETAIL_HPP
  8. #include <stdexcept>
  9. #include <algorithm>
  10. #include <cmath>
  11. #include <iostream>
  12. #include <sstream>
  13. #include <limits>
  14. namespace boost::math::interpolators::detail {
  15. template<class RandomAccessContainer>
  16. class cubic_hermite_detail {
  17. public:
  18. using Real = typename RandomAccessContainer::value_type;
  19. cubic_hermite_detail(RandomAccessContainer && x, RandomAccessContainer && y, RandomAccessContainer dydx)
  20. : x_{std::move(x)}, y_{std::move(y)}, dydx_{std::move(dydx)}
  21. {
  22. using std::abs;
  23. using std::isnan;
  24. if (x_.size() != y_.size())
  25. {
  26. throw std::domain_error("There must be the same number of ordinates as abscissas.");
  27. }
  28. if (x_.size() != dydx_.size())
  29. {
  30. throw std::domain_error("There must be the same number of ordinates as derivative values.");
  31. }
  32. if (x_.size() < 2)
  33. {
  34. throw std::domain_error("Must be at least two data points.");
  35. }
  36. Real x0 = x_[0];
  37. for (size_t i = 1; i < x_.size(); ++i)
  38. {
  39. Real x1 = x_[i];
  40. if (x1 <= x0)
  41. {
  42. std::ostringstream oss;
  43. oss.precision(std::numeric_limits<Real>::digits10+3);
  44. oss << "Abscissas must be listed in strictly increasing order x0 < x1 < ... < x_{n-1}, ";
  45. oss << "but at x[" << i - 1 << "] = " << x0 << ", and x[" << i << "] = " << x1 << ".\n";
  46. throw std::domain_error(oss.str());
  47. }
  48. x0 = x1;
  49. }
  50. }
  51. void push_back(Real x, Real y, Real dydx)
  52. {
  53. using std::abs;
  54. using std::isnan;
  55. if (x <= x_.back())
  56. {
  57. throw std::domain_error("Calling push_back must preserve the monotonicity of the x's");
  58. }
  59. x_.push_back(x);
  60. y_.push_back(y);
  61. dydx_.push_back(dydx);
  62. }
  63. Real operator()(Real x) const
  64. {
  65. if (x < x_[0] || x > x_.back())
  66. {
  67. std::ostringstream oss;
  68. oss.precision(std::numeric_limits<Real>::digits10+3);
  69. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  70. << x_[0] << ", " << x_.back() << "]";
  71. throw std::domain_error(oss.str());
  72. }
  73. // We need t := (x-x_k)/(x_{k+1}-x_k) \in [0,1) for this to work.
  74. // Sadly this neccessitates this loathesome check, otherwise we get t = 1 at x = xf.
  75. if (x == x_.back())
  76. {
  77. return y_.back();
  78. }
  79. auto it = std::upper_bound(x_.begin(), x_.end(), x);
  80. auto i = std::distance(x_.begin(), it) -1;
  81. Real x0 = *(it-1);
  82. Real x1 = *it;
  83. Real y0 = y_[i];
  84. Real y1 = y_[i+1];
  85. Real s0 = dydx_[i];
  86. Real s1 = dydx_[i+1];
  87. Real dx = (x1-x0);
  88. Real t = (x-x0)/dx;
  89. // See the section 'Representations' in the page
  90. // https://en.wikipedia.org/wiki/Cubic_Hermite_spline
  91. Real y = (1-t)*(1-t)*(y0*(1+2*t) + s0*(x-x0))
  92. + t*t*(y1*(3-2*t) + dx*s1*(t-1));
  93. return y;
  94. }
  95. Real prime(Real x) const
  96. {
  97. if (x < x_[0] || x > x_.back())
  98. {
  99. std::ostringstream oss;
  100. oss.precision(std::numeric_limits<Real>::digits10+3);
  101. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  102. << x_[0] << ", " << x_.back() << "]";
  103. throw std::domain_error(oss.str());
  104. }
  105. if (x == x_.back())
  106. {
  107. return dydx_.back();
  108. }
  109. auto it = std::upper_bound(x_.begin(), x_.end(), x);
  110. auto i = std::distance(x_.begin(), it) -1;
  111. Real x0 = *(it-1);
  112. Real x1 = *it;
  113. Real y0 = y_[i];
  114. Real y1 = y_[i+1];
  115. Real s0 = dydx_[i];
  116. Real s1 = dydx_[i+1];
  117. Real dx = (x1-x0);
  118. Real d1 = (y1 - y0 - s0*dx)/(dx*dx);
  119. Real d2 = (s1 - s0)/(2*dx);
  120. Real c2 = 3*d1 - 2*d2;
  121. Real c3 = 2*(d2 - d1)/dx;
  122. return s0 + 2*c2*(x-x0) + 3*c3*(x-x0)*(x-x0);
  123. }
  124. friend std::ostream& operator<<(std::ostream & os, const cubic_hermite_detail & m)
  125. {
  126. os << "(x,y,y') = {";
  127. for (size_t i = 0; i < m.x_.size() - 1; ++i)
  128. {
  129. os << "(" << m.x_[i] << ", " << m.y_[i] << ", " << m.dydx_[i] << "), ";
  130. }
  131. auto n = m.x_.size()-1;
  132. os << "(" << m.x_[n] << ", " << m.y_[n] << ", " << m.dydx_[n] << ")}";
  133. return os;
  134. }
  135. auto size() const
  136. {
  137. return x_.size();
  138. }
  139. int64_t bytes() const
  140. {
  141. return 3*x_.size()*sizeof(Real) + 3*sizeof(x_);
  142. }
  143. std::pair<Real, Real> domain() const
  144. {
  145. return {x_.front(), x_.back()};
  146. }
  147. RandomAccessContainer x_;
  148. RandomAccessContainer y_;
  149. RandomAccessContainer dydx_;
  150. };
  151. template<class RandomAccessContainer>
  152. class cardinal_cubic_hermite_detail {
  153. public:
  154. using Real = typename RandomAccessContainer::value_type;
  155. cardinal_cubic_hermite_detail(RandomAccessContainer && y, RandomAccessContainer dydx, Real x0, Real dx)
  156. : y_{std::move(y)}, dy_{std::move(dydx)}, x0_{x0}, inv_dx_{1/dx}
  157. {
  158. using std::abs;
  159. using std::isnan;
  160. if (y_.size() != dy_.size())
  161. {
  162. throw std::domain_error("There must be the same number of derivatives as ordinates.");
  163. }
  164. if (y_.size() < 2)
  165. {
  166. throw std::domain_error("Must be at least two data points.");
  167. }
  168. if (dx <= 0)
  169. {
  170. throw std::domain_error("dx > 0 is required.");
  171. }
  172. for (auto & dy : dy_)
  173. {
  174. dy *= dx;
  175. }
  176. }
  177. // Why not implement push_back? It's awkward: If the buffer is circular, x0_ += dx_.
  178. // If the buffer is not circular, x0_ is unchanged.
  179. // We need a concept for circular_buffer!
  180. inline Real operator()(Real x) const
  181. {
  182. const Real xf = x0_ + (y_.size()-1)/inv_dx_;
  183. if (x < x0_ || x > xf)
  184. {
  185. std::ostringstream oss;
  186. oss.precision(std::numeric_limits<Real>::digits10+3);
  187. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  188. << x0_ << ", " << xf << "]";
  189. throw std::domain_error(oss.str());
  190. }
  191. if (x == xf)
  192. {
  193. return y_.back();
  194. }
  195. return this->unchecked_evaluation(x);
  196. }
  197. inline Real unchecked_evaluation(Real x) const
  198. {
  199. using std::floor;
  200. Real s = (x-x0_)*inv_dx_;
  201. Real ii = floor(s);
  202. auto i = static_cast<decltype(y_.size())>(ii);
  203. Real t = s - ii;
  204. Real y0 = y_[i];
  205. Real y1 = y_[i+1];
  206. Real dy0 = dy_[i];
  207. Real dy1 = dy_[i+1];
  208. Real r = 1-t;
  209. return r*r*(y0*(1+2*t) + dy0*t)
  210. + t*t*(y1*(3-2*t) - dy1*r);
  211. }
  212. inline Real prime(Real x) const
  213. {
  214. const Real xf = x0_ + (y_.size()-1)/inv_dx_;
  215. if (x < x0_ || x > xf)
  216. {
  217. std::ostringstream oss;
  218. oss.precision(std::numeric_limits<Real>::digits10+3);
  219. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  220. << x0_ << ", " << xf << "]";
  221. throw std::domain_error(oss.str());
  222. }
  223. if (x == xf)
  224. {
  225. return dy_.back()*inv_dx_;
  226. }
  227. return this->unchecked_prime(x);
  228. }
  229. inline Real unchecked_prime(Real x) const
  230. {
  231. using std::floor;
  232. Real s = (x-x0_)*inv_dx_;
  233. Real ii = floor(s);
  234. auto i = static_cast<decltype(y_.size())>(ii);
  235. Real t = s - ii;
  236. Real y0 = y_[i];
  237. Real y1 = y_[i+1];
  238. Real dy0 = dy_[i];
  239. Real dy1 = dy_[i+1];
  240. Real dy = 6*t*(1-t)*(y1 - y0) + (3*t*t - 4*t+1)*dy0 + t*(3*t-2)*dy1;
  241. return dy*inv_dx_;
  242. }
  243. auto size() const
  244. {
  245. return y_.size();
  246. }
  247. int64_t bytes() const
  248. {
  249. return 2*y_.size()*sizeof(Real) + 2*sizeof(y_) + 2*sizeof(Real);
  250. }
  251. std::pair<Real, Real> domain() const
  252. {
  253. Real xf = x0_ + (y_.size()-1)/inv_dx_;
  254. return {x0_, xf};
  255. }
  256. private:
  257. RandomAccessContainer y_;
  258. RandomAccessContainer dy_;
  259. Real x0_;
  260. Real inv_dx_;
  261. };
  262. template<class RandomAccessContainer>
  263. class cardinal_cubic_hermite_detail_aos {
  264. public:
  265. using Point = typename RandomAccessContainer::value_type;
  266. using Real = typename Point::value_type;
  267. cardinal_cubic_hermite_detail_aos(RandomAccessContainer && dat, Real x0, Real dx)
  268. : dat_{std::move(dat)}, x0_{x0}, inv_dx_{1/dx}
  269. {
  270. if (dat_.size() < 2)
  271. {
  272. throw std::domain_error("Must be at least two data points.");
  273. }
  274. if (dat_[0].size() != 2)
  275. {
  276. throw std::domain_error("Each datum must contain (y, y'), and nothing else.");
  277. }
  278. if (dx <= 0)
  279. {
  280. throw std::domain_error("dx > 0 is required.");
  281. }
  282. for (auto & d : dat_)
  283. {
  284. d[1] *= dx;
  285. }
  286. }
  287. inline Real operator()(Real x) const
  288. {
  289. const Real xf = x0_ + (dat_.size()-1)/inv_dx_;
  290. if (x < x0_ || x > xf)
  291. {
  292. std::ostringstream oss;
  293. oss.precision(std::numeric_limits<Real>::digits10+3);
  294. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  295. << x0_ << ", " << xf << "]";
  296. throw std::domain_error(oss.str());
  297. }
  298. if (x == xf)
  299. {
  300. return dat_.back()[0];
  301. }
  302. return this->unchecked_evaluation(x);
  303. }
  304. inline Real unchecked_evaluation(Real x) const
  305. {
  306. using std::floor;
  307. Real s = (x-x0_)*inv_dx_;
  308. Real ii = floor(s);
  309. auto i = static_cast<decltype(dat_.size())>(ii);
  310. Real t = s - ii;
  311. // If we had infinite precision, this would never happen.
  312. // But we don't have infinite precision.
  313. if (t == 0)
  314. {
  315. return dat_[i][0];
  316. }
  317. Real y0 = dat_[i][0];
  318. Real y1 = dat_[i+1][0];
  319. Real dy0 = dat_[i][1];
  320. Real dy1 = dat_[i+1][1];
  321. Real r = 1-t;
  322. return r*r*(y0*(1+2*t) + dy0*t)
  323. + t*t*(y1*(3-2*t) - dy1*r);
  324. }
  325. inline Real prime(Real x) const
  326. {
  327. const Real xf = x0_ + (dat_.size()-1)/inv_dx_;
  328. if (x < x0_ || x > xf)
  329. {
  330. std::ostringstream oss;
  331. oss.precision(std::numeric_limits<Real>::digits10+3);
  332. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  333. << x0_ << ", " << xf << "]";
  334. throw std::domain_error(oss.str());
  335. }
  336. if (x == xf)
  337. {
  338. return dat_.back()[1]*inv_dx_;
  339. }
  340. return this->unchecked_prime(x);
  341. }
  342. inline Real unchecked_prime(Real x) const
  343. {
  344. using std::floor;
  345. Real s = (x-x0_)*inv_dx_;
  346. Real ii = floor(s);
  347. auto i = static_cast<decltype(dat_.size())>(ii);
  348. Real t = s - ii;
  349. if (t == 0)
  350. {
  351. return dat_[i][1]*inv_dx_;
  352. }
  353. Real y0 = dat_[i][0];
  354. Real dy0 = dat_[i][1];
  355. Real y1 = dat_[i+1][0];
  356. Real dy1 = dat_[i+1][1];
  357. Real dy = 6*t*(1-t)*(y1 - y0) + (3*t*t - 4*t+1)*dy0 + t*(3*t-2)*dy1;
  358. return dy*inv_dx_;
  359. }
  360. auto size() const
  361. {
  362. return dat_.size();
  363. }
  364. int64_t bytes() const
  365. {
  366. return dat_.size()*dat_[0].size()*sizeof(Real) + sizeof(dat_) + 2*sizeof(Real);
  367. }
  368. std::pair<Real, Real> domain() const
  369. {
  370. Real xf = x0_ + (dat_.size()-1)/inv_dx_;
  371. return {x0_, xf};
  372. }
  373. private:
  374. RandomAccessContainer dat_;
  375. Real x0_;
  376. Real inv_dx_;
  377. };
  378. }
  379. #endif