Skip to content

Commit 8e0d7f8

Browse files
committed
Add support for custom percentiles in ensemble statistics
Introduced custom percentile handling for ensemble statistics: - Added "CUSTOM" statistics type and percentile field to RifEclipseSummaryAddress - Refactored RimEnsembleStatisticsCase to store and compute arbitrary percentiles - RimEnsembleStatistics now parses custom percentiles from UI input - RimEnsembleCurveSet and related interfaces updated to handle custom percentile curves and labels - Hashing and statistics calculations now include custom percentiles for cache validity
1 parent 9e88940 commit 8e0d7f8

18 files changed

+350
-58
lines changed

ApplicationLibCode/Application/Tools/RiaHashTools.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include <cstddef>
2222
#include <functional>
2323
#include <ranges>
24+
#include <string>
25+
#include <type_traits>
2426

2527
//==================================================================================================
2628
//
@@ -31,14 +33,29 @@ namespace RiaHashTools
3133
{
3234
//--------------------------------------------------------------------------------------------------
3335
/// Variadic template function to combine multiple parameters into a single hash
36+
/// Constrained to non-range types (std::string is excluded as it has std::hash specialization)
3437
//--------------------------------------------------------------------------------------------------
3538
template <typename T>
39+
requires( !std::ranges::range<T> || std::is_same_v<std::decay_t<T>, std::string> )
3640
void combineHash( size_t& seed, const T& value )
3741
{
3842
// Based on https://www.boost.org/doc/libs/1_84_0/libs/container_hash/doc/html/hash.html#notes_hash_combine
3943
seed ^= std::hash<T>()( value ) + 0x9e3779b9 + ( seed << 6 ) + ( seed >> 2 );
4044
}
4145

46+
//--------------------------------------------------------------------------------------------------
47+
/// Overload for range types (excluding std::string)
48+
//--------------------------------------------------------------------------------------------------
49+
template <std::ranges::range Range>
50+
requires( !std::is_same_v<std::decay_t<Range>, std::string> )
51+
void combineHash( size_t& seed, const Range& range )
52+
{
53+
for ( const auto& elem : range )
54+
{
55+
combineHash( seed, elem );
56+
}
57+
}
58+
4259
//--------------------------------------------------------------------------------------------------
4360
///
4461
//--------------------------------------------------------------------------------------------------

ApplicationLibCode/FileInterface/RifEclipseSummaryAddress.cpp

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ RifEclipseSummaryAddress::RifEclipseSummaryAddress( SummaryCategory category, st
4444
, m_number2( -1 )
4545
, m_isErrorResult( false )
4646
, m_id( -1 )
47+
, m_percentile( -1 )
4748
{
4849
std::pair<int, int> reg2regPair;
4950
switch ( category )
@@ -132,6 +133,7 @@ RifEclipseSummaryAddress::RifEclipseSummaryAddress( SummaryCategory category,
132133
, m_number2( -1 )
133134
, m_isErrorResult( isErrorResult )
134135
, m_id( id )
136+
, m_percentile( -1 )
135137
{
136138
switch ( category )
137139
{
@@ -196,6 +198,7 @@ RifEclipseSummaryAddress::RifEclipseSummaryAddress()
196198
, m_number2( -1 )
197199
, m_isErrorResult( false )
198200
, m_id( -1 )
201+
, m_percentile( -1 )
199202
{
200203
}
201204

@@ -669,8 +672,17 @@ std::string RifEclipseSummaryAddress::uiText() const
669672

670673
if ( isStatistics() )
671674
{
672-
auto prefix = RifEclipseSummaryAddressDefines::statisticsTypeToString( statisticsType() );
673-
text = prefix + ":" + text;
675+
std::string prefix;
676+
if ( statisticsType() == RifEclipseSummaryAddressDefines::StatisticsType::CUSTOM && m_percentile >= MIN_PERCENTILE &&
677+
m_percentile <= MAX_PERCENTILE )
678+
{
679+
prefix = "P" + std::to_string( m_percentile );
680+
}
681+
else
682+
{
683+
prefix = RifEclipseSummaryAddressDefines::statisticsTypeToString( statisticsType() );
684+
}
685+
text = prefix + ":" + text;
674686
}
675687

676688
return text;
@@ -1027,6 +1039,31 @@ void RifEclipseSummaryAddress::setId( int id )
10271039
m_id = id;
10281040
}
10291041

1042+
//--------------------------------------------------------------------------------------------------
1043+
///
1044+
//--------------------------------------------------------------------------------------------------
1045+
int RifEclipseSummaryAddress::percentile() const
1046+
{
1047+
return m_percentile;
1048+
}
1049+
1050+
//--------------------------------------------------------------------------------------------------
1051+
///
1052+
//--------------------------------------------------------------------------------------------------
1053+
void RifEclipseSummaryAddress::setPercentile( int percentile )
1054+
{
1055+
// Validate percentile value: must be in valid range [0, 100] or -1 (unset)
1056+
if ( ( percentile >= MIN_PERCENTILE && percentile <= MAX_PERCENTILE ) || percentile == -1 )
1057+
{
1058+
m_percentile = percentile;
1059+
}
1060+
else
1061+
{
1062+
// Invalid percentile value, set to -1 (unset)
1063+
m_percentile = -1;
1064+
}
1065+
}
1066+
10301067
//--------------------------------------------------------------------------------------------------
10311068
///
10321069
//--------------------------------------------------------------------------------------------------

ApplicationLibCode/FileInterface/RifEclipseSummaryAddress.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ class RifEclipseSummaryAddress
110110
bool isStatistics() const;
111111
StatisticsType statisticsType() const;
112112
void setStatisticsType( StatisticsType type );
113+
int percentile() const;
114+
void setPercentile( int percentile );
113115

114116
int regionNumber() const;
115117
int regionNumber2() const;
@@ -166,6 +168,10 @@ class RifEclipseSummaryAddress
166168
std::string formatUiTextRegionToRegion() const;
167169
static std::pair<int, int> regionToRegionPairFromUiText( const std::string& s );
168170

171+
// Percentile constants
172+
static constexpr int MIN_PERCENTILE = 0;
173+
static constexpr int MAX_PERCENTILE = 100;
174+
169175
private:
170176
static RifEclipseSummaryAddress fromTokens( const std::vector<std::string>& tokens );
171177

@@ -188,6 +194,7 @@ class RifEclipseSummaryAddress
188194
int m_number2;
189195
bool m_isErrorResult;
190196
int m_id;
197+
int m_percentile;
191198
};
192199

193200
QTextStream& operator<<( QTextStream& str, const RifEclipseSummaryAddress& sobj );

ApplicationLibCode/FileInterface/RifEclipseSummaryAddressDefines.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ void caf::AppEnum<RifEclipseSummaryAddressDefines::StatisticsType>::setUp()
3030
addItem( RifEclipseSummaryAddressDefines::StatisticsType::P50, "P50", "P50" );
3131
addItem( RifEclipseSummaryAddressDefines::StatisticsType::P90, "P90", "P90" );
3232
addItem( RifEclipseSummaryAddressDefines::StatisticsType::MEAN, "MEAN", "Mean" );
33+
addItem( RifEclipseSummaryAddressDefines::StatisticsType::CUSTOM, "CUSTOM", "Custom" );
3334
setDefault( RifEclipseSummaryAddressDefines::StatisticsType::NONE );
3435
}
3536

ApplicationLibCode/FileInterface/RifEclipseSummaryAddressDefines.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ enum class StatisticsType
7171
P10,
7272
P50,
7373
P90,
74-
MEAN
74+
MEAN,
75+
CUSTOM
7576
};
7677

7778
enum class CurveType

ApplicationLibCode/ProjectDataModel/RimEnsembleCurveSetInterface.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ class RimEnsembleCurveSetInterface
2828
virtual void updateAllCurves() = 0;
2929
virtual void updateStatisticsCurves() = 0;
3030

31-
virtual bool hasP10Data() const = 0;
32-
virtual bool hasP50Data() const = 0;
33-
virtual bool hasP90Data() const = 0;
34-
virtual bool hasMeanData() const = 0;
31+
virtual bool hasP10Data() const = 0;
32+
virtual bool hasP50Data() const = 0;
33+
virtual bool hasP90Data() const = 0;
34+
virtual bool hasMeanData() const = 0;
35+
virtual bool hasPercentileData( int p ) const = 0;
3536
};

ApplicationLibCode/ProjectDataModel/Summary/RimEnsembleCrossPlotStatisticsCase.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,15 @@ bool RimEnsembleCrossPlotStatisticsCase::hasMeanData() const
280280
return !m_meanData.empty();
281281
}
282282

