pchip.hpp 3.7 KB

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