barycentric_rational_detail.hpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /*
  2. * Copyright Nick Thompson, 2017
  3. * Use, modification and distribution are subject to the
  4. * Boost Software License, Version 1.0. (See accompanying file
  5. * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. */
  7. #ifndef BOOST_MATH_INTERPOLATORS_BARYCENTRIC_RATIONAL_DETAIL_HPP
  8. #define BOOST_MATH_INTERPOLATORS_BARYCENTRIC_RATIONAL_DETAIL_HPP
  9. #include <vector>
  10. #include <utility> // for std::move
  11. #include <algorithm> // for std::is_sorted
  12. #include <boost/lexical_cast.hpp>
  13. #include <boost/math/special_functions/fpclassify.hpp>
  14. #include <boost/core/demangle.hpp>
  15. #include <boost/assert.hpp>
  16. namespace boost{ namespace math{ namespace detail{
  17. template<class Real>
  18. class barycentric_rational_imp
  19. {
  20. public:
  21. template <class InputIterator1, class InputIterator2>
  22. barycentric_rational_imp(InputIterator1 start_x, InputIterator1 end_x, InputIterator2 start_y, size_t approximation_order = 3);
  23. barycentric_rational_imp(std::vector<Real>&& x, std::vector<Real>&& y, size_t approximation_order = 3);
  24. Real operator()(Real x) const;
  25. Real prime(Real x) const;
  26. // The barycentric weights are not really that interesting; except to the unit tests!
  27. Real weight(size_t i) const { return m_w[i]; }
  28. std::vector<Real>&& return_x()
  29. {
  30. return std::move(m_x);
  31. }
  32. std::vector<Real>&& return_y()
  33. {
  34. return std::move(m_y);
  35. }
  36. private:
  37. void calculate_weights(size_t approximation_order);
  38. std::vector<Real> m_x;
  39. std::vector<Real> m_y;
  40. std::vector<Real> m_w;
  41. };
  42. template <class Real>
  43. template <class InputIterator1, class InputIterator2>
  44. barycentric_rational_imp<Real>::barycentric_rational_imp(InputIterator1 start_x, InputIterator1 end_x, InputIterator2 start_y, size_t approximation_order)
  45. {
  46. std::ptrdiff_t n = std::distance(start_x, end_x);
  47. if (approximation_order >= (std::size_t)n)
  48. {
  49. throw std::domain_error("Approximation order must be < data length.");
  50. }
  51. // Big sad memcpy.
  52. m_x.resize(n);
  53. m_y.resize(n);
  54. for(unsigned i = 0; start_x != end_x; ++start_x, ++start_y, ++i)
  55. {
  56. // But if we're going to do a memcpy, we can do some error checking which is inexpensive relative to the copy:
  57. if(boost::math::isnan(*start_x))
  58. {
  59. std::string msg = std::string("x[") + boost::lexical_cast<std::string>(i) + "] is a NAN";
  60. throw std::domain_error(msg);
  61. }
  62. if(boost::math::isnan(*start_y))
  63. {
  64. std::string msg = std::string("y[") + boost::lexical_cast<std::string>(i) + "] is a NAN";
  65. throw std::domain_error(msg);
  66. }
  67. m_x[i] = *start_x;
  68. m_y[i] = *start_y;
  69. }
  70. calculate_weights(approximation_order);
  71. }
  72. template <class Real>
  73. barycentric_rational_imp<Real>::barycentric_rational_imp(std::vector<Real>&& x, std::vector<Real>&& y,size_t approximation_order) : m_x(std::move(x)), m_y(std::move(y))
  74. {
  75. BOOST_ASSERT_MSG(m_x.size() == m_y.size(), "There must be the same number of abscissas and ordinates.");
  76. BOOST_ASSERT_MSG(approximation_order < m_x.size(), "Approximation order must be < data length.");
  77. BOOST_ASSERT_MSG(std::is_sorted(m_x.begin(), m_x.end()), "The abscissas must be listed in increasing order x[0] < x[1] < ... < x[n-1].");
  78. calculate_weights(approximation_order);
  79. }
  80. template<class Real>
  81. void barycentric_rational_imp<Real>::calculate_weights(size_t approximation_order)
  82. {
  83. using std::abs;
  84. int64_t n = m_x.size();
  85. m_w.resize(n, 0);
  86. for(int64_t k = 0; k < n; ++k)
  87. {
  88. int64_t i_min = (std::max)(k - (int64_t) approximation_order, (int64_t) 0);
  89. int64_t i_max = k;
  90. if (k >= n - (std::ptrdiff_t)approximation_order)
  91. {
  92. i_max = n - approximation_order - 1;
  93. }
  94. for(int64_t i = i_min; i <= i_max; ++i)
  95. {
  96. Real inv_product = 1;
  97. int64_t j_max = (std::min)(static_cast<int64_t>(i + approximation_order), static_cast<int64_t>(n - 1));
  98. for(int64_t j = i; j <= j_max; ++j)
  99. {
  100. if (j == k)
  101. {
  102. continue;
  103. }
  104. Real diff = m_x[k] - m_x[j];
  105. using std::numeric_limits;
  106. if (abs(diff) < (numeric_limits<Real>::min)())
  107. {
  108. std::string msg = std::string("Spacing between x[")
  109. + boost::lexical_cast<std::string>(k) + std::string("] and x[")
  110. + boost::lexical_cast<std::string>(i) + std::string("] is ")
  111. + boost::lexical_cast<std::string>(diff) + std::string(", which is smaller than the epsilon of ")
  112. + boost::core::demangle(typeid(Real).name());
  113. throw std::logic_error(msg);
  114. }
  115. inv_product *= diff;
  116. }
  117. if (i % 2 == 0)
  118. {
  119. m_w[k] += 1/inv_product;
  120. }
  121. else
  122. {
  123. m_w[k] -= 1/inv_product;
  124. }
  125. }
  126. }
  127. }
  128. template<class Real>
  129. Real barycentric_rational_imp<Real>::operator()(Real x) const
  130. {
  131. Real numerator = 0;
  132. Real denominator = 0;
  133. for(size_t i = 0; i < m_x.size(); ++i)
  134. {
  135. // Presumably we should see if the accuracy is improved by using ULP distance of say, 5 here, instead of testing for floating point equality.
  136. // However, it has been shown that if x approx x_i, but x != x_i, then inaccuracy in the numerator cancels the inaccuracy in the denominator,
  137. // and the result is fairly accurate. See: http://epubs.siam.org/doi/pdf/10.1137/S0036144502417715
  138. if (x == m_x[i])
  139. {
  140. return m_y[i];
  141. }
  142. Real t = m_w[i]/(x - m_x[i]);
  143. numerator += t*m_y[i];
  144. denominator += t;
  145. }
  146. return numerator/denominator;
  147. }
  148. /*
  149. * A formula for computing the derivative of the barycentric representation is given in
  150. * "Some New Aspects of Rational Interpolation", by Claus Schneider and Wilhelm Werner,
  151. * Mathematics of Computation, v47, number 175, 1986.
  152. * http://www.ams.org/journals/mcom/1986-47-175/S0025-5718-1986-0842136-8/S0025-5718-1986-0842136-8.pdf
  153. * and reviewed in
  154. * Recent developments in barycentric rational interpolation
  155. * Jean-Paul Berrut, Richard Baltensperger and Hans D. Mittelmann
  156. *
  157. * Is it possible to complete this in one pass through the data?
  158. */
  159. template<class Real>
  160. Real barycentric_rational_imp<Real>::prime(Real x) const
  161. {
  162. Real rx = this->operator()(x);
  163. Real numerator = 0;
  164. Real denominator = 0;
  165. for(size_t i = 0; i < m_x.size(); ++i)
  166. {
  167. if (x == m_x[i])
  168. {
  169. Real sum = 0;
  170. for (size_t j = 0; j < m_x.size(); ++j)
  171. {
  172. if (j == i)
  173. {
  174. continue;
  175. }
  176. sum += m_w[j]*(m_y[i] - m_y[j])/(m_x[i] - m_x[j]);
  177. }
  178. return -sum/m_w[i];
  179. }
  180. Real t = m_w[i]/(x - m_x[i]);
  181. Real diff = (rx - m_y[i])/(x-m_x[i]);
  182. numerator += t*diff;
  183. denominator += t;
  184. }
  185. return numerator/denominator;
  186. }
  187. }}}
  188. #endif