makima.hpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. // See: https://blogs.mathworks.com/cleve/2019/04/29/makima-piecewise-cubic-interpolation/
  7. // And: https://doi.org/10.1145/321607.321609
  8. #ifndef BOOST_MATH_INTERPOLATORS_MAKIMA_HPP
  9. #define BOOST_MATH_INTERPOLATORS_MAKIMA_HPP
  10. #include <memory>
  11. #include <cmath>
  12. #include <boost/math/interpolators/detail/cubic_hermite_detail.hpp>
  13. namespace boost {
  14. namespace math {
  15. namespace interpolators {
  16. template<class RandomAccessContainer>
  17. class makima {
  18. public:
  19. using Real = typename RandomAccessContainer::value_type;
  20. makima(RandomAccessContainer && x, RandomAccessContainer && y,
  21. Real left_endpoint_derivative = std::numeric_limits<Real>::quiet_NaN(),
  22. Real right_endpoint_derivative = std::numeric_limits<Real>::quiet_NaN())
  23. {
  24. using std::isnan;
  25. using std::abs;
  26. if (x.size() < 4)
  27. {
  28. throw std::domain_error("Must be at least four data points.");
  29. }
  30. RandomAccessContainer s(x.size(), std::numeric_limits<Real>::quiet_NaN());
  31. Real m2 = (y[3]-y[2])/(x[3]-x[2]);
  32. Real m1 = (y[2]-y[1])/(x[2]-x[1]);
  33. Real m0 = (y[1]-y[0])/(x[1]-x[0]);
  34. // Quadratic extrapolation: m_{-1} = 2m_0 - m_1:
  35. Real mm1 = 2*m0 - m1;
  36. // Quadratic extrapolation: m_{-2} = 2*m_{-1}-m_0:
  37. Real mm2 = 2*mm1 - m0;
  38. Real w1 = abs(m1-m0) + abs(m1+m0)/2;
  39. Real w2 = abs(mm1-mm2) + abs(mm1+mm2)/2;
  40. if (isnan(left_endpoint_derivative))
  41. {
  42. s[0] = (w1*mm1 + w2*m0)/(w1+w2);
  43. if (isnan(s[0]))
  44. {
  45. s[0] = 0;
  46. }
  47. }
  48. else
  49. {
  50. s[0] = left_endpoint_derivative;
  51. }
  52. w1 = abs(m2-m1) + abs(m2+m1)/2;
  53. w2 = abs(m0-mm1) + abs(m0+mm1)/2;
  54. s[1] = (w1*m0 + w2*m1)/(w1+w2);
  55. if (isnan(s[1])) {
  56. s[1] = 0;
  57. }
  58. for (decltype(s.size()) i = 2; i < s.size()-2; ++i) {
  59. Real mim2 = (y[i-1]-y[i-2])/(x[i-1]-x[i-2]);
  60. Real mim1 = (y[i ]-y[i-1])/(x[i ]-x[i-1]);
  61. Real mi = (y[i+1]-y[i ])/(x[i+1]-x[i ]);
  62. Real mip1 = (y[i+2]-y[i+1])/(x[i+2]-x[i+1]);
  63. w1 = abs(mip1-mi) + abs(mip1+mi)/2;
  64. w2 = abs(mim1-mim2) + abs(mim1+mim2)/2;
  65. s[i] = (w1*mim1 + w2*mi)/(w1+w2);
  66. if (isnan(s[i])) {
  67. s[i] = 0;
  68. }
  69. }
  70. // Quadratic extrapolation at the other end:
  71. decltype(s.size()) n = s.size();
  72. Real mnm4 = (y[n-3]-y[n-4])/(x[n-3]-x[n-4]);
  73. Real mnm3 = (y[n-2]-y[n-3])/(x[n-2]-x[n-3]);
  74. Real mnm2 = (y[n-1]-y[n-2])/(x[n-1]-x[n-2]);
  75. Real mnm1 = 2*mnm2 - mnm3;
  76. Real mn = 2*mnm1 - mnm2;
  77. w1 = abs(mnm1 - mnm2) + abs(mnm1+mnm2)/2;
  78. w2 = abs(mnm3 - mnm4) + abs(mnm3+mnm4)/2;
  79. s[n-2] = (w1*mnm3 + w2*mnm2)/(w1 + w2);
  80. if (isnan(s[n-2])) {
  81. s[n-2] = 0;
  82. }
  83. w1 = abs(mn - mnm1) + abs(mn+mnm1)/2;
  84. w2 = abs(mnm2 - mnm3) + abs(mnm2+mnm3)/2;
  85. if (isnan(right_endpoint_derivative))
  86. {
  87. s[n-1] = (w1*mnm2 + w2*mnm1)/(w1+w2);
  88. if (isnan(s[n-1])) {
  89. s[n-1] = 0;
  90. }
  91. }
  92. else
  93. {
  94. s[n-1] = right_endpoint_derivative;
  95. }
  96. impl_ = std::make_shared<detail::cubic_hermite_detail<RandomAccessContainer>>(std::move(x), std::move(y), std::move(s));
  97. }
  98. Real operator()(Real x) const {
  99. return impl_->operator()(x);
  100. }
  101. Real prime(Real x) const {
  102. return impl_->prime(x);
  103. }
  104. friend std::ostream& operator<<(std::ostream & os, const makima & m)
  105. {
  106. os << *m.impl_;
  107. return os;
  108. }
  109. void push_back(Real x, Real y) {
  110. using std::abs;
  111. using std::isnan;
  112. if (x <= impl_->x_.back()) {
  113. throw std::domain_error("Calling push_back must preserve the monotonicity of the x's");
  114. }
  115. impl_->x_.push_back(x);
  116. impl_->y_.push_back(y);
  117. impl_->dydx_.push_back(std::numeric_limits<Real>::quiet_NaN());
  118. // dydx_[n-2] was computed by extrapolation. Now dydx_[n-2] -> dydx_[n-3], and it can be computed by the same formula.
  119. decltype(impl_->size()) n = impl_->size();
  120. auto i = n - 3;
  121. Real mim2 = (impl_->y_[i-1]-impl_->y_[i-2])/(impl_->x_[i-1]-impl_->x_[i-2]);
  122. Real mim1 = (impl_->y_[i ]-impl_->y_[i-1])/(impl_->x_[i ]-impl_->x_[i-1]);
  123. Real mi = (impl_->y_[i+1]-impl_->y_[i ])/(impl_->x_[i+1]-impl_->x_[i ]);
  124. Real mip1 = (impl_->y_[i+2]-impl_->y_[i+1])/(impl_->x_[i+2]-impl_->x_[i+1]);
  125. Real w1 = abs(mip1-mi) + abs(mip1+mi)/2;
  126. Real w2 = abs(mim1-mim2) + abs(mim1+mim2)/2;
  127. impl_->dydx_[i] = (w1*mim1 + w2*mi)/(w1+w2);
  128. if (isnan(impl_->dydx_[i])) {
  129. impl_->dydx_[i] = 0;
  130. }
  131. Real mnm4 = (impl_->y_[n-3]-impl_->y_[n-4])/(impl_->x_[n-3]-impl_->x_[n-4]);
  132. Real mnm3 = (impl_->y_[n-2]-impl_->y_[n-3])/(impl_->x_[n-2]-impl_->x_[n-3]);
  133. Real mnm2 = (impl_->y_[n-1]-impl_->y_[n-2])/(impl_->x_[n-1]-impl_->x_[n-2]);
  134. Real mnm1 = 2*mnm2 - mnm3;
  135. Real mn = 2*mnm1 - mnm2;
  136. w1 = abs(mnm1 - mnm2) + abs(mnm1+mnm2)/2;
  137. w2 = abs(mnm3 - mnm4) + abs(mnm3+mnm4)/2;
  138. impl_->dydx_[n-2] = (w1*mnm3 + w2*mnm2)/(w1 + w2);
  139. if (isnan(impl_->dydx_[n-2])) {
  140. impl_->dydx_[n-2] = 0;
  141. }
  142. w1 = abs(mn - mnm1) + abs(mn+mnm1)/2;
  143. w2 = abs(mnm2 - mnm3) + abs(mnm2+mnm3)/2;
  144. impl_->dydx_[n-1] = (w1*mnm2 + w2*mnm1)/(w1+w2);
  145. if (isnan(impl_->dydx_[n-1])) {
  146. impl_->dydx_[n-1] = 0;
  147. }
  148. }
  149. private:
  150. std::shared_ptr<detail::cubic_hermite_detail<RandomAccessContainer>> impl_;
  151. };
  152. }
  153. }
  154. }
  155. #endif