bilinear_uniform_detail.hpp 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. // Copyright Nick Thompson, 2021
  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. #ifndef BOOST_MATH_INTERPOLATORS_BILINEAR_UNIFORM_DETAIL_HPP
  7. #define BOOST_MATH_INTERPOLATORS_BILINEAR_UNIFORM_DETAIL_HPP
  8. #include <stdexcept>
  9. #include <iostream>
  10. #include <string>
  11. #include <limits>
  12. #include <cmath>
  13. #include <utility>
  14. namespace boost::math::interpolators::detail {
  15. template <class RandomAccessContainer>
  16. class bilinear_uniform_imp
  17. {
  18. public:
  19. using Real = typename RandomAccessContainer::value_type;
  20. using Z = typename RandomAccessContainer::size_type;
  21. bilinear_uniform_imp(RandomAccessContainer && fieldData, Z rows, Z cols, Real dx = 1, Real dy = 1, Real x0 = 0, Real y0 = 0)
  22. {
  23. using std::to_string;
  24. if(fieldData.size() != rows*cols)
  25. {
  26. std::string err = std::string(__FILE__) + ":" + to_string(__LINE__)
  27. + " The field data must have rows*cols elements. There are " + to_string(rows) + " rows and " + to_string(cols) + " columns but " + to_string(fieldData.size()) + " elements in the field data.";
  28. throw std::logic_error(err);
  29. }
  30. if (rows < 2) {
  31. throw std::logic_error("There must be at least two rows of data for bilinear interpolation to be well-defined.");
  32. }
  33. if (cols < 2) {
  34. throw std::logic_error("There must be at least two columns of data for bilinear interpolation to be well-defined.");
  35. }
  36. fieldData_ = std::move(fieldData);
  37. rows_ = rows;
  38. cols_ = cols;
  39. x0_ = x0;
  40. y0_ = y0;
  41. dx_ = dx;
  42. dy_ = dy;
  43. if (dx_ <= 0) {
  44. std::string err = std::string(__FILE__) + ":" + to_string(__LINE__) + " dx = " + to_string(dx) + ", but dx > 0 is required. Are the arguments out of order?";
  45. throw std::logic_error(err);
  46. }
  47. if (dy_ <= 0) {
  48. std::string err = std::string(__FILE__) + ":" + to_string(__LINE__) + " dy = " + to_string(dy) + ", but dy > 0 is required. Are the arguments out of order?";
  49. throw std::logic_error(err);
  50. }
  51. }
  52. Real operator()(Real x, Real y) const
  53. {
  54. using std::floor;
  55. if (x > x0_ + (cols_ - 1)*dx_ || x < x0_) {
  56. std::cerr << __FILE__ << ":" << __LINE__ << ":" << __func__ << "\n";
  57. std::cerr << "Querying the bilinear_uniform interpolator at (x,y) = (" << x << ", " << y << ") is not allowed.\n";
  58. std::cerr << "x must lie in the interval [" << x0_ << ", " << x0_ + (cols_ -1)*dx_ << "]\n";
  59. return std::numeric_limits<Real>::quiet_NaN();
  60. }
  61. if (y > y0_ + (rows_ - 1)*dy_ || y < y0_) {
  62. std::cerr << __FILE__ << ":" << __LINE__ << ":" << __func__ << "\n";
  63. std::cerr << "Querying the bilinear_uniform interpolator at (x,y) = (" << x << ", " << y << ") is not allowed.\n";
  64. std::cerr << "y must lie in the interval [" << y0_ << ", " << y0_ + (rows_ -1)*dy_ << "]\n";
  65. return std::numeric_limits<Real>::quiet_NaN();
  66. }
  67. Real s = (x - x0_)/dx_;
  68. Real s0 = floor(s);
  69. Real t = (y - y0_)/dy_;
  70. Real t0 = floor(t);
  71. auto xidx = static_cast<Z>(s0);
  72. auto yidx = static_cast<Z>(t0);
  73. Z idx = yidx*cols_ + xidx;
  74. Real alpha = s - s0;
  75. Real beta = t - t0;
  76. Real fhi;
  77. // If alpha = 0, then we can segfault by reading fieldData_[idx+1]:
  78. if (alpha <= 2*s0*std::numeric_limits<Real>::epsilon()) {
  79. fhi = fieldData_[idx];
  80. } else {
  81. fhi = (1 - alpha)*fieldData_[idx] + alpha*fieldData_[idx + 1];
  82. }
  83. // Again, we can get OOB access without this check.
  84. // This corresponds to interpolation over a line segment aligned with the axes.
  85. if (beta <= 2*t0*std::numeric_limits<Real>::epsilon()) {
  86. return fhi;
  87. }
  88. auto bottom_left = fieldData_[idx + cols_];
  89. Real flo;
  90. if (alpha <= 2*s0*std::numeric_limits<Real>::epsilon()) {
  91. flo = bottom_left;
  92. }
  93. else {
  94. flo = (1 - alpha)*bottom_left + alpha*fieldData_[idx + cols_ + 1];
  95. }
  96. // Convex combination over vertical to get the value:
  97. return (1 - beta)*fhi + beta*flo;
  98. }
  99. friend std::ostream& operator<<(std::ostream& out, bilinear_uniform_imp<RandomAccessContainer> const & bu) {
  100. out << "(x0, y0) = (" << bu.x0_ << ", " << bu.y0_ << "), (dx, dy) = (" << bu.dx_ << ", " << bu.dy_ << "), ";
  101. out << "(xf, yf) = (" << bu.x0_ + (bu.cols_ - 1)*bu.dx_ << ", " << bu.y0_ + (bu.rows_ - 1)*bu.dy_ << ")\n";
  102. for (Z j = 0; j < bu.rows_; ++j) {
  103. out << "{";
  104. for (Z i = 0; i < bu.cols_ - 1; ++i) {
  105. out << bu.fieldData_[j*bu.cols_ + i] << ", ";
  106. }
  107. out << bu.fieldData_[j*bu.cols_ + bu.cols_ - 1] << "}\n";
  108. }
  109. return out;
  110. }
  111. private:
  112. RandomAccessContainer fieldData_;
  113. Z rows_;
  114. Z cols_;
  115. Real x0_;
  116. Real y0_;
  117. Real dx_;
  118. Real dy_;
  119. };
  120. }
  121. #endif