283+
//--------------------------------------------------------------------------------------------------
284+
///
285+
//--------------------------------------------------------------------------------------------------
286+
bool RimEnsembleCrossPlotStatisticsCase::hasPercentileData( int /*percentile*/ ) const
287+
{
288+
// Custom percentiles are not supported for cross-plot statistics
289+
return false;
290+
}
291+
283292
//--------------------------------------------------------------------------------------------------
284293
///
285294
//--------------------------------------------------------------------------------------------------

ApplicationLibCode/ProjectDataModel/Summary/RimEnsembleCrossPlotStatisticsCase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class RimEnsembleCrossPlotStatisticsCase : public RimSummaryCase, public RifSumm
4444
bool hasP50Data() const;
4545
bool hasP90Data() const;
4646
bool hasMeanData() const;
47+
bool hasPercentileData( int percentile ) const;
4748

4849
QString caseName() const override;
4950
void createSummaryReaderInterface() override;

ApplicationLibCode/ProjectDataModel/Summary/RimEnsembleCurveSet.cpp

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2231,6 +2231,8 @@ void RimEnsembleCurveSet::updateStatisticsCurves( const std::vector<RimSummaryCa
22312231
statCases = group->allSummaryCases();
22322232
}
22332233

2234+
auto percentiles = m_statistics->allPercentiles();
2235+
22342236
if ( isXAxisSummaryVector() )
22352237
{
22362238
m_ensembleStatCaseXY->calculate( statCases,
@@ -2242,7 +2244,7 @@ void RimEnsembleCurveSet::updateStatisticsCurves( const std::vector<RimSummaryCa
22422244
}
22432245
else
22442246
{
2245-
m_ensembleStatCaseY->calculate( statCases, summaryAddressY(), m_statistics->includeIncompleteCurves() );
2247+
m_ensembleStatCaseY->calculate( statCases, summaryAddressY(), m_statistics->includeIncompleteCurves(), percentiles );
22462248
}
22472249
}
22482250

@@ -2289,6 +2291,16 @@ void RimEnsembleCurveSet::updateStatisticsCurves( const std::vector<RimSummaryCa
22892291
return RiaSummaryCurveAddress( xStatAddress, yStatAddress );
22902292
};
22912293

2294+
auto getPercentileAddress = []( int percentile, const RifEclipseSummaryAddress& addrY ) -> RiaSummaryCurveAddress
2295+
{
2296+
auto xStatAddress = RifEclipseSummaryAddress::timeAddress();
2297+
auto yStatAddress = addrY;
2298+
yStatAddress.setStatisticsType( RifEclipseSummaryAddressDefines::StatisticsType::CUSTOM );
2299+
yStatAddress.setPercentile( percentile );
2300+
2301+
return RiaSummaryCurveAddress( xStatAddress, yStatAddress );
2302+
};
2303+
22922304
if ( m_statistics->showP10Curve() && m_ensembleStatCaseY->hasP10Data() )
22932305
addresses.push_back( getStatisticsAddress( RifEclipseSummaryAddressDefines::StatisticsType::P10, dataAddressY ) );
22942306
if ( m_statistics->showP50Curve() && m_ensembleStatCaseY->hasP50Data() )
@@ -2297,6 +2309,18 @@ void RimEnsembleCurveSet::updateStatisticsCurves( const std::vector<RimSummaryCa
22972309
addresses.push_back( getStatisticsAddress( RifEclipseSummaryAddressDefines::StatisticsType::P90, dataAddressY ) );
22982310
if ( m_statistics->showMeanCurve() && m_ensembleStatCaseY->hasMeanData() )
22992311
addresses.push_back( getStatisticsAddress( RifEclipseSummaryAddressDefines::StatisticsType::MEAN, dataAddressY ) );
2312+
2313+
// Add custom percentiles (excluding standard P10/P50/P90 if their checkbox is checked)
2314+
for ( int p : m_statistics->allPercentiles() )
2315+
{
2316+
bool isHandledByCheckbox = ( p == 10 && m_statistics->showP10Curve() ) || ( p == 50 && m_statistics->showP50Curve() ) ||
2317+
( p == 90 && m_statistics->showP90Curve() );
2318+
2319+
if ( !isHandledByCheckbox && m_ensembleStatCaseY->hasPercentileData( p ) )
2320+
{
2321+
addresses.push_back( getPercentileAddress( p, dataAddressY ) );
2322+
}
2323+
}
23002324
}
23012325
}
23022326

