// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- // vi: set et ts=4 sw=4 sts=4: /* This file is part of the Open Porous Media project (OPM). OPM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. OPM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OPM. If not, see . Consult the COPYING file in the top-level source directory of this module for the precise wording of the license and the list of copyright holders. */ /*! * \file * * \copydoc Opm::IntervalTabulated2DFunction */ #ifndef OPM_INTERVAL_TABULATED_2D_FUNCTION_HPP #define OPM_INTERVAL_TABULATED_2D_FUNCTION_HPP #include #include #include #include #include #include #include #include #include namespace Opm { /*! * \brief Implements a function that depends on two variables. * * The function is sampled in regular intervals in both directions, i.e., the * interpolation cells are rectangles. The table can be extrapolated in either direction. */ template class IntervalTabulated2DFunction { public: IntervalTabulated2DFunction() { } template IntervalTabulated2DFunction(const std::vector& xPos, const std::vector& yPos, const DataContainer& data, const bool xExtrapolate = false, const bool yExtrapolate = false) : xPos_(xPos) , yPos_(yPos) , samples_(data) , xExtrapolate_(xExtrapolate) , yExtrapolate_(yExtrapolate) { #ifndef NDEBUG // in debug mode, ensure that the x and y positions arrays are strictly // mononically increasing. for (unsigned i = 0; i < xPos.size() - 1; ++ i) { if (xPos[i + 1] <= xPos[i]) throw std::runtime_error("The array for the x-positions is not strictly increasing!"); } for (unsigned i = 0; i < yPos.size() - 1; ++ i) { if (yPos[i + 1] <= yPos[i]) throw std::runtime_error("The array for the y-positions is not strictly increasing!"); } #endif // make sure the size is correct if (numX() != samples_.size()) throw std::runtime_error("numX() is not equal to the number of rows of the sampling points"); for (unsigned xIdx = 0; xIdx < numX(); ++xIdx) { if (samples_[xIdx].size() != numY()) { throw std::runtime_error("The " + std::to_string(xIdx) + "-th row of the sampling points has " "different size than numY() "); } } } /*! * \brief Returns the number of sampling points in X direction. */ size_t numX() const { return xPos_.size(); } /*! * \brief Returns the number of sampling points in Y direction. */ size_t numY() const { return yPos_.size(); } /*! * \brief Returns the minimum of the X coordinate of the sampling points. */ Scalar xMin() const { return xPos_.front(); } /*! * \brief Returns the maximum of the X coordinate of the sampling points. */ Scalar xMax() const { return xPos_.back(); } /*! * \brief Returns the minimum of the Y coordinate of the sampling points. */ Scalar yMin() const { return yPos_.front(); } /*! * \brief Returns the maximum of the Y coordinate of the sampling points. */ Scalar yMax() const { return yPos_.back(); } const std::vector& xPos() const { return xPos_; } const std::vector& yPos() const { return yPos_; } const std::vector>& samples() const { return samples_; } bool xExtrapolate() const { return xExtrapolate_; } bool yExtrapolate() const { return yExtrapolate_; } bool operator==(const IntervalTabulated2DFunction& data) const { return this->xPos() == data.xPos() && this->yPos() == data.yPos() && this->samples() == data.samples() && this->xExtrapolate() == data.xExtrapolate() && this->yExtrapolate() == data.yExtrapolate(); } /*! * \brief Returns the value of a sampling point. */ Scalar valueAt(size_t i, size_t j) const { return samples_[i][j]; } /*! * \brief Returns true if a coordinate lies in the tabulated range */ template bool applies(const Evaluation& x, const Evaluation& y) const { return appliesX(x) && appliesY(y); } /*! * \brief Returns true if a coordinate lies in the tabulated range on the x direction */ template bool appliesX(const Evaluation& x) const { return xMin() <= x && x <= xMax(); } /*! * \brief Returns true if a coordinate lies in the tabulated range on the y direction */ template bool appliesY(const Evaluation& y) const { return yMin() <= y && y <= yMax(); } /*! * \brief Evaluate the function at a given (x,y) position. * * If this method is called for a value outside of the tabulated * range, and extrapolation is not allowed in the corresponding direction, * a \c Opm::NumericalProblem exception is thrown. */ template Evaluation eval(const Evaluation& x, const Evaluation& y) const { if ((!xExtrapolate_ && !appliesX(x)) || (!yExtrapolate_ && !appliesY(y))) { if constexpr (std::is_floating_point_v) { throw NumericalProblem("Attempt to get undefined table value (" + std::to_string(x) + ", " + std::to_string(y) + ")"); } else { throw NumericalProblem("Attempt to get undefined table value (" + std::to_string(x.value()) + ", " + std::to_string(y.value()) + ")"); } }; // bi-linear interpolation: first, calculate the x and y indices in the lookup // table ... const unsigned i = xSegmentIndex_(x); const unsigned j = ySegmentIndex_(y); // bi-linear interpolation / extrapolation const Evaluation alpha = xToAlpha(x, i); const Evaluation beta = yToBeta(y, j); const Evaluation s1 = valueAt(i, j) * (1.0 - beta) + valueAt(i, j + 1) * beta; const Evaluation s2 = valueAt(i + 1, j) * (1.0 - beta) + valueAt(i + 1, j + 1) * beta; Valgrind::CheckDefined(s1); Valgrind::CheckDefined(s2); // ... and combine them using the x position return s1*(1.0 - alpha) + s2*alpha; } private: // the sampling points in the x-drection std::vector xPos_; // the sampling points in the y-drection std::vector yPos_; // data at the sampling points std::vector > samples_; bool xExtrapolate_ = false; bool yExtrapolate_ = false; /*! * \brief Return the interval index of a given position on the x-axis. */ template unsigned xSegmentIndex_(const Evaluation& x) const { assert(xExtrapolate_ || appliesX(x) ); return segmentIndex_(x, xPos_); } /*! * \brief Return the interval index of a given position on the y-axis. */ template unsigned ySegmentIndex_(const Evaluation& y) const { assert(yExtrapolate_ || appliesY(y) ); return segmentIndex_(y, yPos_); } template static unsigned segmentIndex_(const Evaluation& v, const std::vector& vPos) { const unsigned n = vPos.size(); assert(n >= 2); if (v <= vPos.front() || n == 2) return 0; else if (v >= vPos.back()) return n - 2; assert(n > 2 && v > vPos.front() && v < vPos.back()); // bisection. this assumes that the vPos array is strictly mononically // increasing. size_t lowerIdx = 0; size_t upperIdx = vPos.size() - 1; while (lowerIdx + 1 < upperIdx) { size_t pivotIdx = (lowerIdx + upperIdx) / 2; if (v < vPos[pivotIdx]) upperIdx = pivotIdx; else lowerIdx = pivotIdx; } assert(vPos[lowerIdx] <= v); assert(v <= vPos[lowerIdx + 1]); return lowerIdx; } /*! * \brief Return the relative position of an x value in an intervall * * The returned value can be larger than 1 or smaller than zero if it is outside of * the range of the segment. In particular this happens for the extrapolation case. */ template Evaluation xToAlpha(const Evaluation& x, unsigned xSegmentIdx) const { Scalar x1 = xPos_[xSegmentIdx]; Scalar x2 = xPos_[xSegmentIdx + 1]; return (x - x1)/(x2 - x1); } /*! * \brief Return the relative position of an y value in an interval * * The returned value can be larger than 1 or smaller than zero if it is outside of * the range of the segment. In particular this happens for the extrapolation case. */ template Evaluation yToBeta(const Evaluation& y, unsigned ySegmentIdx) const { Scalar y1 = yPos_[ySegmentIdx]; Scalar y2 = yPos_[ySegmentIdx + 1]; return (y - y1)/(y2 - y1); } }; } // namespace Opm #endif