cubic_hermite_detail.hpp 12 KB

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