quintic_hermite_detail.hpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. /*
  2. * Copyright Nick Thompson, 2020
  3. * Use, modification and distribution are subject to the
  4. * Boost Software License, Version 1.0. (See accompanying file
  5. * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. */
  7. #ifndef BOOST_MATH_INTERPOLATORS_DETAIL_QUINTIC_HERMITE_DETAIL_HPP
  8. #define BOOST_MATH_INTERPOLATORS_DETAIL_QUINTIC_HERMITE_DETAIL_HPP
  9. #include <algorithm>
  10. #include <stdexcept>
  11. #include <sstream>
  12. #include <cmath>
  13. namespace boost::math::interpolators::detail {
  14. template<class RandomAccessContainer>
  15. class quintic_hermite_detail {
  16. public:
  17. using Real = typename RandomAccessContainer::value_type;
  18. quintic_hermite_detail(RandomAccessContainer && x, RandomAccessContainer && y, RandomAccessContainer && dydx, RandomAccessContainer && d2ydx2) : x_{std::move(x)}, y_{std::move(y)}, dydx_{std::move(dydx)}, d2ydx2_{std::move(d2ydx2)}
  19. {
  20. if (x_.size() != y_.size())
  21. {
  22. throw std::domain_error("Number of abscissas must = number of ordinates.");
  23. }
  24. if (x_.size() != dydx_.size())
  25. {
  26. throw std::domain_error("Numbers of derivatives must = number of abscissas.");
  27. }
  28. if (x_.size() != d2ydx2_.size())
  29. {
  30. throw std::domain_error("Number of second derivatives must equal number of abscissas.");
  31. }
  32. if (x_.size() < 2)
  33. {
  34. throw std::domain_error("At least 2 abscissas are required.");
  35. }
  36. Real x0 = x_[0];
  37. for (decltype(x_.size()) i = 1; i < x_.size(); ++i)
  38. {
  39. Real x1 = x_[i];
  40. if (x1 <= x0)
  41. {
  42. throw std::domain_error("Abscissas must be sorted in strictly increasing order x0 < x1 < ... < x_{n-1}");
  43. }
  44. x0 = x1;
  45. }
  46. }
  47. void push_back(Real x, Real y, Real dydx, Real d2ydx2)
  48. {
  49. using std::abs;
  50. using std::isnan;
  51. if (x <= x_.back())
  52. {
  53. throw std::domain_error("Calling push_back must preserve the monotonicity of the x's");
  54. }
  55. x_.push_back(x);
  56. y_.push_back(y);
  57. dydx_.push_back(dydx);
  58. d2ydx2_.push_back(d2ydx2);
  59. }
  60. inline Real operator()(Real x) const
  61. {
  62. if (x < x_[0] || x > x_.back())
  63. {
  64. std::ostringstream oss;
  65. oss.precision(std::numeric_limits<Real>::digits10+3);
  66. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  67. << x_[0] << ", " << x_.back() << "]";
  68. throw std::domain_error(oss.str());
  69. }
  70. // We need t := (x-x_k)/(x_{k+1}-x_k) \in [0,1) for this to work.
  71. // Sadly this neccessitates this loathesome check, otherwise we get t = 1 at x = xf.
  72. if (x == x_.back())
  73. {
  74. return y_.back();
  75. }
  76. auto it = std::upper_bound(x_.begin(), x_.end(), x);
  77. auto i = std::distance(x_.begin(), it) -1;
  78. Real x0 = *(it-1);
  79. Real x1 = *it;
  80. Real y0 = y_[i];
  81. Real y1 = y_[i+1];
  82. Real v0 = dydx_[i];
  83. Real v1 = dydx_[i+1];
  84. Real a0 = d2ydx2_[i];
  85. Real a1 = d2ydx2_[i+1];
  86. Real dx = (x1-x0);
  87. Real t = (x-x0)/dx;
  88. Real t2 = t*t;
  89. Real t3 = t2*t;
  90. // See the 'Basis functions' section of:
  91. // https://www.rose-hulman.edu/~finn/CCLI/Notes/day09.pdf
  92. // Also: https://github.com/MrHexxx/QuinticHermiteSpline/blob/master/HermiteSpline.cs
  93. Real y = (1- t3*(10 + t*(-15 + 6*t)))*y0;
  94. y += t*(1+ t2*(-6 + t*(8 -3*t)))*v0*dx;
  95. y += t2*(1 + t*(-3 + t*(3-t)))*a0*dx*dx/2;
  96. y += t3*((1 + t*(-2 + t))*a1*dx*dx/2 + (-4 + t*(7 - 3*t))*v1*dx + (10 + t*(-15 + 6*t))*y1);
  97. return y;
  98. }
  99. inline 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 dx = x1 - x0;
  118. Real y0 = y_[i];
  119. Real y1 = y_[i+1];
  120. Real v0 = dydx_[i];
  121. Real v1 = dydx_[i+1];
  122. Real a0 = d2ydx2_[i];
  123. Real a1 = d2ydx2_[i+1];
  124. Real t= (x-x0)/dx;
  125. Real t2 = t*t;
  126. Real dydx = 30*t2*(1 - 2*t + t*t)*(y1-y0)/dx;
  127. dydx += (1-18*t*t + 32*t*t*t - 15*t*t*t*t)*v0 - t*t*(12 - 28*t + 15*t*t)*v1;
  128. dydx += (t*dx/2)*((2 - 9*t + 12*t*t - 5*t*t*t)*a0 + t*(3 - 8*t + 5*t*t)*a1);
  129. return dydx;
  130. }
  131. inline Real double_prime(Real x) const
  132. {
  133. if (x < x_[0] || x > x_.back())
  134. {
  135. std::ostringstream oss;
  136. oss.precision(std::numeric_limits<Real>::digits10+3);
  137. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  138. << x_[0] << ", " << x_.back() << "]";
  139. throw std::domain_error(oss.str());
  140. }
  141. if (x == x_.back())
  142. {
  143. return d2ydx2_.back();
  144. }
  145. auto it = std::upper_bound(x_.begin(), x_.end(), x);
  146. auto i = std::distance(x_.begin(), it) -1;
  147. Real x0 = *(it-1);
  148. Real x1 = *it;
  149. Real dx = x1 - x0;
  150. Real y0 = y_[i];
  151. Real y1 = y_[i+1];
  152. Real v0 = dydx_[i];
  153. Real v1 = dydx_[i+1];
  154. Real a0 = d2ydx2_[i];
  155. Real a1 = d2ydx2_[i+1];
  156. Real t = (x-x0)/dx;
  157. Real d2ydx2 = 60*t*(1 + t*(-3 + 2*t))*(y1-y0)/(dx*dx);
  158. d2ydx2 += 12*t*(-3 + t*(8 - 5*t))*v0/dx;
  159. d2ydx2 -= 12*t*(2 + t*(-7 + 5*t))*v1/dx;
  160. d2ydx2 += (1 + t*(-9 + t*(18 - 10*t)))*a0;
  161. d2ydx2 += t*(3 + t*(-12 + 10*t))*a1;
  162. return d2ydx2;
  163. }
  164. friend std::ostream& operator<<(std::ostream & os, const quintic_hermite_detail & m)
  165. {
  166. os << "(x,y,y') = {";
  167. for (size_t i = 0; i < m.x_.size() - 1; ++i) {
  168. os << "(" << m.x_[i] << ", " << m.y_[i] << ", " << m.dydx_[i] << ", " << m.d2ydx2_[i] << "), ";
  169. }
  170. auto n = m.x_.size()-1;
  171. os << "(" << m.x_[n] << ", " << m.y_[n] << ", " << m.dydx_[n] << ", " << m.d2ydx2_[n] << ")}";
  172. return os;
  173. }
  174. int64_t bytes() const
  175. {
  176. return 4*x_.size()*sizeof(x_);
  177. }
  178. std::pair<Real, Real> domain() const
  179. {
  180. return {x_.front(), x_.back()};
  181. }
  182. private:
  183. RandomAccessContainer x_;
  184. RandomAccessContainer y_;
  185. RandomAccessContainer dydx_;
  186. RandomAccessContainer d2ydx2_;
  187. };
  188. template<class RandomAccessContainer>
  189. class cardinal_quintic_hermite_detail {
  190. public:
  191. using Real = typename RandomAccessContainer::value_type;
  192. cardinal_quintic_hermite_detail(RandomAccessContainer && y, RandomAccessContainer && dydx, RandomAccessContainer && d2ydx2, Real x0, Real dx)
  193. : y_{std::move(y)}, dy_{std::move(dydx)}, d2y_{std::move(d2ydx2)}, x0_{x0}, inv_dx_{1/dx}
  194. {
  195. if (y_.size() != dy_.size())
  196. {
  197. throw std::domain_error("Numbers of derivatives must = number of abscissas.");
  198. }
  199. if (y_.size() != d2y_.size())
  200. {
  201. throw std::domain_error("Number of second derivatives must equal number of abscissas.");
  202. }
  203. if (y_.size() < 2)
  204. {
  205. throw std::domain_error("At least 2 abscissas are required.");
  206. }
  207. if (dx <= 0)
  208. {
  209. throw std::domain_error("dx > 0 is required.");
  210. }
  211. for (auto & dy : dy_)
  212. {
  213. dy *= dx;
  214. }
  215. for (auto & d2y : d2y_)
  216. {
  217. d2y *= (dx*dx)/2;
  218. }
  219. }
  220. inline Real operator()(Real x) const
  221. {
  222. const Real xf = x0_ + (y_.size()-1)/inv_dx_;
  223. if (x < x0_ || x > xf)
  224. {
  225. std::ostringstream oss;
  226. oss.precision(std::numeric_limits<Real>::digits10+3);
  227. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  228. << x0_ << ", " << xf << "]";
  229. throw std::domain_error(oss.str());
  230. }
  231. if (x == xf)
  232. {
  233. return y_.back();
  234. }
  235. return this->unchecked_evaluation(x);
  236. }
  237. inline Real unchecked_evaluation(Real x) const
  238. {
  239. using std::floor;
  240. Real s = (x-x0_)*inv_dx_;
  241. Real ii = floor(s);
  242. auto i = static_cast<decltype(y_.size())>(ii);
  243. Real t = s - ii;
  244. if (t == 0)
  245. {
  246. return y_[i];
  247. }
  248. Real y0 = y_[i];
  249. Real y1 = y_[i+1];
  250. Real dy0 = dy_[i];
  251. Real dy1 = dy_[i+1];
  252. Real d2y0 = d2y_[i];
  253. Real d2y1 = d2y_[i+1];
  254. // See the 'Basis functions' section of:
  255. // https://www.rose-hulman.edu/~finn/CCLI/Notes/day09.pdf
  256. // Also: https://github.com/MrHexxx/QuinticHermiteSpline/blob/master/HermiteSpline.cs
  257. Real y = (1- t*t*t*(10 + t*(-15 + 6*t)))*y0;
  258. y += t*(1+ t*t*(-6 + t*(8 -3*t)))*dy0;
  259. y += t*t*(1 + t*(-3 + t*(3-t)))*d2y0;
  260. y += t*t*t*((1 + t*(-2 + t))*d2y1 + (-4 + t*(7 -3*t))*dy1 + (10 + t*(-15 + 6*t))*y1);
  261. return y;
  262. }
  263. inline Real prime(Real x) const
  264. {
  265. const Real xf = x0_ + (y_.size()-1)/inv_dx_;
  266. if (x < x0_ || x > xf)
  267. {
  268. std::ostringstream oss;
  269. oss.precision(std::numeric_limits<Real>::digits10+3);
  270. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  271. << x0_ << ", " << xf << "]";
  272. throw std::domain_error(oss.str());
  273. }
  274. if (x == xf)
  275. {
  276. return dy_.back()*inv_dx_;
  277. }
  278. return this->unchecked_prime(x);
  279. }
  280. inline Real unchecked_prime(Real x) const
  281. {
  282. using std::floor;
  283. Real s = (x-x0_)*inv_dx_;
  284. Real ii = floor(s);
  285. auto i = static_cast<decltype(y_.size())>(ii);
  286. Real t = s - ii;
  287. if (t == 0)
  288. {
  289. return dy_[i]*inv_dx_;
  290. }
  291. Real y0 = y_[i];
  292. Real y1 = y_[i+1];
  293. Real dy0 = dy_[i];
  294. Real dy1 = dy_[i+1];
  295. Real d2y0 = d2y_[i];
  296. Real d2y1 = d2y_[i+1];
  297. Real dydx = 30*t*t*(1 - 2*t + t*t)*(y1-y0);
  298. dydx += (1-18*t*t + 32*t*t*t - 15*t*t*t*t)*dy0 - t*t*(12 - 28*t + 15*t*t)*dy1;
  299. dydx += t*((2 - 9*t + 12*t*t - 5*t*t*t)*d2y0 + t*(3 - 8*t + 5*t*t)*d2y1);
  300. dydx *= inv_dx_;
  301. return dydx;
  302. }
  303. inline Real double_prime(Real x) const
  304. {
  305. const Real xf = x0_ + (y_.size()-1)/inv_dx_;
  306. if (x < x0_ || x > xf) {
  307. std::ostringstream oss;
  308. oss.precision(std::numeric_limits<Real>::digits10+3);
  309. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  310. << x0_ << ", " << xf << "]";
  311. throw std::domain_error(oss.str());
  312. }
  313. if (x == xf)
  314. {
  315. return d2y_.back()*2*inv_dx_*inv_dx_;
  316. }
  317. return this->unchecked_double_prime(x);
  318. }
  319. inline Real unchecked_double_prime(Real x) const
  320. {
  321. using std::floor;
  322. Real s = (x-x0_)*inv_dx_;
  323. Real ii = floor(s);
  324. auto i = static_cast<decltype(y_.size())>(ii);
  325. Real t = s - ii;
  326. if (t==0)
  327. {
  328. return d2y_[i]*2*inv_dx_*inv_dx_;
  329. }
  330. Real y0 = y_[i];
  331. Real y1 = y_[i+1];
  332. Real dy0 = dy_[i];
  333. Real dy1 = dy_[i+1];
  334. Real d2y0 = d2y_[i];
  335. Real d2y1 = d2y_[i+1];
  336. Real d2ydx2 = 60*t*(1 - 3*t + 2*t*t)*(y1 - y0)*inv_dx_*inv_dx_;
  337. d2ydx2 += (12*t)*((-3 + 8*t - 5*t*t)*dy0 - (2 - 7*t + 5*t*t)*dy1);
  338. d2ydx2 += (1 - 9*t + 18*t*t - 10*t*t*t)*d2y0*(2*inv_dx_*inv_dx_) + t*(3 - 12*t + 10*t*t)*d2y1*(2*inv_dx_*inv_dx_);
  339. return d2ydx2;
  340. }
  341. int64_t bytes() const
  342. {
  343. return 3*y_.size()*sizeof(Real) + 2*sizeof(Real);
  344. }
  345. std::pair<Real, Real> domain() const
  346. {
  347. Real xf = x0_ + (y_.size()-1)/inv_dx_;
  348. return {x0_, xf};
  349. }
  350. private:
  351. RandomAccessContainer y_;
  352. RandomAccessContainer dy_;
  353. RandomAccessContainer d2y_;
  354. Real x0_;
  355. Real inv_dx_;
  356. };
  357. template<class RandomAccessContainer>
  358. class cardinal_quintic_hermite_detail_aos {
  359. public:
  360. using Point = typename RandomAccessContainer::value_type;
  361. using Real = typename Point::value_type;
  362. cardinal_quintic_hermite_detail_aos(RandomAccessContainer && data, Real x0, Real dx)
  363. : data_{std::move(data)} , x0_{x0}, inv_dx_{1/dx}
  364. {
  365. if (data_.size() < 2)
  366. {
  367. throw std::domain_error("At least two points are required to interpolate using cardinal_quintic_hermite_aos");
  368. }
  369. if (data_[0].size() != 3)
  370. {
  371. throw std::domain_error("Each datum passed to the cardinal_quintic_hermite_aos must have three elements: {y, y', y''}");
  372. }
  373. if (dx <= 0)
  374. {
  375. throw std::domain_error("dx > 0 is required.");
  376. }
  377. for (auto & datum : data_)
  378. {
  379. datum[1] *= dx;
  380. datum[2] *= (dx*dx/2);
  381. }
  382. }
  383. inline Real operator()(Real x) const
  384. {
  385. const Real xf = x0_ + (data_.size()-1)/inv_dx_;
  386. if (x < x0_ || x > xf)
  387. {
  388. std::ostringstream oss;
  389. oss.precision(std::numeric_limits<Real>::digits10+3);
  390. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  391. << x0_ << ", " << xf << "]";
  392. throw std::domain_error(oss.str());
  393. }
  394. if (x == xf)
  395. {
  396. return data_.back()[0];
  397. }
  398. return this->unchecked_evaluation(x);
  399. }
  400. inline Real unchecked_evaluation(Real x) const
  401. {
  402. using std::floor;
  403. Real s = (x-x0_)*inv_dx_;
  404. Real ii = floor(s);
  405. auto i = static_cast<decltype(data_.size())>(ii);
  406. Real t = s - ii;
  407. if (t == 0)
  408. {
  409. return data_[i][0];
  410. }
  411. Real y0 = data_[i][0];
  412. Real dy0 = data_[i][1];
  413. Real d2y0 = data_[i][2];
  414. Real y1 = data_[i+1][0];
  415. Real dy1 = data_[i+1][1];
  416. Real d2y1 = data_[i+1][2];
  417. Real y = (1 - t*t*t*(10 + t*(-15 + 6*t)))*y0;
  418. y += t*(1 + t*t*(-6 + t*(8 - 3*t)))*dy0;
  419. y += t*t*(1 + t*(-3 + t*(3 - t)))*d2y0;
  420. y += t*t*t*((1 + t*(-2 + t))*d2y1 + (-4 + t*(7 - 3*t))*dy1 + (10 + t*(-15 + 6*t))*y1);
  421. return y;
  422. }
  423. inline Real prime(Real x) const
  424. {
  425. const Real xf = x0_ + (data_.size()-1)/inv_dx_;
  426. if (x < x0_ || x > xf)
  427. {
  428. std::ostringstream oss;
  429. oss.precision(std::numeric_limits<Real>::digits10+3);
  430. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  431. << x0_ << ", " << xf << "]";
  432. throw std::domain_error(oss.str());
  433. }
  434. if (x == xf)
  435. {
  436. return data_.back()[1]*inv_dx_;
  437. }
  438. return this->unchecked_prime(x);
  439. }
  440. inline Real unchecked_prime(Real x) const
  441. {
  442. using std::floor;
  443. Real s = (x-x0_)*inv_dx_;
  444. Real ii = floor(s);
  445. auto i = static_cast<decltype(data_.size())>(ii);
  446. Real t = s - ii;
  447. if (t == 0)
  448. {
  449. return data_[i][1]*inv_dx_;
  450. }
  451. Real y0 = data_[i][0];
  452. Real y1 = data_[i+1][0];
  453. Real v0 = data_[i][1];
  454. Real v1 = data_[i+1][1];
  455. Real a0 = data_[i][2];
  456. Real a1 = data_[i+1][2];
  457. Real dy = 30*t*t*(1 - 2*t + t*t)*(y1-y0);
  458. dy += (1-18*t*t + 32*t*t*t - 15*t*t*t*t)*v0 - t*t*(12 - 28*t + 15*t*t)*v1;
  459. dy += t*((2 - 9*t + 12*t*t - 5*t*t*t)*a0 + t*(3 - 8*t + 5*t*t)*a1);
  460. return dy*inv_dx_;
  461. }
  462. inline Real double_prime(Real x) const
  463. {
  464. const Real xf = x0_ + (data_.size()-1)/inv_dx_;
  465. if (x < x0_ || x > xf)
  466. {
  467. std::ostringstream oss;
  468. oss.precision(std::numeric_limits<Real>::digits10+3);
  469. oss << "Requested abscissa x = " << x << ", which is outside of allowed range ["
  470. << x0_ << ", " << xf << "]";
  471. throw std::domain_error(oss.str());
  472. }
  473. if (x == xf)
  474. {
  475. return data_.back()[2]*2*inv_dx_*inv_dx_;
  476. }
  477. return this->unchecked_double_prime(x);
  478. }
  479. inline Real unchecked_double_prime(Real x) const
  480. {
  481. using std::floor;
  482. Real s = (x-x0_)*inv_dx_;
  483. Real ii = floor(s);
  484. auto i = static_cast<decltype(data_.size())>(ii);
  485. Real t = s - ii;
  486. if (t == 0) {
  487. return data_[i][2]*2*inv_dx_*inv_dx_;
  488. }
  489. Real y0 = data_[i][0];
  490. Real dy0 = data_[i][1];
  491. Real d2y0 = data_[i][2];
  492. Real y1 = data_[i+1][0];
  493. Real dy1 = data_[i+1][1];
  494. Real d2y1 = data_[i+1][2];
  495. Real d2ydx2 = 60*t*(1 - 3*t + 2*t*t)*(y1 - y0)*inv_dx_*inv_dx_;
  496. d2ydx2 += (12*t)*((-3 + 8*t - 5*t*t)*dy0 - (2 - 7*t + 5*t*t)*dy1);
  497. d2ydx2 += (1 - 9*t + 18*t*t - 10*t*t*t)*d2y0*(2*inv_dx_*inv_dx_) + t*(3 - 12*t + 10*t*t)*d2y1*(2*inv_dx_*inv_dx_);
  498. return d2ydx2;
  499. }
  500. int64_t bytes() const
  501. {
  502. return data_.size()*data_[0].size()*sizeof(Real) + 2*sizeof(Real);
  503. }
  504. std::pair<Real, Real> domain() const
  505. {
  506. Real xf = x0_ + (data_.size()-1)/inv_dx_;
  507. return {x0_, xf};
  508. }
  509. private:
  510. RandomAccessContainer data_;
  511. Real x0_;
  512. Real inv_dx_;
  513. };
  514. }
  515. #endif