pchip.hpp 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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 <sstream>
  9. #include <memory>
  10. #include <boost/math/interpolators/detail/cubic_hermite_detail.hpp>
  11. namespace boost {
  12. namespace math {
  13. namespace interpolators {
  14. template<class RandomAccessContainer>
  15. class pchip {
  16. public:
  17. using Real = typename RandomAccessContainer::value_type;
  18. pchip(RandomAccessContainer && x, RandomAccessContainer && y,
  19. Real left_endpoint_derivative = std::numeric_limits<Real>::quiet_NaN(),
  20. Real right_endpoint_derivative = std::numeric_limits<Real>::quiet_NaN())
  21. {
  22. using std::isnan;
  23. if (x.size() < 4)
  24. {
  25. std::ostringstream oss;
  26. oss << __FILE__ << ":" << __LINE__ << ":" << __func__;
  27. oss << " This interpolator requires at least four data points.";
  28. throw std::domain_error(oss.str());
  29. }
  30. RandomAccessContainer s(x.size(), std::numeric_limits<Real>::quiet_NaN());
  31. if (isnan(left_endpoint_derivative))
  32. {
  33. // If the derivative is not specified, this seems as good a choice as any.
  34. // In particular, it satisfies the monotonicity constraint 0 <= |y'[0]| < 4Delta_i,
  35. // where Delta_i is the secant slope:
  36. s[0] = (y[1]-y[0])/(x[1]-x[0]);
  37. }
  38. else
  39. {
  40. s[0] = left_endpoint_derivative;
  41. }
  42. for (decltype(s.size()) k = 1; k < s.size()-1; ++k) {
  43. Real hkm1 = x[k] - x[k-1];
  44. Real dkm1 = (y[k] - y[k-1])/hkm1;
  45. Real hk = x[k+1] - x[k];
  46. Real dk = (y[k+1] - y[k])/hk;
  47. Real w1 = 2*hk + hkm1;
  48. Real w2 = hk + 2*hkm1;
  49. if ( (dk > 0 && dkm1 < 0) || (dk < 0 && dkm1 > 0) || dk == 0 || dkm1 == 0)
  50. {
  51. s[k] = 0;
  52. }
  53. else
  54. {
  55. // See here:
  56. // https://www.mathworks.com/content/dam/mathworks/mathworks-dot-com/moler/interp.pdf
  57. // Un-numbered equation just before Section 3.5:
  58. s[k] = (w1+w2)/(w1/dkm1 + w2/dk);
  59. }
  60. }
  61. auto n = s.size();
  62. if (isnan(right_endpoint_derivative))
  63. {
  64. s[n-1] = (y[n-1]-y[n-2])/(x[n-1] - x[n-2]);
  65. }
  66. else
  67. {
  68. s[n-1] = right_endpoint_derivative;
  69. }
  70. impl_ = std::make_shared<detail::cubic_hermite_detail<RandomAccessContainer>>(std::move(x), std::move(y), std::move(s));
  71. }
  72. Real operator()(Real x) const {
  73. return impl_->operator()(x);
  74. }
  75. Real prime(Real x) const {
  76. return impl_->prime(x);
  77. }
  78. friend std::ostream& operator<<(std::ostream & os, const pchip & m)
  79. {
  80. os << *m.impl_;
  81. return os;
  82. }
  83. void push_back(Real x, Real y) {
  84. using std::abs;
  85. using std::isnan;
  86. if (x <= impl_->x_.back()) {
  87. throw std::domain_error("Calling push_back must preserve the monotonicity of the x's");
  88. }
  89. impl_->x_.push_back(x);
  90. impl_->y_.push_back(y);
  91. impl_->dydx_.push_back(std::numeric_limits<Real>::quiet_NaN());
  92. auto n = impl_->size();
  93. impl_->dydx_[n-1] = (impl_->y_[n-1]-impl_->y_[n-2])/(impl_->x_[n-1] - impl_->x_[n-2]);
  94. // Now fix s_[n-2]:
  95. auto k = n-2;
  96. Real hkm1 = impl_->x_[k] - impl_->x_[k-1];
  97. Real dkm1 = (impl_->y_[k] - impl_->y_[k-1])/hkm1;
  98. Real hk = impl_->x_[k+1] - impl_->x_[k];
  99. Real dk = (impl_->y_[k+1] - impl_->y_[k])/hk;
  100. Real w1 = 2*hk + hkm1;
  101. Real w2 = hk + 2*hkm1;
  102. if ( (dk > 0 && dkm1 < 0) || (dk < 0 && dkm1 > 0) || dk == 0 || dkm1 == 0)
  103. {
  104. impl_->dydx_[k] = 0;
  105. }
  106. else
  107. {
  108. impl_->dydx_[k] = (w1+w2)/(w1/dkm1 + w2/dk);
  109. }
  110. }
  111. private:
  112. std::shared_ptr<detail::cubic_hermite_detail<RandomAccessContainer>> impl_;
  113. };
  114. }
  115. }
  116. }
  117. #endif