@@ -2332,8 +2356,17 @@ void RimEnsembleCurveSet::updateStatisticsCurves( const std::vector<RimSummaryCa
23322356

23332357
if ( m_statistics->showCurveLabels() )
23342358
{
2335-
curve->setSymbolLabel( QString::fromStdString(
2336-
RifEclipseSummaryAddressDefines::statisticsTypeToString( address.summaryAddressY().statisticsType() ) ) );
2359+
QString label;
2360+
auto yAddr = address.summaryAddressY();
2361+
if ( yAddr.statisticsType() == RifEclipseSummaryAddressDefines::StatisticsType::CUSTOM && yAddr.percentile() >= 0 )
2362+
{
2363+
label = QString( "P%1" ).arg( yAddr.percentile() );
2364+
}
2365+
else
2366+
{
2367+
label = QString::fromStdString( RifEclipseSummaryAddressDefines::statisticsTypeToString( yAddr.statisticsType() ) );
2368+
}
2369+
curve->setSymbolLabel( label );
23372370
}
23382371
curve->setLineStyle( RiuQwtPlotCurveDefines::LineStyleEnum::STYLE_SOLID );
23392372
}
@@ -2523,6 +2556,16 @@ bool RimEnsembleCurveSet::hasMeanData() const
25232556
return m_ensembleStatCaseY->hasMeanData();
25242557
}
25252558

2559+
//--------------------------------------------------------------------------------------------------
2560+
///
2561+
//--------------------------------------------------------------------------------------------------
2562+
bool RimEnsembleCurveSet::hasPercentileData( int p ) const
2563+
{
2564+
if ( isXAxisSummaryVector() ) return m_ensembleStatCaseXY->hasPercentileData( p );
2565+
2566+
return m_ensembleStatCaseY->hasPercentileData( p );
2567+
}
2568+
25262569
//--------------------------------------------------------------------------------------------------
25272570
///
25282571
//--------------------------------------------------------------------------------------------------

ApplicationLibCode/ProjectDataModel/Summary/RimEnsembleCurveSet.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ class RimEnsembleCurveSet : public caf::PdmObject, public RimEnsembleCurveSetInt
163163
bool hasP50Data() const override;
164164
bool hasP90Data() const override;
165165
bool hasMeanData() const override;
166+
bool hasPercentileData( int p ) const override;
166167

167168
const RimEnsembleStatistics* statisticsOptions() const;
168169

0 commit comments

Comments
 (0)