pchip.hpp 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  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_PCHIP_HPP
  7. #define BOOST_MATH_INTERPOLATORS_PCHIP_HPP
  8. #include <memory>
  9. #include <boost/math/interpolators/detail/cubic_hermite_detail.hpp>
  10. namespace boost::math::interpolators {
  11. template<class RandomAccessContainer>
  12. class pchip {
  13. public:
  14. using Real = typename RandomAccessContainer::value_type;
  15. pchip(RandomAccessContainer && x, RandomAccessContainer && y,
  16. Real left_endpoint_derivative = std::numeric_limits<Real>::quiet_NaN(),
  17. Real right_endpoint_derivative = std::numeric_limits<Real>::quiet_NaN())
  18. {
  19. if (x.size() < 4)
  20. {
  21. throw std::domain_error("Must be at least four data points.");
  22. }
  23. RandomAccessContainer s(x.size(), std::numeric_limits<Real>::quiet_NaN());
  24. if (isnan(left_endpoint_derivative))
  25. {
  26. // O(h) finite difference derivative:
  27. // This, I believe, is the only derivative guaranteed to be monotonic:
  28. s[0] = (y[1]-y[0])/(x[1]-x[0]);
  29. }
  30. else
  31. {
  32. s[0] = left_endpoint_derivative;
  33. }
  34. for (decltype(s.size()) k = 1; k < s.size()-1; ++k) {
  35. Real hkm1 = x[k] - x[k-1];
  36. Real dkm1 = (y[k] - y[k-1])/hkm1;
  37. Real hk = x[k+1] - x[k];
  38. Real dk = (y[k+1] - y[k])/hk;
  39. Real w1 = 2*hk + hkm1;
  40. Real w2 = hk + 2*hkm1;
  41. if ( (dk > 0 && dkm1 < 0) || (dk < 0 && dkm1 > 0) || dk == 0 || dkm1 == 0)
  42. {
  43. s[k] = 0;
  44. }
  45. else
  46. {
  47. s[k] = (w1+w2)/(w1/dkm1 + w2/dk);
  48. }
  49. }
  50. // Quadratic extrapolation at the other end:
  51. auto n = s.size();
  52. if (isnan(right_endpoint_derivative))
  53. {
  54. s[n-1] = (y[n-1]-y[n-2])/(x[n-1] - x[n-2]);
  55. }
  56. else
  57. {
  58. s[n-1] = right_endpoint_derivative;
  59. }
  60. impl_ = std::make_shared<detail::cubic_hermite_detail<RandomAccessContainer>>(std::move(x), std::move(y), std::move(s));
  61. }
  62. Real operator()(Real x) const {
  63. return impl_->operator()(x);
  64. }
  65. Real prime(Real x) const {
  66. return impl_->prime(x);
  67. }
  68. friend std::ostream& operator<<(std::ostream & os, const pchip & m)
  69. {
  70. os << *m.impl_;
  71. return os;
  72. }
  73. void push_back(Real x, Real y) {
  74. using std::abs;
  75. using std::isnan;
  76. if (x <= impl_->x_.back()) {
  77. throw std::domain_error("Calling push_back must preserve the monotonicity of the x's");
  78. }
  79. impl_->x_.push_back(x);
  80. impl_->y_.push_back(y);
  81. impl_->dydx_.push_back(std::numeric_limits<Real>::quiet_NaN());
  82. auto n = impl_->size();
  83. impl_->dydx_[n-1] = (impl_->y_[n-1]-impl_->y_[n-2])/(impl_->x_[n-1] - impl_->x_[n-2]);
  84. // Now fix s_[n-2]:
  85. auto k = n-2;
  86. Real hkm1 = impl_->x_[k] - impl_->x_[k-1];
  87. Real dkm1 = (impl_->y_[k] - impl_->y_[k-1])/hkm1;
  88. Real hk = impl_->x_[k+1] - impl_->x_[k];
  89. Real dk = (impl_->y_[k+1] - impl_->y_[k])/hk;
  90. Real w1 = 2*hk + hkm1;
  91. Real w2 = hk + 2*hkm1;
  92. if ( (dk > 0 && dkm1 < 0) || (dk < 0 && dkm1 > 0) || dk == 0 || dkm1 == 0)
  93. {
  94. impl_->dydx_[k] = 0;
  95. }
  96. else
  97. {
  98. impl_->dydx_[k] = (w1+w2)/(w1/dkm1 + w2/dk);
  99. }
  100. }
  101. private:
  102. std::shared_ptr<detail::cubic_hermite_detail<RandomAccessContainer>> impl_;
  103. };
  104. }
  105. #endif