mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
#13430: Exclude zero values from ensemble statistics for derived vectors
Add m_includeZeroValues option to RimEnsembleStatistics (default on, preserving existing behavior). When disabled, exact zero values (e.g. from shut-in wells/groups) are excluded from the mean, P10/P50/P90 and custom percentile calculations in RimEnsembleStatisticsCase::calculate(). This is useful for derived vectors such as WCT, GOR and PI, where zeros caused by a shut well/group should not skew the statistical curves. Exact equality (not an epsilon) is used, since shut-in values are reported as exact 0.0 by the simulator and non-accumulated vectors use STEP_RIGHT interpolation, which does not introduce floating-point noise.
This commit is contained in:
1 parent
1d331f6cc1
commit
7b580694ed
7 files changed
+164
-4
No files matched your search
@@ -2233,7 +2233,11 @@ void RimEnsembleCurveSet::updateStatisticsCurves( const std::vector<RimSummaryCa
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ensembleStatCaseY->calculate( statCases, summaryAddressY(), m_statistics->includeIncompleteCurves(), percentiles );
|
||||
m_ensembleStatCaseY->calculate( statCases,
|
||||
summaryAddressY(),
|
||||
m_statistics->includeIncompleteCurves(),
|
||||
percentiles,
|
||||
m_statistics->includeZeroValues() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ RimEnsembleStatistics::RimEnsembleStatistics( RimEnsembleCurveSetInterface* pare
|
||||
m_customPercentiles.setCustomValidationCallback( [this]() -> QString { return validateCustomPercentiles(); } );
|
||||
CAF_PDM_InitField( &m_showCurveLabels, "ShowCurveLabels", true, "Show Curve Labels" );
|
||||
CAF_PDM_InitField( &m_includeIncompleteCurves, "IncludeIncompleteCurves", false, "Include Incomplete Curves" );
|
||||
CAF_PDM_InitField( &m_includeZeroValues, "IncludeZeroValues", true, "Include Zero Values" );
|
||||
|
||||
CAF_PDM_InitField( &m_crossPlotCurvesBinCount, "CrossPlotCurvesBinCount", 100, "Bin Count" );
|
||||
CAF_PDM_InitField( &m_crossPlotCurvesStatisticsRealizationCountThresholdPerBin,
|
||||
@@ -241,6 +242,14 @@ bool RimEnsembleStatistics::includeIncompleteCurves() const
|
||||
return m_includeIncompleteCurves;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RimEnsembleStatistics::includeZeroValues() const
|
||||
{
|
||||
return m_includeZeroValues;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -302,6 +311,7 @@ void RimEnsembleStatistics::defaultUiOrdering( bool showCrossPlotGroup, caf::Pdm
|
||||
uiOrdering.add( &m_showEnsembleCurves );
|
||||
uiOrdering.add( &m_basedOnFilteredCases );
|
||||
uiOrdering.add( &m_includeIncompleteCurves );
|
||||
uiOrdering.add( &m_includeZeroValues );
|
||||
uiOrdering.add( &m_showCurveLabels );
|
||||
|
||||
if ( showCrossPlotGroup )
|
||||
|
||||
@@ -55,6 +55,8 @@ public:
|
||||
|
||||
void enableIncludeIncompleteCurves( bool enable );
|
||||
|
||||
bool includeZeroValues() const;
|
||||
|
||||
cvf::Color3f color() const { return m_color; }
|
||||
void setColor( const cvf::Color3f& color );
|
||||
bool customColor() const;
|
||||
@@ -93,6 +95,7 @@ private:
|
||||
caf::PdmField<QString> m_customPercentiles;
|
||||
caf::PdmField<bool> m_showCurveLabels;
|
||||
caf::PdmField<bool> m_includeIncompleteCurves;
|
||||
caf::PdmField<bool> m_includeZeroValues;
|
||||
caf::PdmField<bool> m_customColor;
|
||||
|
||||
// Ensemble cross plot settings
|
||||
|
||||
@@ -168,9 +168,11 @@ RifSummaryReaderInterface* RimEnsembleStatisticsCase::summaryReader()
|
||||
void RimEnsembleStatisticsCase::calculate( const std::vector<RimSummaryCase*>& summaryCases,
|
||||
const RifEclipseSummaryAddress& inputAddress,
|
||||
bool includeIncompleteCurves,
|
||||
const std::vector<int>& percentiles )
|
||||
const std::vector<int>& percentiles,
|
||||
bool includeZeroValues )
|
||||
{
|
||||
auto hash = RiaHashTools::hash( summaryCases, inputAddress.toEclipseTextAddress(), includeIncompleteCurves, percentiles );
|
||||
auto hash =
|
||||
RiaHashTools::hash( summaryCases, inputAddress.toEclipseTextAddress(), includeIncompleteCurves, percentiles, includeZeroValues );
|
||||
if ( hash == m_hash ) return;
|
||||
|
||||
auto startTime = RiaLogging::currentTime();
|
||||
@@ -266,6 +268,15 @@ void RimEnsembleStatisticsCase::calculate( const std::vector<RimSummaryCase*>& s
|
||||
valuesAtTimeStep.push_back( curveValues[curveIdx][timeStepIndex] );
|
||||
}
|
||||
|
||||
if ( !includeZeroValues )
|
||||
{
|
||||
// Shut-in wells/groups report exact 0.0 for rate/ratio vectors (e.g. WCT, GOR, PI) from the simulator, and non-accumulated
|
||||
// vectors use STEP_RIGHT interpolation above, which copies recorded values without introducing floating-point noise. An
|
||||
// epsilon comparison is therefore not needed, and could incorrectly exclude small-but-valid values for low-magnitude
|
||||
// vectors such as PI.
|
||||
valuesAtTimeStep.erase( std::remove( valuesAtTimeStep.begin(), valuesAtTimeStep.end(), 0.0 ), valuesAtTimeStep.end() );
|
||||
}
|
||||
|
||||
double mean = RigStatisticsMath::calculateMean( valuesAtTimeStep );
|
||||
m_meanData.push_back( mean );
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ public:
|
||||
void calculate( const std::vector<RimSummaryCase*>& summaryCases,
|
||||
const RifEclipseSummaryAddress& inputAddress,
|
||||
bool includeIncompleteCurves,
|
||||
const std::vector<int>& percentiles = { 10, 50, 90 } );
|
||||
const std::vector<int>& percentiles = { 10, 50, 90 },
|
||||
bool includeZeroValues = true );
|
||||
|
||||
std::vector<time_t> timeSteps( const RifEclipseSummaryAddress& resultAddress ) const override;
|
||||
std::pair<bool, std::vector<double>> values( const RifEclipseSummaryAddress& resultAddress ) const override;
|
||||
|
||||
@@ -202,6 +202,7 @@ set(SOURCE_UNITTEST_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RiaSummaryAddressCollectionTools-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../TestModels/RimMockSummaryCase.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimMockSummaryCase-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimEnsembleStatisticsCase-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryCalculation-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RifMultipleSummaryReaders-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryCalculationCollection-Test.cpp
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "RimEnsembleStatisticsCase.h"
|
||||
#include "RimMockSummaryCase.h"
|
||||
|
||||
#include "RifEclipseSummaryAddress.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Three realizations with a zero value (well shut-in) at the second time step. With zero values
|
||||
/// included, the mean at that time step is affected by the zero. With zero values excluded, the zero
|
||||
/// should not contribute to the mean (or percentiles).
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimEnsembleStatisticsCase, IncludeZeroValuesAffectsMean )
|
||||
{
|
||||
auto addr = RifEclipseSummaryAddress::wellAddress( "WWCT", "W-1" );
|
||||
|
||||
RimMockSummaryCase caseA;
|
||||
RimMockSummaryCase caseB;
|
||||
RimMockSummaryCase caseC;
|
||||
|
||||
std::vector<time_t> timeSteps = { 0, 100, 200 };
|
||||
|
||||
caseA.addVector( addr, timeSteps, { 0.1, 0.0, 0.3 } );
|
||||
caseB.addVector( addr, timeSteps, { 0.2, 0.0, 0.4 } );
|
||||
caseC.addVector( addr, timeSteps, { 0.3, 0.6, 0.5 } );
|
||||
|
||||
std::vector<RimSummaryCase*> summaryCases = { &caseA, &caseB, &caseC };
|
||||
|
||||
auto meanAddress = addr;
|
||||
meanAddress.setStatisticsType( RifEclipseSummaryAddressDefines::StatisticsType::MEAN );
|
||||
|
||||
{
|
||||
RimEnsembleStatisticsCase statisticsCase;
|
||||
bool includeIncompleteCurves = true;
|
||||
bool includeZeroValues = true;
|
||||
statisticsCase.calculate( summaryCases, addr, includeIncompleteCurves, { 10, 50, 90 }, includeZeroValues );
|
||||
|
||||
auto [ok, meanValues] = statisticsCase.values( meanAddress );
|
||||
ASSERT_TRUE( ok );
|
||||
ASSERT_EQ( 3u, meanValues.size() );
|
||||
|
||||
// Mean of 0.0, 0.0, 0.6 at the second time step
|
||||
EXPECT_NEAR( 0.2, meanValues[1], 1.0e-6 );
|
||||
}
|
||||
|
||||
{
|
||||
RimEnsembleStatisticsCase statisticsCase;
|
||||
bool includeIncompleteCurves = true;
|
||||
bool includeZeroValues = false;
|
||||
statisticsCase.calculate( summaryCases, addr, includeIncompleteCurves, { 10, 50, 90 }, includeZeroValues );
|
||||
|
||||
auto [ok, meanValues] = statisticsCase.values( meanAddress );
|
||||
ASSERT_TRUE( ok );
|
||||
ASSERT_EQ( 3u, meanValues.size() );
|
||||
|
||||
// Mean of only 0.6 at the second time step, as the two zero values are excluded
|
||||
EXPECT_NEAR( 0.6, meanValues[1], 1.0e-6 );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// If every realization has a zero value at a given time step and zero values are excluded, no data
|
||||
/// remains for the statistics calculation at that time step.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimEnsembleStatisticsCase, IncludeZeroValuesFalseWithAllZerosGivesInvalidMean )
|
||||
{
|
||||
auto addr = RifEclipseSummaryAddress::wellAddress( "WWCT", "W-1" );
|
||||
|
||||
RimMockSummaryCase caseA;
|
||||
RimMockSummaryCase caseB;
|
||||
|
||||
std::vector<time_t> timeSteps = { 0, 100 };
|
||||
|
||||
caseA.addVector( addr, timeSteps, { 0.1, 0.0 } );
|
||||
caseB.addVector( addr, timeSteps, { 0.2, 0.0 } );
|
||||
|
||||
std::vector<RimSummaryCase*> summaryCases = { &caseA, &caseB };
|
||||
|
||||
RimEnsembleStatisticsCase statisticsCase;
|
||||
bool includeIncompleteCurves = true;
|
||||
bool includeZeroValues = false;
|
||||
statisticsCase.calculate( summaryCases, addr, includeIncompleteCurves, { 10, 50, 90 }, includeZeroValues );
|
||||
|
||||
auto meanAddress = addr;
|
||||
meanAddress.setStatisticsType( RifEclipseSummaryAddressDefines::StatisticsType::MEAN );
|
||||
|
||||
auto [ok, meanValues] = statisticsCase.values( meanAddress );
|
||||
ASSERT_TRUE( ok );
|
||||
ASSERT_EQ( 2u, meanValues.size() );
|
||||
|
||||
EXPECT_NEAR( 0.15, meanValues[0], 1.0e-6 );
|
||||
EXPECT_EQ( std::numeric_limits<double>::infinity(), meanValues[1] );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Values that are close to, but not exactly, zero are not treated as zero and are not excluded. Shut-in
|
||||
/// values are reported as exact 0.0 by the simulator, so an exact comparison is used rather than an
|
||||
/// epsilon, to avoid excluding small-but-valid values for e.g. low-magnitude vectors such as PI.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimEnsembleStatisticsCase, IncludeZeroValuesFalseDoesNotExcludeNearZeroValues )
|
||||
{
|
||||
auto addr = RifEclipseSummaryAddress::wellAddress( "WWCT", "W-1" );
|
||||
|
||||
RimMockSummaryCase caseA;
|
||||
RimMockSummaryCase caseB;
|
||||
|
||||
std::vector<time_t> timeSteps = { 0, 100 };
|
||||
|
||||
caseA.addVector( addr, timeSteps, { 0.1, 1.0e-9 } );
|
||||
caseB.addVector( addr, timeSteps, { 0.2, -1.0e-9 } );
|
||||
|
||||
std::vector<RimSummaryCase*> summaryCases = { &caseA, &caseB };
|
||||
|
||||
RimEnsembleStatisticsCase statisticsCase;
|
||||
bool includeIncompleteCurves = true;
|
||||
bool includeZeroValues = false;
|
||||
statisticsCase.calculate( summaryCases, addr, includeIncompleteCurves, { 10, 50, 90 }, includeZeroValues );
|
||||
|
||||
auto meanAddress = addr;
|
||||
meanAddress.setStatisticsType( RifEclipseSummaryAddressDefines::StatisticsType::MEAN );
|
||||
|
||||
auto [ok, meanValues] = statisticsCase.values( meanAddress );
|
||||
ASSERT_TRUE( ok );
|
||||
ASSERT_EQ( 2u, meanValues.size() );
|
||||
|
||||
// The near-zero values are not excluded, since they are not exactly 0.0
|
||||
EXPECT_NEAR( 0.0, meanValues[1], 1.0e-9 );
|
||||
}
|
||||
Reference in new issue
Block a user