opm-common
UniformXTabulated2DFunction.hpp
Go to the documentation of this file.
1 // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 // vi: set et ts=4 sw=4 sts=4:
3 /*
4  This file is part of the Open Porous Media project (OPM).
5 
6  OPM is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 2 of the License, or
9  (at your option) any later version.
10 
11  OPM is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with OPM. If not, see <http://www.gnu.org/licenses/>.
18 
19  Consult the COPYING file in the top-level source directory of this
20  module for the precise wording of the license and the list of
21  copyright holders.
22 */
28 #ifndef OPM_UNIFORM_X_TABULATED_2D_FUNCTION_HPP
29 #define OPM_UNIFORM_X_TABULATED_2D_FUNCTION_HPP
30 
32 
35 
36 #include <cassert>
37 #include <cmath>
38 #include <cstddef>
39 #include <iosfwd>
40 #include <limits>
41 #include <tuple>
42 #include <type_traits>
43 #include <vector>
44 
45 namespace Opm {
54 template <class Scalar>
56 {
57 public:
58  typedef std::tuple</*x=*/Scalar, /*y=*/Scalar, /*value=*/Scalar> SamplePoint;
59 
71  LeftExtreme,
72  RightExtreme,
73  Vertical
74  };
75 
76  explicit UniformXTabulated2DFunction(const InterpolationPolicy interpolationGuide = Vertical)
77  : interpolationGuide_(interpolationGuide)
78  { }
79 
80  UniformXTabulated2DFunction(const std::vector<Scalar>& xPos,
81  const std::vector<Scalar>& yPos,
82  const std::vector<std::vector<SamplePoint>>& samples,
83  InterpolationPolicy interpolationGuide)
84  : samples_(samples)
85  , xPos_(xPos)
86  , yPos_(yPos)
87  , interpolationGuide_(interpolationGuide)
88  { }
89 
93  Scalar xMin() const
94  { return xPos_.front(); }
95 
99  Scalar xMax() const
100  { return xPos_.back(); }
101 
105  Scalar xAt(std::size_t i) const
106  { return xPos_[i]; }
107 
111  Scalar yAt(std::size_t i, std::size_t j) const
112  { return std::get<1>(samples_[i][j]); }
113 
117  Scalar valueAt(std::size_t i, std::size_t j) const
118  { return std::get<2>(samples_[i][j]); }
119 
123  std::size_t numX() const
124  { return xPos_.size(); }
125 
129  Scalar yMin(unsigned i) const
130  { return std::get<1>(samples_.at(i).front()); }
131 
135  Scalar yMax(unsigned i) const
136  { return std::get<1>(samples_.at(i).back()); }
137 
141  std::size_t numY(unsigned i) const
142  { return samples_.at(i).size(); }
143 
147  Scalar iToX(unsigned i) const
148  {
149  assert(i < numX());
150 
151  return xPos_.at(i);
152  }
153 
154  const std::vector<std::vector<SamplePoint>>& samples() const
155  {
156  return samples_;
157  }
158 
159  const std::vector<Scalar>& xPos() const
160  {
161  return xPos_;
162  }
163 
164  const std::vector<Scalar>& yPos() const
165  {
166  return yPos_;
167  }
168 
169  InterpolationPolicy interpolationGuide() const
170  {
171  return interpolationGuide_;
172  }
173 
177  Scalar jToY(unsigned i, unsigned j) const
178  {
179  assert(i < numX());
180  assert(std::size_t(j) < samples_[i].size());
181 
182  return std::get<1>(samples_.at(i).at(j));
183  }
184 
188  template <class Evaluation>
189  unsigned xSegmentIndex(const Evaluation& x,
190  [[maybe_unused]] bool extrapolate = false) const
191  {
192  assert(extrapolate || (xMin() <= x && x <= xMax()));
193 
194  // we need at least two sampling points!
195  assert(xPos_.size() >= 2);
196 
197  if (x <= xPos_[1])
198  return 0;
199  else if (x >= xPos_[xPos_.size() - 2])
200  return xPos_.size() - 2;
201  else {
202  assert(xPos_.size() >= 3);
203 
204  // bisection
205  unsigned lowerIdx = 1;
206  unsigned upperIdx = xPos_.size() - 2;
207  while (lowerIdx + 1 < upperIdx) {
208  unsigned pivotIdx = (lowerIdx + upperIdx) / 2;
209  if (x < xPos_[pivotIdx])
210  upperIdx = pivotIdx;
211  else
212  lowerIdx = pivotIdx;
213  }
214 
215  return lowerIdx;
216  }
217  }
218 
225  template <class Evaluation>
226  Evaluation xToAlpha(const Evaluation& x, unsigned segmentIdx) const
227  {
228  Scalar x1 = xPos_[segmentIdx];
229  Scalar x2 = xPos_[segmentIdx + 1];
230  return (x - x1)/(x2 - x1);
231  }
232 
236  template <class Evaluation>
237  unsigned ySegmentIndex(const Evaluation& y, unsigned xSampleIdx,
238  [[maybe_unused]] bool extrapolate = false) const
239  {
240  assert(xSampleIdx < numX());
241  const auto& colSamplePoints = samples_.at(xSampleIdx);
242 
243  assert(colSamplePoints.size() >= 2);
244  assert(extrapolate || (yMin(xSampleIdx) <= y && y <= yMax(xSampleIdx)));
245 
246  if (y <= std::get<1>(colSamplePoints[1]))
247  return 0;
248  else if (y >= std::get<1>(colSamplePoints[colSamplePoints.size() - 2]))
249  return colSamplePoints.size() - 2;
250  else {
251  assert(colSamplePoints.size() >= 3);
252 
253  // bisection
254  unsigned lowerIdx = 1;
255  unsigned upperIdx = colSamplePoints.size() - 2;
256  while (lowerIdx + 1 < upperIdx) {
257  unsigned pivotIdx = (lowerIdx + upperIdx) / 2;
258  if (y < std::get<1>(colSamplePoints[pivotIdx]))
259  upperIdx = pivotIdx;
260  else
261  lowerIdx = pivotIdx;
262  }
263 
264  return lowerIdx;
265  }
266  }
267 
274  template <class Evaluation>
275  Evaluation yToBeta(const Evaluation& y, unsigned xSampleIdx, unsigned ySegmentIdx) const
276  {
277  assert(xSampleIdx < numX());
278  assert(ySegmentIdx < numY(xSampleIdx) - 1);
279 
280  const auto& colSamplePoints = samples_.at(xSampleIdx);
281 
282  Scalar y1 = std::get<1>(colSamplePoints[ySegmentIdx]);
283  Scalar y2 = std::get<1>(colSamplePoints[ySegmentIdx + 1]);
284 
285  return (y - y1)/(y2 - y1);
286  }
287 
291  template <class Evaluation>
292  bool applies(const Evaluation& x, const Evaluation& y) const
293  {
294  if (x < xMin() || xMax() < x)
295  return false;
296 
297  unsigned i = xSegmentIndex(x, /*extrapolate=*/false);
298  Scalar alpha = xToAlpha(decay<Scalar>(x), i);
299 
300  const auto& col1SamplePoints = samples_.at(i);
301  const auto& col2SamplePoints = samples_.at(i + 1);
302 
303  Scalar minY =
304  alpha*std::get<1>(col1SamplePoints.front()) +
305  (1 - alpha)*std::get<1>(col2SamplePoints.front());
306 
307  Scalar maxY =
308  alpha*std::get<1>(col1SamplePoints.back()) +
309  (1 - alpha)*std::get<1>(col2SamplePoints.back());
310 
311  return minY <= y && y <= maxY;
312  }
319  template <class Evaluation>
320  Evaluation eval(const Evaluation& x, const Evaluation& y, bool extrapolate=false) const
321  {
322  Evaluation alpha, beta1, beta2;
323  unsigned i, j1, j2;
324  findPoints(i, j1, j2, alpha, beta1, beta2, x, y, extrapolate);
325  return eval(i, j1, j2, alpha, beta1, beta2);
326  }
327 
328  template <class Evaluation>
329  void findPoints(unsigned& i,
330  unsigned& j1,
331  unsigned& j2,
332  Evaluation& alpha,
333  Evaluation& beta1,
334  Evaluation& beta2,
335  const Evaluation& x,
336  const Evaluation& y,
337  bool extrapolate) const
338  {
339 #ifndef NDEBUG
340  if (!extrapolate && !applies(x, y)) {
341  if constexpr (std::is_floating_point_v<Evaluation>) {
342  throw NumericalProblem("Attempt to get undefined table value (" +
343  std::to_string(x) + ", " +
344  std::to_string(y) + ")");
345  } else {
346  throw NumericalProblem("Attempt to get undefined table value (" +
347  std::to_string(x.value()) + ", " +
348  std::to_string(y.value()) + ")");
349  }
350  };
351 #endif
352 
353  // bi-linear interpolation: first, calculate the x and y indices in the lookup
354  // table ...
355  i = xSegmentIndex(x, extrapolate);
356  alpha = xToAlpha(x, i);
357  // The 'shift' is used to shift the points used to interpolate within
358  // the (i) and (i+1) sets of sample points, so that when approaching
359  // the boundary of the domain given by the samples, one gets the same
360  // value as one would get by interpolating along the boundary curve
361  // itself.
362  Evaluation shift = 0.0;
363  if (interpolationGuide_ == InterpolationPolicy::Vertical) {
364  // Shift is zero, no need to reset it.
365  } else {
366  // find upper and lower y value
367  if (interpolationGuide_ == InterpolationPolicy::LeftExtreme) {
368  // The domain is above the boundary curve, up to y = infinity.
369  // The shift is therefore the same for all values of y.
370  shift = yPos_[i+1] - yPos_[i];
371  } else {
372  assert(interpolationGuide_ == InterpolationPolicy::RightExtreme);
373  // The domain is below the boundary curve, down to y = 0.
374  // The shift is therefore no longer the the same for all
375  // values of y, since at y = 0 the shift must be zero.
376  // The shift is computed by linear interpolation between
377  // the maximal value at the domain boundary curve, and zero.
378  shift = yPos_[i+1] - yPos_[i];
379  auto yEnd = yPos_[i]*(1.0 - alpha) + yPos_[i+1]*alpha;
380  if (yEnd > 0.) {
381  shift = shift * y / yEnd;
382  } else {
383  shift = 0.;
384  }
385  }
386  }
387  auto yLower = y - alpha*shift;
388  auto yUpper = y + (1-alpha)*shift;
389 
390  j1 = ySegmentIndex(yLower, i, extrapolate);
391  j2 = ySegmentIndex(yUpper, i + 1, extrapolate);
392  beta1 = yToBeta(yLower, i, j1);
393  beta2 = yToBeta(yUpper, i + 1, j2);
394  }
395 
396  template <class Evaluation>
397  Evaluation eval(const unsigned& i, const unsigned& j1, const unsigned& j2, const Evaluation& alpha,const Evaluation& beta1,const Evaluation& beta2) const
398  {
399  // evaluate the two function values for the same y value ...
400  const Evaluation& s1 = valueAt(i, j1)*(1.0 - beta1) + valueAt(i, j1 + 1)*beta1;
401  const Evaluation& s2 = valueAt(i + 1, j2)*(1.0 - beta2) + valueAt(i + 1, j2 + 1)*beta2;
402 
403  Valgrind::CheckDefined(s1);
404  Valgrind::CheckDefined(s2);
405 
406  // ... and combine them using the x position
407  const Evaluation& result = s1*(1.0 - alpha) + s2*alpha;
408  Valgrind::CheckDefined(result);
409 
410  return result;
411  }
412 
418  std::size_t appendXPos(Scalar nextX)
419  {
420  if (xPos_.empty() || xPos_.back() < nextX) {
421  xPos_.push_back(nextX);
422  yPos_.push_back(std::numeric_limits<Scalar>::lowest() / 2);
423  samples_.push_back({});
424  return xPos_.size() - 1;
425  }
426  else if (xPos_.front() > nextX) {
427  // this is slow, but so what?
428  xPos_.insert(xPos_.begin(), nextX);
429  yPos_.insert(yPos_.begin(), std::numeric_limits<Scalar>::lowest() / 2);
430  samples_.insert(samples_.begin(), std::vector<SamplePoint>());
431  return 0;
432  }
433  throw std::invalid_argument("Sampling points should be specified either monotonically "
434  "ascending or descending.");
435  }
436 
442  std::size_t appendSamplePoint(std::size_t i, Scalar y, Scalar value)
443  {
444  assert(i < numX());
445  Scalar x = iToX(i);
446  if (samples_[i].empty()) {
447  samples_[i].emplace_back(x, y, value);
448  yPos_[i] = y;
449  return 0;
450  }
451  else if (std::get<1>(samples_[i].back()) < y) {
452  samples_[i].emplace_back(x, y, value);
453  if (interpolationGuide_ == InterpolationPolicy::RightExtreme) {
454  yPos_[i] = y;
455  }
456  return samples_[i].size() - 1;
457  }
458  else if (std::get<1>(samples_[i].front()) > y) {
459  // slow, but we still don't care...
460  samples_[i].emplace(samples_[i].begin(), x, y, value);
461  if (interpolationGuide_ == InterpolationPolicy::LeftExtreme) {
462  yPos_[i] = y;
463  }
464  return 0;
465  }
466 
467  throw std::invalid_argument("Sampling points must be specified in either monotonically "
468  "ascending or descending order.");
469  }
470 
477  void print(std::ostream& os) const;
478 
479  bool operator==(const UniformXTabulated2DFunction<Scalar>& data) const {
480  return this->xPos() == data.xPos() &&
481  this->yPos() == data.yPos() &&
482  this->samples() == data.samples() &&
483  this->interpolationGuide() == data.interpolationGuide();
484  }
485 
486 private:
487  // the vector which contains the values of the sample points
488  // f(x_i, y_j). don't use this directly, use getSamplePoint(i,j)
489  // instead!
490  std::vector<std::vector<SamplePoint> > samples_;
491 
492  // the position of each vertical line on the x-axis
493  std::vector<Scalar> xPos_;
494  // the position on the y-axis of the guide point
495  std::vector<Scalar> yPos_;
496  InterpolationPolicy interpolationGuide_;
497 };
498 } // namespace Opm
499 
500 #endif
Scalar xAt(std::size_t i) const
Returns the value of the X coordinate of the sampling points.
Definition: UniformXTabulated2DFunction.hpp:105
Scalar xMax() const
Returns the maximum of the X coordinate of the sampling points.
Definition: UniformXTabulated2DFunction.hpp:99
Evaluation xToAlpha(const Evaluation &x, unsigned segmentIdx) const
Return the relative position of an x value in an intervall.
Definition: UniformXTabulated2DFunction.hpp:226
Definition: Exceptions.hpp:39
A traits class which provides basic mathematical functions for arbitrary scalar floating point values...
Scalar yMax(unsigned i) const
Returns the maximum of the Y coordinate of the sampling points for a given column.
Definition: UniformXTabulated2DFunction.hpp:135
void print(std::ostream &os) const
Print the table for debugging purposes.
Definition: UniformXTabulated2DFunction.cpp:34
std::size_t appendSamplePoint(std::size_t i, Scalar y, Scalar value)
Append a sample point.
Definition: UniformXTabulated2DFunction.hpp:442
Scalar valueAt(std::size_t i, std::size_t j) const
Returns the value of a sampling point.
Definition: UniformXTabulated2DFunction.hpp:117
unsigned xSegmentIndex(const Evaluation &x, [[maybe_unused]] bool extrapolate=false) const
Return the interval index of a given position on the x-axis.
Definition: UniformXTabulated2DFunction.hpp:189
Provides the OPM specific exception classes.
Evaluation eval(const Evaluation &x, const Evaluation &y, bool extrapolate=false) const
Evaluate the function at a given (x,y) position.
Definition: UniformXTabulated2DFunction.hpp:320
Scalar xMin() const
Returns the minimum of the X coordinate of the sampling points.
Definition: UniformXTabulated2DFunction.hpp:93
std::size_t numX() const
Returns the number of sampling points in X direction.
Definition: UniformXTabulated2DFunction.hpp:123
Implements a scalar function that depends on two variables and which is sampled uniformly in the X di...
Definition: UniformXTabulated2DFunction.hpp:55
Scalar yAt(std::size_t i, std::size_t j) const
Returns the value of the Y coordinate of a sampling point.
Definition: UniformXTabulated2DFunction.hpp:111
This class implements a small container which holds the transmissibility mulitpliers for all the face...
Definition: Exceptions.hpp:30
std::size_t appendXPos(Scalar nextX)
Set the x-position of a vertical line.
Definition: UniformXTabulated2DFunction.hpp:418
InterpolationPolicy
Indicates how interpolation will be performed.
Definition: UniformXTabulated2DFunction.hpp:70
Scalar jToY(unsigned i, unsigned j) const
Return the position on the y-axis of the j-th interval.
Definition: UniformXTabulated2DFunction.hpp:177
Evaluation yToBeta(const Evaluation &y, unsigned xSampleIdx, unsigned ySegmentIdx) const
Return the relative position of an y value in an interval.
Definition: UniformXTabulated2DFunction.hpp:275
unsigned ySegmentIndex(const Evaluation &y, unsigned xSampleIdx, [[maybe_unused]] bool extrapolate=false) const
Return the interval index of a given position on the y-axis.
Definition: UniformXTabulated2DFunction.hpp:237
bool applies(const Evaluation &x, const Evaluation &y) const
Returns true iff a coordinate lies in the tabulated range.
Definition: UniformXTabulated2DFunction.hpp:292
std::size_t numY(unsigned i) const
Returns the number of sampling points in Y direction a given column.
Definition: UniformXTabulated2DFunction.hpp:141
Some templates to wrap the valgrind client request macros.
Scalar iToX(unsigned i) const
Return the position on the x-axis of the i-th interval.
Definition: UniformXTabulated2DFunction.hpp:147
Scalar yMin(unsigned i) const
Returns the minimum of the Y coordinate of the sampling points for a given column.
Definition: UniformXTabulated2DFunction.hpp:129