// 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 #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) { std::ostringstream oss; oss << __FILE__ << ":" << __LINE__ << ":" << __func__; oss << " This interpolator requires at least four data points."; throw std::domain_error(oss.str()); } RandomAccessContainer s(x.size(), std::numeric_limits::quiet_NaN()); if (isnan(left_endpoint_derivative)) { // If the derivative is not specified, this seems as good a choice as any. // In particular, it satisfies the monotonicity constraint 0 <= |y'[0]| < 4Delta_i, // where Delta_i is the secant slope: 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 { // See here: // https://www.mathworks.com/content/dam/mathworks/mathworks-dot-com/moler/interp.pdf // Un-numbered equation just before Section 3.5: s[k] = (w1+w2)/(w1/dkm1 + w2/dk); } } 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