My Project
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 
34 
35 #include <iostream>
36 #include <vector>
37 #include <limits>
38 #include <tuple>
39 #include <sstream>
40 #include <cassert>
41 #include <cmath>
42 
43 namespace Opm {
52 template <class Scalar>
54 {
55 public:
56  typedef std::tuple</*x=*/Scalar, /*y=*/Scalar, /*value=*/Scalar> SamplePoint;
57 
69  LeftExtreme,
70  RightExtreme,
71  Vertical
72  };
73 
74  explicit UniformXTabulated2DFunction(const InterpolationPolicy interpolationGuide = Vertical)
75  : interpolationGuide_(interpolationGuide)
76  { }
77 
78  UniformXTabulated2DFunction(const std::vector<Scalar>& xPos,
79  const std::vector<Scalar>& yPos,
80  const std::vector<std::vector<SamplePoint>>& samples,
81  InterpolationPolicy interpolationGuide)
82  : samples_(samples)
83  , xPos_(xPos)
84  , yPos_(yPos)
85  , interpolationGuide_(interpolationGuide)
86  { }
87 
91  Scalar xMin() const
92  { return xPos_.front(); }
93 
97  Scalar xMax() const
98  { return xPos_.back(); }
99 
103  Scalar xAt(size_t i) const
104  { return xPos_[i]; }
105 
109  Scalar yAt(size_t i, size_t j) const
110  { return std::get<1>(samples_[i][j]); }
111 
115  Scalar valueAt(size_t i, size_t j) const
116  { return std::get<2>(samples_[i][j]); }
117 
121  size_t numX() const
122  { return xPos_.size(); }
123 
127  Scalar yMin(unsigned i) const
128  { return std::get<1>(samples_.at(i).front()); }
129 
133  Scalar yMax(unsigned i) const
134  { return std::get<1>(samples_.at(i).back()); }
135 
139  size_t numY(unsigned i) const
140  { return samples_.at(i).size(); }
141 
145  Scalar iToX(unsigned i) const
146  {
147  assert(i < numX());
148 
149  return xPos_.at(i);
150  }
151 
152  const std::vector<std::vector<SamplePoint>>& samples() const
153  {
154  return samples_;
155  }
156 
157  const std::vector<Scalar>& xPos() const
158  {
159  return xPos_;
160  }
161 
162  const std::vector<Scalar>& yPos() const
163  {
164  return yPos_;
165  }
166 
167  InterpolationPolicy interpolationGuide() const
168  {
169  return interpolationGuide_;
170  }
171 
175  Scalar jToY(unsigned i, unsigned j) const
176  {
177  assert(i < numX());
178  assert(size_t(j) < samples_[i].size());
179 
180  return std::get<1>(samples_.at(i).at(j));
181  }
182 
186  template <class Evaluation>
187  unsigned xSegmentIndex(const Evaluation& x,
188  [[maybe_unused]] bool extrapolate = false) const
189  {
190  assert(extrapolate || (xMin() <= x && x <= xMax()));
191 
192  // we need at least two sampling points!
193  assert(xPos_.size() >= 2);
194 
195  if (x <= xPos_[1])
196  return 0;
197  else if (x >= xPos_[xPos_.size() - 2])
198  return xPos_.size() - 2;
199  else {
200  assert(xPos_.size() >= 3);
201 
202  // bisection
203  unsigned lowerIdx = 1;
204  unsigned upperIdx = xPos_.size() - 2;
205  while (lowerIdx + 1 < upperIdx) {
206  unsigned pivotIdx = (lowerIdx + upperIdx) / 2;
207  if (x < xPos_[pivotIdx])
208  upperIdx = pivotIdx;
209  else
210  lowerIdx = pivotIdx;
211  }
212 
213  return lowerIdx;
214  }
215  }
216 
223  template <class Evaluation>
224  Evaluation xToAlpha(const Evaluation& x, unsigned segmentIdx) const
225  {
226  Scalar x1 = xPos_[segmentIdx];
227  Scalar x2 = xPos_[segmentIdx + 1];
228  return (x - x1)/(x2 - x1);
229  }
230 
234  template <class Evaluation>
235  unsigned ySegmentIndex(const Evaluation& y, unsigned xSampleIdx,
236  [[maybe_unused]] bool extrapolate = false) const
237  {
238  assert(xSampleIdx < numX());
239  const auto& colSamplePoints = samples_.at(xSampleIdx);
240 
241  assert(colSamplePoints.size() >= 2);
242  assert(extrapolate || (yMin(xSampleIdx) <= y && y <= yMax(xSampleIdx)));
243 
244  if (y <= std::get<1>(colSamplePoints[1]))
245  return 0;
246  else if (y >= std::get<1>(colSamplePoints[colSamplePoints.size() - 2]))
247  return colSamplePoints.size() - 2;
248  else {
249  assert(colSamplePoints.size() >= 3);
250 
251  // bisection
252  unsigned lowerIdx = 1;
253  unsigned upperIdx = colSamplePoints.size() - 2;
254  while (lowerIdx + 1 < upperIdx) {
255  unsigned pivotIdx = (lowerIdx + upperIdx) / 2;
256  if (y < std::get<1>(colSamplePoints[pivotIdx]))
257  upperIdx = pivotIdx;
258  else
259  lowerIdx = pivotIdx;
260  }
261 
262  return lowerIdx;
263  }
264  }
265 
272  template <class Evaluation>
273  Evaluation yToBeta(const Evaluation& y, unsigned xSampleIdx, unsigned ySegmentIdx) const
274  {
275  assert(xSampleIdx < numX());
276  assert(ySegmentIdx < numY(xSampleIdx) - 1);
277 
278  const auto& colSamplePoints = samples_.at(xSampleIdx);
279 
280  Scalar y1 = std::get<1>(colSamplePoints[ySegmentIdx]);
281  Scalar y2 = std::get<1>(colSamplePoints[ySegmentIdx + 1]);
282 
283  return (y - y1)/(y2 - y1);
284  }
285 
289  template <class Evaluation>
290  bool applies(const Evaluation& x, const Evaluation& y) const
291  {
292  if (x < xMin() || xMax() < x)
293  return false;
294 
295  unsigned i = xSegmentIndex(x, /*extrapolate=*/false);
296  Scalar alpha = xToAlpha(decay<Scalar>(x), i);
297 
298  const auto& col1SamplePoints = samples_.at(i);
299  const auto& col2SamplePoints = samples_.at(i + 1);
300 
301  Scalar minY =
302  alpha*std::get<1>(col1SamplePoints.front()) +
303  (1 - alpha)*std::get<1>(col2SamplePoints.front());
304 
305  Scalar maxY =
306  alpha*std::get<1>(col1SamplePoints.back()) +
307  (1 - alpha)*std::get<1>(col2SamplePoints.back());
308 
309  return minY <= y && y <= maxY;
310  }
317  template <class Evaluation>
318  Evaluation eval(const Evaluation& x, const Evaluation& y, bool extrapolate=false) const
319  {
320 #ifndef NDEBUG
321  if (!extrapolate && !applies(x, y)) {
322  std::ostringstream oss;
323  oss << "Attempt to get undefined table value (" << x << ", " << y << ")";
324  throw NumericalIssue(oss.str());
325  };
326 #endif
327 
328  // bi-linear interpolation: first, calculate the x and y indices in the lookup
329  // table ...
330  unsigned i = xSegmentIndex(x, extrapolate);
331  const Evaluation& alpha = xToAlpha(x, i);
332  // The 'shift' is used to shift the points used to interpolate within
333  // the (i) and (i+1) sets of sample points, so that when approaching
334  // the boundary of the domain given by the samples, one gets the same
335  // value as one would get by interpolating along the boundary curve
336  // itself.
337  Evaluation shift = 0.0;
338  if (interpolationGuide_ == InterpolationPolicy::Vertical) {
339  // Shift is zero, no need to reset it.
340  } else {
341  // find upper and lower y value
342  if (interpolationGuide_ == InterpolationPolicy::LeftExtreme) {
343  // The domain is above the boundary curve, up to y = infinity.
344  // The shift is therefore the same for all values of y.
345  shift = yPos_[i+1] - yPos_[i];
346  } else {
347  assert(interpolationGuide_ == InterpolationPolicy::RightExtreme);
348  // The domain is below the boundary curve, down to y = 0.
349  // The shift is therefore no longer the the same for all
350  // values of y, since at y = 0 the shift must be zero.
351  // The shift is computed by linear interpolation between
352  // the maximal value at the domain boundary curve, and zero.
353  shift = yPos_[i+1] - yPos_[i];
354  auto yEnd = yPos_[i]*(1.0 - alpha) + yPos_[i+1]*alpha;
355  if (yEnd > 0.) {
356  shift = shift * y / yEnd;
357  } else {
358  shift = 0.;
359  }
360  }
361  }
362  auto yLower = y - alpha*shift;
363  auto yUpper = y + (1-alpha)*shift;
364 
365  unsigned j1 = ySegmentIndex(yLower, i, extrapolate);
366  unsigned j2 = ySegmentIndex(yUpper, i + 1, extrapolate);
367  const Evaluation& beta1 = yToBeta(yLower, i, j1);
368  const Evaluation& beta2 = yToBeta(yUpper, i + 1, j2);
369 
370  // evaluate the two function values for the same y value ...
371  const Evaluation& s1 = valueAt(i, j1)*(1.0 - beta1) + valueAt(i, j1 + 1)*beta1;
372  const Evaluation& s2 = valueAt(i + 1, j2)*(1.0 - beta2) + valueAt(i + 1, j2 + 1)*beta2;
373 
374  Valgrind::CheckDefined(s1);
375  Valgrind::CheckDefined(s2);
376 
377  // ... and combine them using the x position
378  const Evaluation& result = s1*(1.0 - alpha) + s2*alpha;
379  Valgrind::CheckDefined(result);
380 
381  return result;
382  }
383 
389  size_t appendXPos(Scalar nextX)
390  {
391  if (xPos_.empty() || xPos_.back() < nextX) {
392  xPos_.push_back(nextX);
393  yPos_.push_back(-1e100);
394  samples_.push_back({});
395  return xPos_.size() - 1;
396  }
397  else if (xPos_.front() > nextX) {
398  // this is slow, but so what?
399  xPos_.insert(xPos_.begin(), nextX);
400  yPos_.insert(yPos_.begin(), -1e100);
401  samples_.insert(samples_.begin(), std::vector<SamplePoint>());
402  return 0;
403  }
404  throw std::invalid_argument("Sampling points should be specified either monotonically "
405  "ascending or descending.");
406  }
407 
413  size_t appendSamplePoint(size_t i, Scalar y, Scalar value)
414  {
415  assert(i < numX());
416  Scalar x = iToX(i);
417  if (samples_[i].empty() || std::get<1>(samples_[i].back()) < y) {
418  samples_[i].push_back(SamplePoint(x, y, value));
419  if (interpolationGuide_ == InterpolationPolicy::RightExtreme) {
420  yPos_[i] = y;
421  }
422  return samples_[i].size() - 1;
423  }
424  else if (std::get<1>(samples_[i].front()) > y) {
425  // slow, but we still don't care...
426  samples_[i].insert(samples_[i].begin(), SamplePoint(x, y, value));
427  if (interpolationGuide_ == InterpolationPolicy::LeftExtreme) {
428  yPos_[i] = y;
429  }
430  return 0;
431  }
432 
433  throw std::invalid_argument("Sampling points must be specified in either monotonically "
434  "ascending or descending order.");
435  }
436 
443  void print(std::ostream& os = std::cout) const
444  {
445  Scalar x0 = xMin();
446  Scalar x1 = xMax();
447  int m = numX();
448 
449  Scalar y0 = 1e30;
450  Scalar y1 = -1e30;
451  size_t n = 0;
452  for (int i = 0; i < m; ++ i) {
453  y0 = std::min(y0, yMin(i));
454  y1 = std::max(y1, yMax(i));
455  n = std::max(n, numY(i));
456  }
457 
458  m *= 3;
459  n *= 3;
460  for (int i = 0; i <= m; ++i) {
461  Scalar x = x0 + (x1 - x0)*i/m;
462  for (size_t j = 0; j <= n; ++j) {
463  Scalar y = y0 + (y1 - y0)*j/n;
464  os << x << " " << y << " " << eval(x, y) << "\n";
465  }
466  os << "\n";
467  }
468  }
469 
470  bool operator==(const UniformXTabulated2DFunction<Scalar>& data) const {
471  return this->xPos() == data.xPos() &&
472  this->yPos() == data.yPos() &&
473  this->samples() == data.samples() &&
474  this->interpolationGuide() == data.interpolationGuide();
475  }
476 
477 private:
478  // the vector which contains the values of the sample points
479  // f(x_i, y_j). don't use this directly, use getSamplePoint(i,j)
480  // instead!
481  std::vector<std::vector<SamplePoint> > samples_;
482 
483  // the position of each vertical line on the x-axis
484  std::vector<Scalar> xPos_;
485  // the position on the y-axis of the guide point
486  std::vector<Scalar> yPos_;
487  InterpolationPolicy interpolationGuide_;
488 };
489 } // namespace Opm
490 
491 #endif
Provides the opm-material specific exception classes.
A traits class which provides basic mathematical functions for arbitrary scalar floating point values...
Some templates to wrap the valgrind client request macros.
Definition: Exceptions.hpp:46
Implements a scalar function that depends on two variables and which is sampled uniformly in the X di...
Definition: UniformXTabulated2DFunction.hpp:54
Scalar xMax() const
Returns the maximum of the X coordinate of the sampling points.
Definition: UniformXTabulated2DFunction.hpp:97
size_t appendSamplePoint(size_t i, Scalar y, Scalar value)
Append a sample point.
Definition: UniformXTabulated2DFunction.hpp:413
Evaluation xToAlpha(const Evaluation &x, unsigned segmentIdx) const
Return the relative position of an x value in an intervall.
Definition: UniformXTabulated2DFunction.hpp:224
Evaluation yToBeta(const Evaluation &y, unsigned xSampleIdx, unsigned ySegmentIdx) const
Return the relative position of an y value in an interval.
Definition: UniformXTabulated2DFunction.hpp:273
Scalar xAt(size_t i) const
Returns the value of the X coordinate of the sampling points.
Definition: UniformXTabulated2DFunction.hpp:103
void print(std::ostream &os=std::cout) const
Print the table for debugging purposes.
Definition: UniformXTabulated2DFunction.hpp:443
Scalar jToY(unsigned i, unsigned j) const
Return the position on the y-axis of the j-th interval.
Definition: UniformXTabulated2DFunction.hpp:175
InterpolationPolicy
Indicates how interpolation will be performed.
Definition: UniformXTabulated2DFunction.hpp:68
Scalar valueAt(size_t i, size_t j) const
Returns the value of a sampling point.
Definition: UniformXTabulated2DFunction.hpp:115
Scalar xMin() const
Returns the minimum of the X coordinate of the sampling points.
Definition: UniformXTabulated2DFunction.hpp:91
Scalar yMin(unsigned i) const
Returns the minimum of the Y coordinate of the sampling points for a given column.
Definition: UniformXTabulated2DFunction.hpp:127
size_t numY(unsigned i) const
Returns the number of sampling points in Y direction a given column.
Definition: UniformXTabulated2DFunction.hpp:139
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:235
size_t numX() const
Returns the number of sampling points in X direction.
Definition: UniformXTabulated2DFunction.hpp:121
Scalar yMax(unsigned i) const
Returns the maximum of the Y coordinate of the sampling points for a given column.
Definition: UniformXTabulated2DFunction.hpp:133
Scalar iToX(unsigned i) const
Return the position on the x-axis of the i-th interval.
Definition: UniformXTabulated2DFunction.hpp:145
Scalar yAt(size_t i, size_t j) const
Returns the value of the Y coordinate of a sampling point.
Definition: UniformXTabulated2DFunction.hpp:109
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:187
size_t appendXPos(Scalar nextX)
Set the x-position of a vertical line.
Definition: UniformXTabulated2DFunction.hpp:389
Evaluation eval(const Evaluation &x, const Evaluation &y, bool extrapolate=false) const
Evaluate the function at a given (x,y) position.
Definition: UniformXTabulated2DFunction.hpp:318
bool applies(const Evaluation &x, const Evaluation &y) const
Returns true iff a coordinate lies in the tabulated range.
Definition: UniformXTabulated2DFunction.hpp:290