///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2020 Equinor ASA // // ResInsight 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 3 of the License, or // (at your option) any later version. // // ResInsight 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 at // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RiaInterpolationTools.h" #include "cafAssert.h" #include #include #include //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool almostEqual( double a, double b, double maxRelDiff = std::numeric_limits::epsilon() * 128 ) { // Calculate the difference. double diff = std::fabs( a - b ); double fabsa = std::fabs( a ); double fabsb = std::fabs( b ); // Find the largest double largest = ( fabsb > fabsa ) ? fabsb : fabsa; return ( diff <= largest * maxRelDiff ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RiaInterpolationTools::linear( const std::vector& x, const std::vector& y, double value, ExtrapolationMode extrapolationMode ) { CAF_ASSERT( x.size() == y.size() ); // Handle cases with only one data point. if ( x.size() <= 1 ) { return std::numeric_limits::infinity(); } // Find the lower boundary bool found = false; int lowerIndex = 0; for ( int i = 0; i < static_cast( x.size() - 1 ); i++ ) { if ( x[i] <= value && x[i + 1] >= value ) { lowerIndex = i; found = true; } } // Value is outside of the defined range if ( !found ) { // Check if we are just outside the boundaries if ( almostEqual( value, x[0] ) ) return y[0]; else if ( almostEqual( value, x[x.size() - 1] ) ) return y[x.size() - 1]; if ( extrapolationMode == ExtrapolationMode::CLOSEST ) { return extrapolateClosestValue( x, y, value ); } else if ( extrapolationMode == ExtrapolationMode::TREND ) { return extrapolate( x, y, value ); } CAF_ASSERT( extrapolationMode == ExtrapolationMode::NONE ); return std::numeric_limits::infinity(); } int upperIndex = lowerIndex + 1; double lowerX = x[lowerIndex]; double lowerY = y[lowerIndex]; double upperX = x[upperIndex]; double upperY = y[upperIndex]; double deltaY = upperY - lowerY; double deltaX = upperX - lowerX; return lowerY + ( ( value - lowerX ) / deltaX ) * deltaY; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RiaInterpolationTools::extrapolate( const std::vector& x, const std::vector& y, double value ) { return y[0] + ( value - x[0] ) / ( x[1] - x[0] ) * ( y[1] - y[0] ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RiaInterpolationTools::extrapolateClosestValue( const std::vector& x, const std::vector& y, double value ) { if ( value <= x[0] ) return y[0]; else return y[x.size() - 1]; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- int RiaInterpolationTools::findNextDataPoint( const std::vector& values, int index ) { for ( size_t i = index; i < values.size(); i++ ) { if ( values[i] != std::numeric_limits::infinity() ) return static_cast( i ); } return -1; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- int RiaInterpolationTools::findPreviousDataPoint( const std::vector& values, int index ) { CAF_ASSERT( index >= 0 ); for ( int i = index; i >= 0; i-- ) { if ( values[i] != std::numeric_limits::infinity() ) return static_cast( i ); } return -1; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- int RiaInterpolationTools::extrapolateRange( int start, int end, int firstPoint, int lastPoint, const std::vector& x, std::vector& y ) { std::vector xs = { x[firstPoint], x[lastPoint] }; std::vector ys = { y[firstPoint], y[lastPoint] }; for ( int index = start; index < end; index++ ) { // Avoid excessive extrapolation when points are very close if ( almostEqual( xs[0], xs[1] ) || almostEqual( ys[0], ys[1] ) ) y[index] = ys[0]; else y[index] = extrapolate( xs, ys, x[index] ); } return end; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- int RiaInterpolationTools::interpolateRange( int start, int end, int firstPoint, int lastPoint, const std::vector& x, std::vector& y ) { CAF_ASSERT( start <= end ); std::vector xs = { x[firstPoint], x[lastPoint] }; std::vector ys = { y[firstPoint], y[lastPoint] }; for ( int index = start; index < end; index++ ) { y[index] = RiaInterpolationTools::linear( xs, ys, x[index] ); } return end; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RiaInterpolationTools::interpolateMissingValues( const std::vector& x, std::vector& y ) { CAF_ASSERT( x.size() == y.size() ); int index = 0; // Previous index which is not inf int prevSetIndex = -1; while ( index < static_cast( y.size() ) ) { // Missing values are inf in the input data if ( y[index] == std::numeric_limits::infinity() ) { // Find the next index with a value int nextSetIndex = findNextDataPoint( y, index + 1 ); if ( prevSetIndex == -1 ) { // The first value is inf: need to find next two valid points and extrapolate int nextSetIndex2 = findNextDataPoint( y, nextSetIndex + 1 ); index = extrapolateRange( index, nextSetIndex, nextSetIndex, nextSetIndex2, x, y ); } else if ( nextSetIndex == -1 ) { // The last value is inf: extrapolate from two last data points int prevSetIndex2 = findPreviousDataPoint( y, prevSetIndex - 1 ); index = extrapolateRange( index, (int)y.size(), prevSetIndex2, prevSetIndex, x, y ); } else if ( nextSetIndex != static_cast( y.size() ) ) { // The missing values somewhere between non-inf data: interpolate all the values index = interpolateRange( index, nextSetIndex, prevSetIndex, nextSetIndex, x, y ); } } else { // Nothing to do for the values which are not missing prevSetIndex = index; ++index; } } }