// Copyright Nick Thompson, 2020 // Use, modification and distribution are subject to the // Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_MATH_INTERPOLATORS_PCHIP_HPP #define BOOST_MATH_INTERPOLATORS_PCHIP_HPP #include #include namespace boost { namespace math { namespace interpolators { template class pchip { public: using Real = typename RandomAccessContainer::value_type; pchip(RandomAccessContainer && x, RandomAccessContainer && y, Real left_endpoint_derivative = std::numeric_limits::quiet_NaN(), Real right_endpoint_derivative = std::numeric_limits::quiet_NaN()) { using std::isnan; if (x.size() < 4) { throw std::domain_error("Must be at least four data points."); } RandomAccessContainer s(x.size(), std::numeric_limits::quiet_NaN()); if (isnan(left_endpoint_derivative)) { // O(h) finite difference derivative: // This, I believe, is the only derivative guaranteed to be monotonic: s[0] = (y[1]-y[0])/(x[1]-x[0]); } else { s[0] = left_endpoint_derivative; } for (decltype(s.size()) k = 1; k < s.size()-1; ++k) { Real hkm1 = x[k] - x[k-1]; Real dkm1 = (y[k] - y[k-1])/hkm1; Real hk = x[k+1] - x[k]; Real dk = (y[k+1] - y[k])/hk; Real w1 = 2*hk + hkm1; Real w2 = hk + 2*hkm1; if ( (dk > 0 && dkm1 < 0) || (dk < 0 && dkm1 > 0) || dk == 0 || dkm1 == 0) { s[k] = 0; } else { s[k] = (w1+w2)/(w1/dkm1 + w2/dk); } } // Quadratic extrapolation at the other end: auto n = s.size(); if (isnan(right_endpoint_derivative)) { s[n-1] = (y[n-1]-y[n-2])/(x[n-1] - x[n-2]); } else { s[n-1] = right_endpoint_derivative; } impl_ = std::make_shared>(std::move(x), std::move(y), std::move(s)); } Real operator()(Real x) const { return impl_->operator()(x); } Real prime(Real x) const { return impl_->prime(x); } friend std::ostream& operator<<(std::ostream & os, const pchip & m) { os << *m.impl_; return os; } void push_back(Real x, Real y) { using std::abs; using std::isnan; if (x <= impl_->x_.back()) { throw std::domain_error("Calling push_back must preserve the monotonicity of the x's"); } impl_->x_.push_back(x); impl_->y_.push_back(y); impl_->dydx_.push_back(std::numeric_limits::quiet_NaN()); auto n = impl_->size(); impl_->dydx_[n-1] = (impl_->y_[n-1]-impl_->y_[n-2])/(impl_->x_[n-1] - impl_->x_[n-2]); // Now fix s_[n-2]: auto k = n-2; Real hkm1 = impl_->x_[k] - impl_->x_[k-1]; Real dkm1 = (impl_->y_[k] - impl_->y_[k-1])/hkm1; Real hk = impl_->x_[k+1] - impl_->x_[k]; Real dk = (impl_->y_[k+1] - impl_->y_[k])/hk; Real w1 = 2*hk + hkm1; Real w2 = hk + 2*hkm1; if ( (dk > 0 && dkm1 < 0) || (dk < 0 && dkm1 > 0) || dk == 0 || dkm1 == 0) { impl_->dydx_[k] = 0; } else { impl_->dydx_[k] = (w1+w2)/(w1/dkm1 + w2/dk); } } private: std::shared_ptr> impl_; }; } } } #endif