opm-common
Tabulated1DFunction.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 */
27 #ifndef OPM_TABULATED_1D_FUNCTION_HPP
28 #define OPM_TABULATED_1D_FUNCTION_HPP
29 
30 #include <opm/common/OpmLog/OpmLog.hpp>
32 
33 #include <algorithm>
34 #include <cassert>
35 #include <cstddef>
36 #include <iosfwd>
37 #include <stdexcept>
38 #include <vector>
39 
40 namespace Opm {
41 
42 struct SegmentIndex {
43  std::size_t value;
44 };
45 
50 template <class Scalar>
52 {
53 public:
60  {}
61 
70  template <class ScalarArrayX, class ScalarArrayY>
71  Tabulated1DFunction(std::size_t nSamples,
72  const ScalarArrayX& x,
73  const ScalarArrayY& y,
74  bool sortInputs = true)
75  { this->setXYArrays(nSamples, x, y, sortInputs); }
76 
86  template <class ScalarContainer>
87  Tabulated1DFunction(const ScalarContainer& x,
88  const ScalarContainer& y,
89  bool sortInputs = true)
90  { this->setXYContainers(x, y, sortInputs); }
91 
99  template <class PointContainer>
100  explicit Tabulated1DFunction(const PointContainer& points,
101  bool sortInputs = true)
102  { this->setContainerOfTuples(points, sortInputs); }
103 
109  template <class ScalarArrayX, class ScalarArrayY>
110  void setXYArrays(std::size_t nSamples,
111  const ScalarArrayX& x,
112  const ScalarArrayY& y,
113  bool sortInputs = true)
114  {
115  assert(nSamples > 1);
116 
117  resizeArrays_(nSamples);
118  for (std::size_t i = 0; i < nSamples; ++i) {
119  xValues_[i] = x[i];
120  yValues_[i] = y[i];
121  }
122 
123  if (sortInputs)
124  sortInput_();
125  else if (xValues_[0] > xValues_[numSamples() - 1])
126  reverseSamplingPoints_();
127  }
128 
134  template <class ScalarContainerX, class ScalarContainerY>
135  void setXYContainers(const ScalarContainerX& x,
136  const ScalarContainerY& y,
137  bool sortInputs = true)
138  {
139  assert(x.size() == y.size());
140 
141  resizeArrays_(x.size());
142  if (x.size() > 0) {
143  std::ranges::copy(x, xValues_.begin());
144  std::ranges::copy(y, yValues_.begin());
145 
146  if (sortInputs)
147  sortInput_();
148  else if (xValues_[0] > xValues_[numSamples() - 1])
149  reverseSamplingPoints_();
150  }
151  }
152 
156  template <class PointArray>
157  void setArrayOfPoints(std::size_t nSamples,
158  const PointArray& points,
159  bool sortInputs = true)
160  {
161  // a linear function with less than two sampling points? what an incredible
162  // bad idea!
163  assert(nSamples > 1);
164 
165  resizeArrays_(nSamples);
166  for (std::size_t i = 0; i < nSamples; ++i) {
167  xValues_[i] = points[i][0];
168  yValues_[i] = points[i][1];
169  }
170 
171  if (sortInputs)
172  sortInput_();
173  else if (xValues_[0] > xValues_[numSamples() - 1])
174  reverseSamplingPoints_();
175  }
176 
191  template <class XYContainer>
192  void setContainerOfTuples(const XYContainer& points,
193  bool sortInputs = true)
194  {
195  // a linear function with less than two sampling points? what an incredible
196  // bad idea!
197  assert(points.size() > 1);
198 
199  resizeArrays_(points.size());
200  typename XYContainer::const_iterator it = points.begin();
201  typename XYContainer::const_iterator endIt = points.end();
202  for (unsigned i = 0; it != endIt; ++i, ++it) {
203  xValues_[i] = std::get<0>(*it);
204  yValues_[i] = std::get<1>(*it);
205  }
206 
207  if (sortInputs)
208  sortInput_();
209  else if (xValues_[0] > xValues_[numSamples() - 1])
210  reverseSamplingPoints_();
211  }
212 
216  std::size_t numSamples() const
217  { return xValues_.size(); }
218 
222  Scalar xMin() const
223  { return xValues_[0]; }
224 
228  Scalar xMax() const
229  { return xValues_[numSamples() - 1]; }
230 
234  Scalar xAt(std::size_t i) const
235  { return xValues_[i]; }
236 
237  const std::vector<Scalar>& xValues() const
238  { return xValues_; }
239 
240  const std::vector<Scalar>& yValues() const
241  { return yValues_; }
242 
246  Scalar valueAt(std::size_t i) const
247  { return yValues_[i]; }
248 
252  template <class Evaluation>
253  bool applies(const Evaluation& x) const
254  { return xValues_[0] <= x && x <= xValues_[numSamples() - 1]; }
255 
265  template <class Evaluation>
266  Evaluation eval(const Evaluation& x, bool extrapolate = false) const
267  {
268  SegmentIndex segIdx = findSegmentIndex(x, extrapolate);
269  return eval(x, segIdx);
270  }
271 
272  template <class Evaluation>
273  Evaluation eval(const Evaluation& x, SegmentIndex segIdxIn) const
274  {
275  std::size_t segIdx = segIdxIn.value;
276  Scalar x0 = xValues_[segIdx];
277  Scalar x1 = xValues_[segIdx + 1];
278 
279  Scalar y0 = yValues_[segIdx];
280  Scalar y1 = yValues_[segIdx + 1];
281 
282  return y0 + (y1 - y0)*(x - x0)/(x1 - x0);
283  }
284 
296  template <class Evaluation>
297  Evaluation evalDerivative(const Evaluation& x, bool extrapolate = false) const
298  {
299  std::size_t segIdx = findSegmentIndex(x, extrapolate).value;
300  return evalDerivative_(x, segIdx);
301  }
302 
317  template <class Evaluation>
318  Evaluation evalSecondDerivative(const Evaluation&, bool = false) const
319  { return 0.0; }
320 
335  template <class Evaluation>
336  Evaluation evalThirdDerivative(const Evaluation&, bool = false) const
337  { return 0.0; }
338 
347  int monotonic(Scalar x0, Scalar x1,
348  [[maybe_unused]] bool extrapolate = false) const
349  {
350  assert(x0 != x1);
351 
352  // make sure that x0 is smaller than x1
353  if (x0 > x1)
354  std::swap(x0, x1);
355 
356  assert(x0 < x1);
357 
358  int r = 3;
359  if (x0 < xMin()) {
360  assert(extrapolate);
361 
362  x0 = xMin();
363  };
364 
365  std::size_t i = findSegmentIndex(x0, extrapolate).value;
366  if (xValues_[i + 1] >= x1) {
367  // interval is fully contained within a single function
368  // segment
369  updateMonotonicity_(i, r);
370  return r;
371  }
372 
373  // the first segment overlaps with the specified interval
374  // partially
375  updateMonotonicity_(i, r);
376  ++ i;
377 
378  // make sure that the segments which are completly in the
379  // interval [x0, x1] all exhibit the same monotonicity.
380  std::size_t iEnd = findSegmentIndex(x1, extrapolate).value;
381  for (; i < iEnd - 1; ++i) {
382  updateMonotonicity_(i, r);
383  if (!r)
384  return 0;
385  }
386 
387  // if the user asked for a part of the function which is
388  // extrapolated, we need to check the slope at the function's
389  // endpoint
390  if (x1 > xMax()) {
391  assert(extrapolate);
392 
393  Scalar m = evalDerivative_(xMax(), /*segmentIdx=*/numSamples() - 2);
394  if (m < 0)
395  return (r < 0 || r==3) ? -1 : 0;
396  else if (m > 0)
397  return r > 0 ? 1 : 0;
398 
399  return r;
400  }
401 
402  // check for the last segment
403  updateMonotonicity_(iEnd, r);
404 
405  return r;
406  }
407 
412  int monotonic() const
413  { return monotonic(xMin(), xMax()); }
414 
431  void printCSV(Scalar xi0, Scalar xi1, unsigned k, std::ostream& os) const;
432 
433  bool operator==(const Tabulated1DFunction<Scalar>& data) const {
434  return xValues_ == data.xValues_ &&
435  yValues_ == data.yValues_;
436  }
437 
438  template <class Evaluation>
439  SegmentIndex findSegmentIndex(const Evaluation& x, bool extrapolate = false) const
440  {
441  if (!isfinite(x)) {
442  throw std::runtime_error("We can not search for extrapolation/interpolation "
443  "segment in an 1D table for non-finite value " +
444  std::to_string(getValue(x)) + " .");
445  }
446 
447  if (!extrapolate && !applies(x))
448  throw std::logic_error("Trying to evaluate a tabulated function outside of its range");
449 
450  // we need at least two sampling points!
451  if (numSamples() < 2) {
452  throw std::logic_error("We need at least two sampling points to "
453  "do interpolation/extrapolation, "
454  "and the table only contains " +
455  std::to_string(numSamples()) +
456  " sampling points");
457  }
458 
459  if (x <= xValues_[1])
460  return SegmentIndex{0};
461  else if (x >= xValues_[xValues_.size() - 2])
462  return SegmentIndex{xValues_.size() - 2};
463  else {
464  // bisection
465  std::size_t lowerIdx = 1;
466  std::size_t upperIdx = xValues_.size() - 2;
467  while (lowerIdx + 1 < upperIdx) {
468  std::size_t pivotIdx = (lowerIdx + upperIdx) / 2;
469  if (x < xValues_[pivotIdx])
470  upperIdx = pivotIdx;
471  else
472  lowerIdx = pivotIdx;
473  }
474 
475  if (xValues_[lowerIdx] > x || x > xValues_[lowerIdx + 1]) {
476  std::string msg = "Problematic interpolation/extrapolation "
477  "segment is found for the input value " +
478  std::to_string(Opm::getValue(x)) +
479  "\nthe lower index of the found segment is " +
480  std::to_string(lowerIdx) +
481  ", the size of the table is " +
482  std::to_string(numSamples()) +
483  ",\nand the end values of the found segment are " +
484  std::to_string(xValues_[lowerIdx]) +
485  " and " +
486  std::to_string(xValues_[lowerIdx + 1]) +
487  ", respectively.\n";
488  msg += "Outputting the problematic table for more information "
489  "(with *** marking the found segment):";
490  for (std::size_t i = 0; i < numSamples(); ++i) {
491  if (i % 10 == 0)
492  msg += "\n";
493  if (i == lowerIdx)
494  msg += " ***";
495  msg += " " + std::to_string(xValues_[i]);
496  if (i == lowerIdx + 1)
497  msg += " ***";
498  }
499  msg += "\n";
500  OpmLog::debug(msg);
501  throw std::runtime_error(msg);
502  }
503  return SegmentIndex{lowerIdx};
504  }
505  }
506 
507 private:
508  template <class Evaluation>
509  Evaluation evalDerivative_(const Evaluation& x, std::size_t segIdx) const
510  {
511 
512  Scalar x0 = xValues_[segIdx];
513  Scalar x1 = xValues_[segIdx + 1];
514 
515  Scalar y0 = yValues_[segIdx];
516  Scalar y1 = yValues_[segIdx + 1];
517 
518  Evaluation ret = blank(x);
519  ret = (y1 - y0)/(x1 - x0);
520  return ret;
521  }
522 
523  // returns the monotonicity of a segment
524  //
525  // The return value have the following meaning:
526  //
527  // 3: function is constant within interval [x0, x1]
528  // 1: function is monotonously increasing in the specified interval
529  // 0: function is not monotonic in the specified interval
530  // -1: function is monotonously decreasing in the specified interval
531  int updateMonotonicity_(std::size_t i, int& r) const
532  {
533  if (yValues_[i] < yValues_[i + 1]) {
534  // monotonically increasing?
535  if (r == 3 || r == 1)
536  r = 1;
537  else
538  r = 0;
539  return 1;
540  }
541  else if (yValues_[i] > yValues_[i + 1]) {
542  // monotonically decreasing?
543  if (r == 3 || r == -1)
544  r = -1;
545  else
546  r = 0;
547  return -1;
548  }
549 
550  return 3;
551  }
552 
556  struct ComparatorX_
557  {
558  explicit ComparatorX_(const std::vector<Scalar>& x)
559  : x_(x)
560  {}
561 
562  bool operator ()(std::size_t idxA, std::size_t idxB) const
563  { return x_.at(idxA) < x_.at(idxB); }
564 
565  const std::vector<Scalar>& x_;
566  };
567 
571  void sortInput_()
572  {
573  std::size_t n = numSamples();
574 
575  // create a vector containing 0...n-1
576  std::vector<unsigned> idxVector(n);
577  for (unsigned i = 0; i < n; ++i)
578  idxVector[i] = i;
579 
580  // sort the indices according to the x values of the sample
581  // points
582  ComparatorX_ cmp(xValues_);
583  std::ranges::sort(idxVector, cmp);
584 
585  // reorder the sample points
586  std::vector<Scalar> tmpX(n), tmpY(n);
587  for (std::size_t i = 0; i < idxVector.size(); ++ i) {
588  tmpX[i] = xValues_[idxVector[i]];
589  tmpY[i] = yValues_[idxVector[i]];
590  }
591  xValues_ = tmpX;
592  yValues_ = tmpY;
593  }
594 
599  void reverseSamplingPoints_()
600  {
601  // reverse the arrays
602  std::size_t n = numSamples();
603  for (std::size_t i = 0; i <= (n - 1)/2; ++i) {
604  std::swap(xValues_[i], xValues_[n - i - 1]);
605  std::swap(yValues_[i], yValues_[n - i - 1]);
606  }
607  }
608 
612  void resizeArrays_(std::size_t nSamples)
613  {
614  xValues_.resize(nSamples);
615  yValues_.resize(nSamples);
616  }
617 
618  std::vector<Scalar> xValues_;
619  std::vector<Scalar> yValues_;
620 };
621 
622 } // namespace Opm
623 
624 #endif
bool applies(const Evaluation &x) const
Return true iff the given x is in range [x1, xn].
Definition: Tabulated1DFunction.hpp:253
Scalar xMin() const
Return the x value of the leftmost sampling point.
Definition: Tabulated1DFunction.hpp:222
Evaluation evalDerivative(const Evaluation &x, bool extrapolate=false) const
Evaluate the spline&#39;s derivative at a given position.
Definition: Tabulated1DFunction.hpp:297
void setXYContainers(const ScalarContainerX &x, const ScalarContainerY &y, bool sortInputs=true)
Set the sampling points for the piecewise linear function.
Definition: Tabulated1DFunction.hpp:135
Evaluation evalThirdDerivative(const Evaluation &, bool=false) const
Evaluate the function&#39;s third derivative at a given position.
Definition: Tabulated1DFunction.hpp:336
std::size_t numSamples() const
Returns the number of sampling points.
Definition: Tabulated1DFunction.hpp:216
Scalar valueAt(std::size_t i) const
Return the value of the a sample point with a given index.
Definition: Tabulated1DFunction.hpp:246
Tabulated1DFunction()
Default constructor for a piecewise linear function.
Definition: Tabulated1DFunction.hpp:59
This class implements a small container which holds the transmissibility mulitpliers for all the face...
Definition: Exceptions.hpp:30
Evaluation eval(const Evaluation &x, bool extrapolate=false) const
Evaluate the spline at a given position.
Definition: Tabulated1DFunction.hpp:266
A number of commonly used algebraic functions for the localized OPM automatic differentiation (AD) fr...
Tabulated1DFunction(const PointContainer &points, bool sortInputs=true)
Convenience constructor for a piecewise linear function.
Definition: Tabulated1DFunction.hpp:100
Scalar xMax() const
Return the x value of the rightmost sampling point.
Definition: Tabulated1DFunction.hpp:228
int monotonic() const
Same as monotonic(x0, x1), but with the entire range of the function as interval. ...
Definition: Tabulated1DFunction.hpp:412
Tabulated1DFunction(std::size_t nSamples, const ScalarArrayX &x, const ScalarArrayY &y, bool sortInputs=true)
Convenience constructor for a piecewise linear function.
Definition: Tabulated1DFunction.hpp:71
void setArrayOfPoints(std::size_t nSamples, const PointArray &points, bool sortInputs=true)
Set the sampling points for the piecewise linear function.
Definition: Tabulated1DFunction.hpp:157
Evaluation evalSecondDerivative(const Evaluation &, bool=false) const
Evaluate the function&#39;s second derivative at a given position.
Definition: Tabulated1DFunction.hpp:318
int monotonic(Scalar x0, Scalar x1, [[maybe_unused]] bool extrapolate=false) const
Returns 1 if the function is monotonically increasing, -1 if the function is mononously decreasing an...
Definition: Tabulated1DFunction.hpp:347
void printCSV(Scalar xi0, Scalar xi1, unsigned k, std::ostream &os) const
Prints k tuples of the format (x, y, dx/dy, isMonotonic) to stdout.
Definition: Tabulated1DFunction.cpp:33
void setContainerOfTuples(const XYContainer &points, bool sortInputs=true)
Set the sampling points of the piecewise linear function using a STL-compatible container of tuple-li...
Definition: Tabulated1DFunction.hpp:192
void setXYArrays(std::size_t nSamples, const ScalarArrayX &x, const ScalarArrayY &y, bool sortInputs=true)
Set the sampling points for the piecewise linear function.
Definition: Tabulated1DFunction.hpp:110
Implements a linearly interpolated scalar function that depends on one variable.
Definition: Tabulated1DFunction.hpp:51
Scalar xAt(std::size_t i) const
Return the x value of the a sample point with a given index.
Definition: Tabulated1DFunction.hpp:234
Definition: Tabulated1DFunction.hpp:42
Tabulated1DFunction(const ScalarContainer &x, const ScalarContainer &y, bool sortInputs=true)
Convenience constructor for a piecewise linear function.
Definition: Tabulated1DFunction.hpp:87