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)
7 #ifndef BOOST_MATH_INTERPOLATORS_PCHIP_HPP
8 #define BOOST_MATH_INTERPOLATORS_PCHIP_HPP
10 #include <boost/math/interpolators/detail/cubic_hermite_detail.hpp>
12 namespace boost::math::interpolators {
14 template<class RandomAccessContainer>
17 using Real = typename RandomAccessContainer::value_type;
19 pchip(RandomAccessContainer && x, RandomAccessContainer && y,
20 Real left_endpoint_derivative = std::numeric_limits<Real>::quiet_NaN(),
21 Real right_endpoint_derivative = std::numeric_limits<Real>::quiet_NaN())
25 throw std::domain_error("Must be at least four data points.");
27 RandomAccessContainer s(x.size(), std::numeric_limits<Real>::quiet_NaN());
28 if (isnan(left_endpoint_derivative))
30 // O(h) finite difference derivative:
31 // This, I believe, is the only derivative guaranteed to be monotonic:
32 s[0] = (y[1]-y[0])/(x[1]-x[0]);
36 s[0] = left_endpoint_derivative;
39 for (decltype(s.size()) k = 1; k < s.size()-1; ++k) {
40 Real hkm1 = x[k] - x[k-1];
41 Real dkm1 = (y[k] - y[k-1])/hkm1;
43 Real hk = x[k+1] - x[k];
44 Real dk = (y[k+1] - y[k])/hk;
45 Real w1 = 2*hk + hkm1;
46 Real w2 = hk + 2*hkm1;
47 if ( (dk > 0 && dkm1 < 0) || (dk < 0 && dkm1 > 0) || dk == 0 || dkm1 == 0)
53 s[k] = (w1+w2)/(w1/dkm1 + w2/dk);
57 // Quadratic extrapolation at the other end:
59 if (isnan(right_endpoint_derivative))
61 s[n-1] = (y[n-1]-y[n-2])/(x[n-1] - x[n-2]);
65 s[n-1] = right_endpoint_derivative;
67 impl_ = std::make_shared<detail::cubic_hermite_detail<RandomAccessContainer>>(std::move(x), std::move(y), std::move(s));
70 Real operator()(Real x) const {
71 return impl_->operator()(x);
74 Real prime(Real x) const {
75 return impl_->prime(x);
78 friend std::ostream& operator<<(std::ostream & os, const pchip & m)
84 void push_back(Real x, Real y) {
87 if (x <= impl_->x_.back()) {
88 throw std::domain_error("Calling push_back must preserve the monotonicity of the x's");
90 impl_->x_.push_back(x);
91 impl_->y_.push_back(y);
92 impl_->dydx_.push_back(std::numeric_limits<Real>::quiet_NaN());
93 auto n = impl_->size();
94 impl_->dydx_[n-1] = (impl_->y_[n-1]-impl_->y_[n-2])/(impl_->x_[n-1] - impl_->x_[n-2]);
97 Real hkm1 = impl_->x_[k] - impl_->x_[k-1];
98 Real dkm1 = (impl_->y_[k] - impl_->y_[k-1])/hkm1;
100 Real hk = impl_->x_[k+1] - impl_->x_[k];
101 Real dk = (impl_->y_[k+1] - impl_->y_[k])/hk;
102 Real w1 = 2*hk + hkm1;
103 Real w2 = hk + 2*hkm1;
104 if ( (dk > 0 && dkm1 < 0) || (dk < 0 && dkm1 > 0) || dk == 0 || dkm1 == 0)
110 impl_->dydx_[k] = (w1+w2)/(w1/dkm1 + w2/dk);
115 std::shared_ptr<detail::cubic_hermite_detail<RandomAccessContainer>> impl_;