diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.cpp index bee9035cfd..423c601dfd 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.cpp @@ -20,6 +20,8 @@ #include "RimEclipseStatisticsCaseEvaluator.h" +#include "RiaLogging.h" + #include "RigCaseCellResultsData.h" #include "RigEclipseCaseData.h" #include "RigEclipseResultInfo.h" @@ -262,13 +264,20 @@ void RimEclipseStatisticsCaseEvaluator::evaluateForResults( const QList pValPoss.push_back( m_statisticsConfig.m_pMinPos ); pValPoss.push_back( m_statisticsConfig.m_pMidPos ); pValPoss.push_back( m_statisticsConfig.m_pMaxPos ); - std::vector pVals = + auto resultValues = RigStatisticsMath::calculateNearestRankPercentiles( values, pValPoss, RigStatisticsMath::PercentileStyle::SWITCHED ); - statParams[PMIN] = pVals[0]; - statParams[PMID] = pVals[1]; - statParams[PMAX] = pVals[2]; + if ( resultValues.has_value() ) + { + statParams[PMIN] = ( *resultValues )[0]; + statParams[PMID] = ( *resultValues )[1]; + statParams[PMAX] = ( *resultValues )[2]; + } + else + { + RiaLogging::warning( QString::fromStdString( resultValues.error() ) ); + } } else if ( m_statisticsConfig.m_pValMethod == RimEclipseStatisticsCase::PercentileCalcType::HISTOGRAM_ESTIMATED ) { @@ -285,17 +294,24 @@ void RimEclipseStatisticsCaseEvaluator::evaluateForResults( const QList else if ( m_statisticsConfig.m_pValMethod == RimEclipseStatisticsCase::PercentileCalcType::INTERPOLATED_OBSERVATION ) { - std::vector pValPoss; - pValPoss.push_back( m_statisticsConfig.m_pMinPos ); - pValPoss.push_back( m_statisticsConfig.m_pMidPos ); - pValPoss.push_back( m_statisticsConfig.m_pMaxPos ); - std::vector pVals = + std::vector percentiles; + percentiles.push_back( m_statisticsConfig.m_pMinPos ); + percentiles.push_back( m_statisticsConfig.m_pMidPos ); + percentiles.push_back( m_statisticsConfig.m_pMaxPos ); + auto resultValues = RigStatisticsMath::calculateInterpolatedPercentiles( values, - pValPoss, + percentiles, RigStatisticsMath::PercentileStyle::SWITCHED ); - statParams[PMIN] = pVals[0]; - statParams[PMID] = pVals[1]; - statParams[PMAX] = pVals[2]; + if ( resultValues.has_value() ) + { + statParams[PMIN] = ( *resultValues )[0]; + statParams[PMID] = ( *resultValues )[1]; + statParams[PMAX] = ( *resultValues )[2]; + } + else + { + RiaLogging::warning( QString::fromStdString( resultValues.error() ) ); + } } else { diff --git a/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.cpp b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.cpp index a61b4a4b9c..fad42bec2b 100644 --- a/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.cpp +++ b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.cpp @@ -23,10 +23,48 @@ #include #include #include +#include #include +namespace +{ +bool isValidQuantile( double quantile ) +{ + return quantile >= 0.0 && quantile <= 1.0; +} + +bool isValidPercentile( double percentile ) +{ + return percentile >= 0.0 && percentile <= 100.0; +} + +bool areValidQuantiles( const std::vector& quantiles ) +{ + return std::all_of( quantiles.begin(), quantiles.end(), isValidQuantile ); +} + +bool areValidPercentiles( const std::vector& percentiles ) +{ + return std::all_of( percentiles.begin(), percentiles.end(), isValidPercentile ); +} +} // namespace + //-------------------------------------------------------------------------------------------------- /// A function to do basic statistical calculations +/// +/// Formulas: +/// mean = sum(x) / n +/// +/// Standard deviation (population): +/// stdev = sqrt((n * sum(x^2) - (sum(x))^2)) / n +/// +/// Which is equivalent to: sqrt(sum((x - mean)^2) / n) +/// +/// range = max - min +/// +/// References: +/// Standard deviation: https://en.wikipedia.org/wiki/Standard_deviation +/// Rapid calculation method: https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods //-------------------------------------------------------------------------------------------------- void RigStatisticsMath::calculateBasicStatistics( const std::vector& values, @@ -85,8 +123,18 @@ void RigStatisticsMath::calculateBasicStatistics( const std::vector& val } //-------------------------------------------------------------------------------------------------- -/// Algorithm: -/// https://en.wikipedia.org/wiki/Percentile#Third_variant,_'%22%60UNIQ--postMath-00000052-QINU%60%22' +/// Calculate statistical curves (P10, P50, P90, mean) +/// +/// Percentiles (P10, P50, P90) are calculated using linear interpolation: +/// rank = percentile * (n + 1) - 1 +/// value = sorted[floor(rank)] + frac(rank) * (sorted[floor(rank)+1] - sorted[floor(rank)]) +/// +/// Mean is calculated as: +/// mean = sum(x) / n +/// +/// References: +/// Percentiles: https://en.wikipedia.org/wiki/Percentile#Third_variant,_C_=_0 +/// P10/P50/P90: https://en.wikipedia.org/wiki/Percentile#Definitions //-------------------------------------------------------------------------------------------------- void RigStatisticsMath::calculateStatisticsCurves( const std::vector& values, double* p10, @@ -99,12 +147,76 @@ void RigStatisticsMath::calculateStatisticsCurves( const std::vector& va if ( values.empty() ) return; - enum PValue + // Use the vector-based implementation + std::vector percentiles = { 0.1, 0.5, 0.9 }; + auto results = calculatePercentiles( values, percentiles, percentileStyle ); + + if ( results.has_value() && results->size() == 3 ) { - P10, - P50, - P90 - }; + *p10 = ( *results )[0]; + *p50 = ( *results )[1]; + *p90 = ( *results )[2]; + } + else + { + *p10 = HUGE_VAL; + *p50 = HUGE_VAL; + *p90 = HUGE_VAL; + } + + // Calculate mean separately + std::vector validValues = values; + validValues.erase( std::remove_if( validValues.begin(), + validValues.end(), + []( double x ) { return !RiaStatisticsTools::isValidNumber( x ); } ), + validValues.end() ); + + if ( !validValues.empty() ) + { + double valueSum = std::accumulate( validValues.begin(), validValues.end(), 0.0 ); + *mean = valueSum / validValues.size(); + } + else + { + *mean = HUGE_VAL; + } +} + +//-------------------------------------------------------------------------------------------------- +/// Calculate percentiles using linear interpolation method +/// +/// Formula: +/// rank = percentile * (n + 1) - 1 +/// +/// If rank is not an integer: +/// value = sorted[floor(rank)] + frac(rank) * (sorted[floor(rank)+1] - sorted[floor(rank)]) +/// +/// Where frac(rank) is the fractional part of rank +/// +/// Valid for percentiles in range [1/(n+1), n/(n+1)] +/// +/// References: +/// https://en.wikipedia.org/wiki/Percentile +/// https://en.wikipedia.org/wiki/Percentile#Third_variant,_C_=_0 +//-------------------------------------------------------------------------------------------------- +std::expected, std::string> RigStatisticsMath::calculatePercentiles( const std::vector& values, + const std::vector& quantiles, + PercentileStyle percentileStyle ) +{ + if ( !areValidQuantiles( quantiles ) ) + { + return std::unexpected( "Quantiles must be in range [0-1]" ); + } + + if ( values.empty() ) + { + return std::unexpected( "Input values are empty" ); + } + + if ( quantiles.empty() ) + { + return std::unexpected( "Quantiles are empty" ); + } std::vector sortedValues = values; @@ -113,62 +225,89 @@ void RigStatisticsMath::calculateStatisticsCurves( const std::vector& va []( double x ) { return !RiaStatisticsTools::isValidNumber( x ); } ), sortedValues.end() ); + if ( sortedValues.empty() ) + { + return std::unexpected( "No valid values in input" ); + } + std::sort( sortedValues.begin(), sortedValues.end() ); - double valueSum = std::accumulate( sortedValues.begin(), sortedValues.end(), 0.0 ); + int valueCount = (int)sortedValues.size(); + std::vector resultValues; + resultValues.reserve( quantiles.size() ); - int valueCount = (int)sortedValues.size(); - double percentiles[] = { 0.1, 0.5, 0.9 }; - double pValues[] = { HUGE_VAL, HUGE_VAL, HUGE_VAL }; - - for ( int i = P10; i <= P90; i++ ) + for ( size_t i = 0; i < quantiles.size(); ++i ) { + double quantile = quantiles[i]; + + if ( percentileStyle == PercentileStyle::SWITCHED ) + { + quantile = 1.0 - quantile; + } + + double value = HUGE_VAL; + // Check valid params - if ( ( percentiles[i] < 1.0 / ( (double)valueCount + 1 ) ) || ( percentiles[i] > (double)valueCount / ( (double)valueCount + 1 ) ) ) - continue; - - double rank = percentiles[i] * ( valueCount + 1 ) - 1; - double rankRem; - double rankFrac = std::modf( rank, &rankRem ); - int rankInt = static_cast( rankRem ); - - if ( rankInt < valueCount - 1 ) + if ( quantile >= 1.0 / ( static_cast( valueCount ) + 1 ) && + quantile <= static_cast( valueCount ) / ( static_cast( valueCount ) + 1 ) ) { - pValues[i] = sortedValues[rankInt] + rankFrac * ( sortedValues[rankInt + 1] - sortedValues[rankInt] ); - } - else - { - pValues[i] = sortedValues.back(); + double rank = quantile * ( valueCount + 1 ) - 1; + double rankRem; + double rankFrac = std::modf( rank, &rankRem ); + int rankInt = static_cast( rankRem ); + + if ( rankInt < valueCount - 1 ) + { + value = sortedValues[rankInt] + rankFrac * ( sortedValues[rankInt + 1] - sortedValues[rankInt] ); + } + else + { + value = sortedValues.back(); + } } + + resultValues.push_back( value ); } - *p50 = pValues[P50]; - - if ( percentileStyle == PercentileStyle::REGULAR ) - { - *p10 = pValues[P10]; - *p90 = pValues[P90]; - } - else - { - CVF_ASSERT( percentileStyle == PercentileStyle::SWITCHED ); - *p10 = pValues[P90]; - *p90 = pValues[P10]; - } - - *mean = valueSum / valueCount; + return resultValues; } //-------------------------------------------------------------------------------------------------- /// Calculate the percentiles of /a inputValues at the pValPosition percentages using the "Nearest Rank" /// method. This method treats HUGE_VAL as "undefined" values, and ignores these. Will return HUGE_VAL if /// the inputValues does not contain any valid values +/// +/// Formula (Nearest Rank Method): +/// index = floor(n * percentile) +/// value = sorted[index] +/// +/// Note: pValPositions are expected as percentages (0-100), converted to fraction (0-1) internally +/// +/// References: +/// https://en.wikipedia.org/wiki/Percentile#The_nearest-rank_method +/// https://en.wikipedia.org/wiki/Percentile#First_variant,_C_=_1/2 //-------------------------------------------------------------------------------------------------- -std::vector RigStatisticsMath::calculateNearestRankPercentiles( const std::vector& inputValues, - const std::vector& pValPositions, - RigStatisticsMath::PercentileStyle percentileStyle ) +std::expected, std::string> + RigStatisticsMath::calculateNearestRankPercentiles( const std::vector& inputValues, + const std::vector& percentiles, + RigStatisticsMath::PercentileStyle percentileStyle ) { + if ( !areValidPercentiles( percentiles ) ) + { + return std::unexpected( "Percentiles must be in range [0-100]" ); + } + + if ( inputValues.empty() ) + { + return std::unexpected( "Input values are empty" ); + } + + if ( percentiles.empty() ) + { + return std::unexpected( "Percentiles are empty" ); + } + std::vector sortedValues; sortedValues.reserve( inputValues.size() ); @@ -180,39 +319,69 @@ std::vector RigStatisticsMath::calculateNearestRankPercentiles( const st } } - std::sort( sortedValues.begin(), sortedValues.end() ); - - std::vector percentiles( pValPositions.size(), HUGE_VAL ); - if ( !sortedValues.empty() ) + if ( sortedValues.empty() ) { - for ( size_t i = 0; i < pValPositions.size(); ++i ) - { - double pVal = HUGE_VAL; - - double pValPosition = cvf::Math::abs( pValPositions[i] ) / 100; - if ( percentileStyle == RigStatisticsMath::PercentileStyle::SWITCHED ) pValPosition = 1.0 - pValPosition; - - size_t pValIndex = static_cast( sortedValues.size() * pValPosition ); - - if ( pValIndex >= sortedValues.size() ) pValIndex = sortedValues.size() - 1; - - pVal = sortedValues[pValIndex]; - percentiles[i] = pVal; - } + return std::unexpected( "No valid values in input" ); } - return percentiles; -}; + std::sort( sortedValues.begin(), sortedValues.end() ); + + std::vector resultValues( percentiles.size(), HUGE_VAL ); + for ( size_t i = 0; i < percentiles.size(); ++i ) + { + double quantile = cvf::Math::abs( percentiles[i] ) / 100; + if ( percentileStyle == RigStatisticsMath::PercentileStyle::SWITCHED ) quantile = 1.0 - quantile; + + size_t index = static_cast( sortedValues.size() * quantile ); + + if ( index >= sortedValues.size() ) index = sortedValues.size() - 1; + + auto value = sortedValues[index]; + resultValues[i] = value; + } + + return resultValues; +} //-------------------------------------------------------------------------------------------------- /// Calculate the percentiles of /a inputValues at the pValPosition percentages by interpolating input values. /// This method treats HUGE_VAL as "undefined" values, and ignores these. Will return HUGE_VAL if /// the inputValues does not contain any valid values +/// +/// Formula (Linear Interpolation Method): +/// doubleIndex = (n - 1) * percentile +/// lowerIndex = floor(doubleIndex) +/// upperIndex = lowerIndex + 1 +/// weight = doubleIndex - lowerIndex +/// +/// value = (1 - weight) * sorted[lowerIndex] + weight * sorted[upperIndex] +/// +/// Note: pValPositions are expected as percentages (0-100), convert to fraction (0-1) internally +/// +/// References: +/// https://en.wikipedia.org/wiki/Percentile#The_linear_interpolation_between_closest_ranks_method +/// https://en.wikipedia.org/wiki/Percentile#Second_variant,_C_=_1 //-------------------------------------------------------------------------------------------------- -std::vector RigStatisticsMath::calculateInterpolatedPercentiles( const std::vector& inputValues, - const std::vector& pValPositions, - RigStatisticsMath::PercentileStyle percentileStyle ) +std::expected, std::string> + RigStatisticsMath::calculateInterpolatedPercentiles( const std::vector& inputValues, + const std::vector& percentiles, + RigStatisticsMath::PercentileStyle percentileStyle ) { + if ( !areValidPercentiles( percentiles ) ) + { + return std::unexpected( "Percentiles must be in range [0-100]" ); + } + + if ( inputValues.empty() ) + { + return std::unexpected( "Input values are empty" ); + } + + if ( percentiles.empty() ) + { + return std::unexpected( "Percentiles are empty" ); + } + std::vector sortedValues; sortedValues.reserve( inputValues.size() ); @@ -224,39 +393,41 @@ std::vector RigStatisticsMath::calculateInterpolatedPercentiles( const s } } - std::sort( sortedValues.begin(), sortedValues.end() ); - - std::vector percentiles( pValPositions.size(), HUGE_VAL ); - if ( !sortedValues.empty() ) + if ( sortedValues.empty() ) { - for ( size_t i = 0; i < pValPositions.size(); ++i ) - { - double pVal = HUGE_VAL; - - double pValPosition = cvf::Math::abs( pValPositions[i] ) / 100.0; - if ( percentileStyle == RigStatisticsMath::PercentileStyle::SWITCHED ) pValPosition = 1.0 - pValPosition; - - double doubleIndex = ( sortedValues.size() - 1 ) * pValPosition; - - size_t lowerValueIndex = static_cast( floor( doubleIndex ) ); - size_t upperValueIndex = lowerValueIndex + 1; - - double upperValueWeight = doubleIndex - lowerValueIndex; - assert( upperValueWeight < 1.0 ); - - if ( upperValueIndex < sortedValues.size() ) - { - pVal = ( 1.0 - upperValueWeight ) * sortedValues[lowerValueIndex] + upperValueWeight * sortedValues[upperValueIndex]; - } - else - { - pVal = sortedValues[lowerValueIndex]; - } - percentiles[i] = pVal; - } + return std::unexpected( "No valid values in input" ); } - return percentiles; + std::sort( sortedValues.begin(), sortedValues.end() ); + + std::vector resultValues( percentiles.size(), HUGE_VAL ); + for ( size_t i = 0; i < percentiles.size(); ++i ) + { + double value = HUGE_VAL; + + double quantile = cvf::Math::abs( percentiles[i] ) / 100.0; + if ( percentileStyle == RigStatisticsMath::PercentileStyle::SWITCHED ) quantile = 1.0 - quantile; + + double doubleIndex = ( sortedValues.size() - 1 ) * quantile; + + size_t lowerValueIndex = static_cast( floor( doubleIndex ) ); + size_t upperValueIndex = lowerValueIndex + 1; + + double upperValueWeight = doubleIndex - lowerValueIndex; + assert( upperValueWeight < 1.0 ); + + if ( upperValueIndex < sortedValues.size() ) + { + value = ( 1.0 - upperValueWeight ) * sortedValues[lowerValueIndex] + upperValueWeight * sortedValues[upperValueIndex]; + } + else + { + value = sortedValues[lowerValueIndex]; + } + resultValues[i] = value; + } + + return resultValues; } //-------------------------------------------------------------------------------------------------- @@ -328,7 +499,22 @@ void RigHistogramCalculator::addData( const std::vector& data ) } //-------------------------------------------------------------------------------------------------- +/// Calculate percentile from histogram data /// +/// Formula: +/// 1. Find cumulative count up to target: targetCount = percentile * totalObservations +/// 2. Find bin where cumulative count >= targetCount +/// 3. Interpolate within bin: +/// unusedFraction = (cumulativeCount - targetCount) / binCount +/// value = binEndValue - unusedFraction * binWidth +/// +/// Where: +/// binWidth = (max - min) / numberOfBins +/// binEndValue = min + (binIndex + 1) * binWidth +/// +/// References: +/// https://en.wikipedia.org/wiki/Histogram +/// https://en.wikipedia.org/wiki/Percentile#Estimating_percentiles_from_a_histogram //-------------------------------------------------------------------------------------------------- double RigHistogramCalculator::calculatePercentil( double pVal, RigStatisticsMath::PercentileStyle percentileStyle ) { @@ -354,7 +540,7 @@ double RigHistogramCalculator::calculatePercentil( double pVal, RigStatisticsMat if ( accObsCount >= pValObservationCount ) { double domainValueAtEndOfBin = m_min + ( binIdx + 1 ) * binWidth; - double unusedFractionOfLastBin = (double)( accObsCount - pValObservationCount ) / binObsCount; + double unusedFractionOfLastBin = static_cast( accObsCount - pValObservationCount ) / binObsCount; double histogramBasedEstimate = domainValueAtEndOfBin - unusedFractionOfLastBin * binWidth; diff --git a/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.h b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.h index f8e9355bbe..5c8ad13592 100644 --- a/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.h +++ b/ApplicationLibCode/ResultStatisticsCache/RigStatisticsMath.h @@ -21,7 +21,9 @@ #include #include +#include #include +#include #include class RigStatisticsMath @@ -42,14 +44,16 @@ public: double* p90, double* mean, PercentileStyle percentileStyle ); + static std::expected, std::string> + calculatePercentiles( const std::vector& values, const std::vector& quantiles, PercentileStyle percentileStyle ); - static std::vector calculateNearestRankPercentiles( const std::vector& inputValues, - const std::vector& pValPositions, - PercentileStyle percentileStyle ); + static std::expected, std::string> calculateNearestRankPercentiles( const std::vector& inputValues, + const std::vector& percentiles, + PercentileStyle percentileStyle ); - static std::vector calculateInterpolatedPercentiles( const std::vector& inputValues, - const std::vector& pValPositions, - PercentileStyle percentileStyle ); + static std::expected, std::string> calculateInterpolatedPercentiles( const std::vector& inputValues, + const std::vector& percentiles, + PercentileStyle percentileStyle ); }; //================================================================================================== diff --git a/ApplicationLibCode/UnitTests/RigStatisticsMath-Test.cpp b/ApplicationLibCode/UnitTests/RigStatisticsMath-Test.cpp index 360c45e25e..6ed9419c5c 100644 --- a/ApplicationLibCode/UnitTests/RigStatisticsMath-Test.cpp +++ b/ApplicationLibCode/UnitTests/RigStatisticsMath-Test.cpp @@ -85,18 +85,18 @@ TEST( RigStatisticsMath, RankPercentiles ) values.push_back( HUGE_VAL ); values.push_back( -57020.4389966513000 ); - std::vector pValPos; - pValPos.push_back( 10 ); - pValPos.push_back( 40 ); - pValPos.push_back( 50 ); - pValPos.push_back( 90 ); - std::vector pVals = - RigStatisticsMath::calculateNearestRankPercentiles( values, pValPos, RigStatisticsMath::PercentileStyle::REGULAR ); + std::vector resultValues; + resultValues.push_back( 10 ); + resultValues.push_back( 40 ); + resultValues.push_back( 50 ); + resultValues.push_back( 90 ); + auto pVals = RigStatisticsMath::calculateNearestRankPercentiles( values, resultValues, RigStatisticsMath::PercentileStyle::REGULAR ); - EXPECT_DOUBLE_EQ( -76092.8157632591000, pVals[0] ); - EXPECT_DOUBLE_EQ( 2788.2723335651900, pVals[1] ); - EXPECT_DOUBLE_EQ( 6391.979999097290, pVals[2] ); - EXPECT_DOUBLE_EQ( 96161.7546348456000, pVals[3] ); + ASSERT_TRUE( pVals.has_value() ); + EXPECT_DOUBLE_EQ( -76092.8157632591000, ( *pVals )[0] ); + EXPECT_DOUBLE_EQ( 2788.2723335651900, ( *pVals )[1] ); + EXPECT_DOUBLE_EQ( 6391.979999097290, ( *pVals )[2] ); + EXPECT_DOUBLE_EQ( 96161.7546348456000, ( *pVals )[3] ); } //-------------------------------------------------------------------------------------------------- @@ -164,18 +164,18 @@ TEST( RigStatisticsMath, InterpolatedPercentiles ) values.push_back( HUGE_VAL ); values.push_back( -57020.4389966513000 ); - std::vector pValPos; - pValPos.push_back( 10 ); - pValPos.push_back( 40 ); - pValPos.push_back( 50 ); - pValPos.push_back( 90 ); - std::vector pVals = - RigStatisticsMath::calculateInterpolatedPercentiles( values, pValPos, RigStatisticsMath::PercentileStyle::REGULAR ); + std::vector resultValues; + resultValues.push_back( 10 ); + resultValues.push_back( 40 ); + resultValues.push_back( 50 ); + resultValues.push_back( 90 ); + auto pVals = RigStatisticsMath::calculateInterpolatedPercentiles( values, resultValues, RigStatisticsMath::PercentileStyle::REGULAR ); - EXPECT_DOUBLE_EQ( -72278.340409937548, pVals[0] ); - EXPECT_DOUBLE_EQ( -2265.6006907818496, pVals[1] ); - EXPECT_DOUBLE_EQ( 6391.9799990972897, pVals[2] ); - EXPECT_DOUBLE_EQ( 93073.49128098879, pVals[3] ); + ASSERT_TRUE( pVals.has_value() ); + EXPECT_DOUBLE_EQ( -72278.340409937548, ( *pVals )[0] ); + EXPECT_DOUBLE_EQ( -2265.6006907818496, ( *pVals )[1] ); + EXPECT_DOUBLE_EQ( 6391.9799990972897, ( *pVals )[2] ); + EXPECT_DOUBLE_EQ( 93073.49128098879, ( *pVals )[3] ); } //--------------------------------------------------------------------------------------------------