ResInsight/ApplicationLibCode/Application/Tools/RiaInterpolationTools.cpp

230 lines
8.3 KiB
C++
Raw Normal View History

/////////////////////////////////////////////////////////////////////////////////
//
// 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiaInterpolationTools.h"
#include "cafAssert.h"
#include <cmath>
2020-06-11 01:06:07 -05:00
#include <cstdlib>
#include <limits>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool almostEqual( double a, double b, double maxRelDiff = std::numeric_limits<double>::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<double>& x, const std::vector<double>& 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<double>::infinity();
}
// Find the lower boundary
bool found = false;
int lowerIndex = 0;
for ( int i = 0; i < static_cast<int>( 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<double>::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<double>& x, const std::vector<double>& y, double value )
{
return y[0] + ( value - x[0] ) / ( x[1] - x[0] ) * ( y[1] - y[0] );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RiaInterpolationTools::extrapolateClosestValue( const std::vector<double>& x, const std::vector<double>& y, double value )
{
if ( value <= x[0] )
return y[0];
else
return y[x.size() - 1];
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiaInterpolationTools::findNextDataPoint( const std::vector<double>& values, int index )
{
for ( size_t i = index; i < values.size(); i++ )
{
if ( values[i] != std::numeric_limits<double>::infinity() ) return static_cast<int>( i );
}
return -1;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiaInterpolationTools::findPreviousDataPoint( const std::vector<double>& values, int index )
{
CAF_ASSERT( index >= 0 );
for ( int i = index; i >= 0; i-- )
{
if ( values[i] != std::numeric_limits<double>::infinity() ) return static_cast<int>( i );
}
return -1;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RiaInterpolationTools::extrapolateRange( int start, int end, int firstPoint, int lastPoint, const std::vector<double>& x, std::vector<double>& y )
{
2020-11-06 03:46:38 -06:00
std::vector<double> xs = { x[firstPoint], x[lastPoint] };
std::vector<double> 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<double>& x, std::vector<double>& y )
{
CAF_ASSERT( start <= end );
2020-11-06 03:46:38 -06:00
std::vector<double> xs = { x[firstPoint], x[lastPoint] };
std::vector<double> 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<double>& x, std::vector<double>& y )
{
CAF_ASSERT( x.size() == y.size() );
int index = 0;
// Previous index which is not inf
int prevSetIndex = -1;
while ( index < static_cast<int>( y.size() ) )
{
// Missing values are inf in the input data
if ( y[index] == std::numeric_limits<double>::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 );
2020-09-09 01:43:23 -05:00
index = extrapolateRange( index, (int)y.size(), prevSetIndex2, prevSetIndex, x, y );
}
else if ( nextSetIndex != static_cast<int>( 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;
}
}
}