Add All Samples / Mean per Realization sampling mode to RFT parameter cross plot

Adds a 'Sampling' dropdown to RimParameterRftCrossPlot: 'Mean per Realization' (previous, default behavior) plots one point per case using the mean RFT pressure within the depth range, while 'All Samples' plots one point per individual RFT pressure sample within the depth range, grouped per case/color. Extracted the shared per-case depth filtering logic into computePressureSamplesPerCase(), with computeMeanPressurePerCase() now built on top of it. Axis labels and exported ASCII data adapt to the selected mode.
This commit is contained in:
Magne Sjaastad committed 2026-10-06 11:18:06 +02:00
1 parent 07b82a52a9
commit 9791aaf52a
2 files changed
+156 -44

No files matched your search

@@ -63,6 +63,7 @@
#include <QPaintDevice>
#include <limits>
#include <map>
#include <numeric>
namespace
@@ -76,6 +77,17 @@ QString depthTypeAbbreviation( RiaDefines::DepthType depthType )
}
} // namespace
namespace caf
{
template <>
void caf::AppEnum<RimParameterRftCrossPlot::SamplingMode>::setUp()
{
addItem( RimParameterRftCrossPlot::SamplingMode::ALL_SAMPLES, "ALL_SAMPLES", "All Samples" );
addItem( RimParameterRftCrossPlot::SamplingMode::MEAN_PER_REALIZATION, "MEAN_PER_REALIZATION", "Mean per Realization" );
setDefault( RimParameterRftCrossPlot::SamplingMode::MEAN_PER_REALIZATION );
}
} // namespace caf
CAF_PDM_SOURCE_INIT( RimParameterRftCrossPlot, "ParameterRftCrossPlot" );
//--------------------------------------------------------------------------------------------------
@@ -96,6 +108,7 @@ RimParameterRftCrossPlot::RimParameterRftCrossPlot()
CAF_PDM_InitField( &m_depthRangeMax, "DepthRangeMax", 5000.0, "Max Depth" );
CAF_PDM_InitField( &m_depthType, "DepthType", caf::AppEnum<RiaDefines::DepthType>( RiaDefines::DepthType::MEASURED_DEPTH ), "Depth Type" );
m_depthType.uiCapability()->setUiHidden( true ); // driven by the parent RimRftCorrelationReportPlot
CAF_PDM_InitField( &m_samplingMode, "SamplingMode", SamplingModeEnum( SamplingMode::MEAN_PER_REALIZATION ), "Sampling" );
CAF_PDM_InitField( &m_ensembleParameter, "EnsembleParameter", QString(), "Ensemble Parameter" );
m_ensembleParameter.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() );
@@ -251,14 +264,14 @@ RiuQwtPlotWidget* RimParameterRftCrossPlot::viewer()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RimParameterRftCrossPlot::computeMeanPressurePerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
bool useDepthRange,
double depthRangeMin,
double depthRangeMax,
RiaDefines::DepthType depthType )
std::vector<std::vector<double>> RimParameterRftCrossPlot::computePressureSamplesPerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
bool useDepthRange,
double depthRangeMin,
double depthRangeMax,
RiaDefines::DepthType depthType )
{
if ( !ensemble || wellName.isEmpty() || !timeStep.isValid() ) return {};
@@ -272,21 +285,21 @@ std::vector<double> RimParameterRftCrossPlot::computeMeanPressurePerCase( RimSum
const auto& allCases = ensemble->allSummaryCases();
std::vector<double> pressurePerCase;
pressurePerCase.reserve( allCases.size() );
std::vector<std::vector<double>> samplesPerCase;
samplesPerCase.reserve( allCases.size() );
for ( RimSummaryCase* summaryCase : allCases )
{
if ( !summaryCase )
{
pressurePerCase.push_back( std::numeric_limits<double>::infinity() );
samplesPerCase.push_back( {} );
continue;
}
RifReaderRftInterface* reader = summaryCase->rftReader();
if ( !reader )
{
pressurePerCase.push_back( std::numeric_limits<double>::infinity() );
samplesPerCase.push_back( {} );
continue;
}
@@ -295,7 +308,7 @@ std::vector<double> RimParameterRftCrossPlot::computeMeanPressurePerCase( RimSum
reader->values( pressureAddress, &pressures );
if ( pressures.empty() )
{
pressurePerCase.push_back( std::numeric_limits<double>::infinity() );
samplesPerCase.push_back( {} );
continue;
}
@@ -309,8 +322,8 @@ std::vector<double> RimParameterRftCrossPlot::computeMeanPressurePerCase( RimSum
if ( depths.size() != pressures.size() )
{
// Depth filter requested but no aligned depth data is available for this case;
// exclude rather than silently return an unfiltered mean.
pressurePerCase.push_back( std::numeric_limits<double>::infinity() );
// exclude rather than silently return an unfiltered set of samples.
samplesPerCase.push_back( {} );
continue;
}
for ( size_t i = 0; i < depths.size(); ++i )
@@ -321,10 +334,36 @@ std::vector<double> RimParameterRftCrossPlot::computeMeanPressurePerCase( RimSum
samplesInRange = pressures;
}
if ( samplesInRange.empty() )
samplesPerCase.push_back( samplesInRange );
}
return samplesPerCase;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RimParameterRftCrossPlot::computeMeanPressurePerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
bool useDepthRange,
double depthRangeMin,
double depthRangeMax,
RiaDefines::DepthType depthType )
{
const std::vector<std::vector<double>> samplesPerCase =
computePressureSamplesPerCase( ensemble, wellName, timeStep, eclipseCase, useDepthRange, depthRangeMin, depthRangeMax, depthType );
std::vector<double> pressurePerCase;
pressurePerCase.reserve( samplesPerCase.size() );
for ( const std::vector<double>& samples : samplesPerCase )
{
if ( samples.empty() )
pressurePerCase.push_back( std::numeric_limits<double>::infinity() );
else
pressurePerCase.push_back( std::accumulate( samplesInRange.begin(), samplesInRange.end(), 0.0 ) / samplesInRange.size() );
pressurePerCase.push_back( std::accumulate( samples.begin(), samples.end(), 0.0 ) / samples.size() );
}
return pressurePerCase;
@@ -345,29 +384,57 @@ std::vector<RimParameterRftCrossPlot::CaseData> RimParameterRftCrossPlot::create
const auto& allCases = m_ensemble->allSummaryCases();
const std::vector<double> pressurePerCase = computeMeanPressurePerCase( m_ensemble(),
m_wellName(),
m_selectedTimeStep(),
m_eclipseCase(),
m_useDepthRange(),
m_depthRangeMin(),
m_depthRangeMax(),
m_depthType() );
if ( pressurePerCase.size() != allCases.size() ) return {};
std::vector<CaseData> result;
result.reserve( allCases.size() );
for ( size_t caseIdx = 0; caseIdx < allCases.size(); ++caseIdx )
if ( m_samplingMode() == SamplingMode::ALL_SAMPLES )
{
RimSummaryCase* summaryCase = allCases[caseIdx];
if ( !summaryCase ) continue;
if ( std::isinf( pressurePerCase[caseIdx] ) ) continue;
if ( caseIdx >= static_cast<size_t>( parameter.values.size() ) ) continue;
const std::vector<std::vector<double>> samplesPerCase = computePressureSamplesPerCase( m_ensemble(),
m_wellName(),
m_selectedTimeStep(),
m_eclipseCase(),
m_useDepthRange(),
m_depthRangeMin(),
m_depthRangeMax(),
m_depthType() );
result.push_back(
{ .parameterValue = parameter.values[caseIdx].toDouble(), .pressureValue = pressurePerCase[caseIdx], .summaryCase = summaryCase } );
if ( samplesPerCase.size() != allCases.size() ) return {};
for ( size_t caseIdx = 0; caseIdx < allCases.size(); ++caseIdx )
{
RimSummaryCase* summaryCase = allCases[caseIdx];
if ( !summaryCase ) continue;
if ( caseIdx >= static_cast<size_t>( parameter.values.size() ) ) continue;
const double parameterValue = parameter.values[caseIdx].toDouble();
for ( double pressureValue : samplesPerCase[caseIdx] )
result.push_back( { .parameterValue = parameterValue, .pressureValue = pressureValue, .summaryCase = summaryCase } );
}
}
else
{
const std::vector<double> pressurePerCase = computeMeanPressurePerCase( m_ensemble(),
m_wellName(),
m_selectedTimeStep(),
m_eclipseCase(),
m_useDepthRange(),
m_depthRangeMin(),
m_depthRangeMax(),
m_depthType() );
if ( pressurePerCase.size() != allCases.size() ) return {};
for ( size_t caseIdx = 0; caseIdx < allCases.size(); ++caseIdx )
{
RimSummaryCase* summaryCase = allCases[caseIdx];
if ( !summaryCase ) continue;
if ( std::isinf( pressurePerCase[caseIdx] ) ) continue;
if ( caseIdx >= static_cast<size_t>( parameter.values.size() ) ) continue;
result.push_back( { .parameterValue = parameter.values[caseIdx].toDouble(),
.pressureValue = pressurePerCase[caseIdx],
.summaryCase = summaryCase } );
}
}
return result;
@@ -391,10 +458,13 @@ void RimParameterRftCrossPlot::updateAxes()
const int axisTitleSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisTitleFontSize() );
const int axisValueSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisValueFontSize() );
const QString depthLabel =
m_useDepthRange()
? QString( "Mean Pressure [%1 %2 - %3]" ).arg( depthTypeAbbreviation( m_depthType() ) ).arg( m_depthRangeMin() ).arg( m_depthRangeMax() )
: QString( "Mean Pressure" );
const QString pressureLabel = m_samplingMode() == SamplingMode::ALL_SAMPLES ? "Pressure" : "Mean Pressure";
const QString depthLabel = m_useDepthRange() ? QString( "%1 [%2 %3 - %4]" )
.arg( pressureLabel )
.arg( depthTypeAbbreviation( m_depthType() ) )
.arg( m_depthRangeMin() )
.arg( m_depthRangeMax() )
: pressureLabel;
m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultLeft(), depthLabel );
m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultLeft(), true );
@@ -420,8 +490,9 @@ void RimParameterRftCrossPlot::updateAxes()
//--------------------------------------------------------------------------------------------------
QString RimParameterRftCrossPlot::asciiDataForPlotExport() const
{
QString asciiData;
asciiData += "Realization\tParameter\tMean Pressure\n";
QString asciiData;
const QString pressureLabel = m_samplingMode() == SamplingMode::ALL_SAMPLES ? "Pressure" : "Mean Pressure";
asciiData += QString( "Realization\tParameter\t%1\n" ).arg( pressureLabel );
for ( const auto& [paramValue, pressureValue, summaryCase] : createCaseData() )
{
asciiData += QString( "%1\t%2\t%3\n" ).arg( summaryCase->displayCaseName() ).arg( paramValue ).arg( pressureValue );
@@ -579,6 +650,7 @@ void RimParameterRftCrossPlot::defineUiOrdering( QString uiConfigName, caf::PdmU
auto* crossPlotGroup = uiOrdering.addNewGroup( "Cross Plot Parameter" );
crossPlotGroup->add( &m_ensembleParameter );
crossPlotGroup->add( &m_samplingMode );
auto* plotGroup = uiOrdering.addNewGroup( "Plot Settings" );
plotGroup->setCollapsedByDefault();
@@ -757,11 +829,29 @@ void RimParameterRftCrossPlot::createPoints()
}
}
int idx = 0;
// Group points by summary case so "All Samples" mode draws one curve (and color) per
// realization, with all its samples, instead of one curve per individual sample.
std::vector<RimSummaryCase*> caseOrder;
std::map<RimSummaryCase*, std::pair<std::vector<double>, std::vector<double>>> pointsPerCase;
for ( const auto& [paramValue, pressureValue, summaryCase] : caseData )
{
auto it = pointsPerCase.find( summaryCase );
if ( it == pointsPerCase.end() )
{
caseOrder.push_back( summaryCase );
it = pointsPerCase.emplace( summaryCase, std::make_pair( std::vector<double>{}, std::vector<double>{} ) ).first;
}
it->second.first.push_back( paramValue );
it->second.second.push_back( pressureValue );
}
int idx = 0;
for ( RimSummaryCase* summaryCase : caseOrder )
{
const auto& [xValues, yValues] = pointsPerCase[summaryCase];
auto* plotCurve = new RiuQwtPlotCurve;
plotCurve->setSamplesValues( { paramValue }, { pressureValue } );
plotCurve->setSamplesValues( xValues, yValues );
plotCurve->setStyle( QwtPlotCurve::NoCurve );
const bool isSelected = selectedSummaryCases.contains( summaryCase );
@@ -48,10 +48,19 @@ class RimParameterRftCrossPlot : public RimPlot
CAF_PDM_HEADER_INIT;
public:
// Determines how the RFT pressure samples within the selected depth range are reduced to
// (ensemble parameter, pressure) points shown in the cross plot.
enum class SamplingMode
{
ALL_SAMPLES, // One point per individual RFT sample within the depth range.
MEAN_PER_REALIZATION // One point per realization, using the mean pressure within the depth range.
};
using SamplingModeEnum = caf::AppEnum<SamplingMode>;
struct CaseData
{
double parameterValue;
double pressureValue; // mean pressure in depth range
double pressureValue; // pressure sample, or mean pressure in depth range
RimSummaryCase* summaryCase;
};
@@ -91,6 +100,18 @@ public:
double depthRangeMax,
RiaDefines::DepthType depthType = RiaDefines::DepthType::MEASURED_DEPTH );
// Computes the individual RFT pressure samples within the depth range per ensemble case.
// Indices match ensemble->allSummaryCases(). A case with no data gets an empty vector.
static std::vector<std::vector<double>>
computePressureSamplesPerCase( RimSummaryEnsemble* ensemble,
const QString& wellName,
const QDateTime& timeStep,
RimEclipseResultCase* eclipseCase,
bool useDepthRange,
double depthRangeMin,
double depthRangeMax,
RiaDefines::DepthType depthType = RiaDefines::DepthType::MEASURED_DEPTH );
// RimPlot pure virtual overrides
RiuPlotWidget* plotWidget() override;
void setAutoScaleXEnabled( bool ) override {}
@@ -131,6 +152,7 @@ private:
caf::PdmField<double> m_depthRangeMin;
caf::PdmField<double> m_depthRangeMax;
caf::PdmField<caf::AppEnum<RiaDefines::DepthType>> m_depthType;
caf::PdmField<SamplingModeEnum> m_samplingMode;
caf::PdmField<QString> m_ensembleParameter;
caf::PdmField<bool> m_useAutoPlotTitle;