mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
Extract RFT cross plot static helpers into RimRftCrossPlotTools, add unit tests
- Move depthTypeAbbreviation(), computePressureSamplesPerCase() and computeMeanPressurePerCase() out of RimParameterRftCrossPlot into a new standalone RimRftCrossPlotTools namespace, shared by RimParameterRftCrossPlot and RimRftTornadoPlot (removing duplicated code in the latter). - Extract pure, reader-independent helpers filterPressuresByDepthRange() and computeMean() so the depth filtering and averaging logic can be unit tested without RFT reader/ensemble mocks. - Add RimRftCrossPlotTools-Test.cpp with gtest cases covering depth range filtering (disabled, inclusive bounds, out-of-range, mismatched sizes) and mean computation (empty/normal), plus depth type abbreviation. Move RimWellLogRftCurve::rftCurveDepthValues into RimRftCrossPlotTools This static helper's only caller was already RimRftCrossPlotTools::computePressureSamplesPerCase, so move it alongside the other RFT cross plot/tornado plot helpers for consistency.
This commit is contained in:
1 parent
9791aaf52a
commit
b35b481ff1
10 files changed
+424
-239
No files matched your search
@@ -9,6 +9,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimRftCorrelationReportPlot.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimRftTornadoPlot.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimCorrelationBarChartTools.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimRftCrossPlotTools.cpp
|
||||
)
|
||||
|
||||
list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES})
|
||||
+29
-153
@@ -21,24 +21,18 @@
|
||||
#include "RiaColorTables.h"
|
||||
#include "RiaPreferences.h"
|
||||
|
||||
#include "RifEclipseRftAddress.h"
|
||||
#include "RifReaderRftInterface.h"
|
||||
|
||||
#include "RigEnsembleParameter.h"
|
||||
#include "RigStatisticsTools.h"
|
||||
|
||||
#include "RiaExtractionTools.h"
|
||||
|
||||
#include "Well/RigEclipseWellLogExtractor.h"
|
||||
|
||||
#include "RimEclipseCase.h"
|
||||
#include "RimEclipseResultCase.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimRftCrossPlotTools.h"
|
||||
#include "RimSummaryCase.h"
|
||||
#include "RimSummaryEnsemble.h"
|
||||
#include "RimSummaryEnsembleTools.h"
|
||||
#include "RimWellLogRftCurve.h"
|
||||
#include "RimWellPath.h"
|
||||
|
||||
#include "RiuContextMenuLauncher.h"
|
||||
#include "RiuDockWidgetTools.h"
|
||||
@@ -66,17 +60,6 @@
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
namespace
|
||||
{
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Short abbreviation used in axis titles and plot titles for the active depth type.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString depthTypeAbbreviation( RiaDefines::DepthType depthType )
|
||||
{
|
||||
return depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH ? "TVD" : "MD";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace caf
|
||||
{
|
||||
template <>
|
||||
@@ -261,114 +244,6 @@ RiuQwtPlotWidget* RimParameterRftCrossPlot::viewer()
|
||||
return m_plotWidget;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
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 {};
|
||||
|
||||
RigEclipseWellLogExtractor* extractor = nullptr;
|
||||
if ( eclipseCase )
|
||||
{
|
||||
RimWellPath* wellPath = RimProject::current()->wellPathFromSimWellName( wellName );
|
||||
extractor = RiaExtractionTools::findOrCreateWellLogExtractor( wellPath, eclipseCase );
|
||||
if ( !extractor ) extractor = RiaExtractionTools::findOrCreateSimWellExtractor( eclipseCase, wellName, false, 0 );
|
||||
}
|
||||
|
||||
const auto& allCases = ensemble->allSummaryCases();
|
||||
|
||||
std::vector<std::vector<double>> samplesPerCase;
|
||||
samplesPerCase.reserve( allCases.size() );
|
||||
|
||||
for ( RimSummaryCase* summaryCase : allCases )
|
||||
{
|
||||
if ( !summaryCase )
|
||||
{
|
||||
samplesPerCase.push_back( {} );
|
||||
continue;
|
||||
}
|
||||
|
||||
RifReaderRftInterface* reader = summaryCase->rftReader();
|
||||
if ( !reader )
|
||||
{
|
||||
samplesPerCase.push_back( {} );
|
||||
continue;
|
||||
}
|
||||
|
||||
auto pressureAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::PRESSURE );
|
||||
std::vector<double> pressures;
|
||||
reader->values( pressureAddress, &pressures );
|
||||
if ( pressures.empty() )
|
||||
{
|
||||
samplesPerCase.push_back( {} );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use the same depth values the RFT curves use for their depth axis, so the filter
|
||||
// operates on values consistent with what the user sees in the RFT plot.
|
||||
std::vector<double> depths = RimWellLogRftCurve::rftCurveDepthValues( reader, wellName, timeStep, extractor, depthType );
|
||||
|
||||
std::vector<double> samplesInRange;
|
||||
if ( useDepthRange )
|
||||
{
|
||||
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 set of samples.
|
||||
samplesPerCase.push_back( {} );
|
||||
continue;
|
||||
}
|
||||
for ( size_t i = 0; i < depths.size(); ++i )
|
||||
if ( depths[i] >= depthRangeMin && depths[i] <= depthRangeMax ) samplesInRange.push_back( pressures[i] );
|
||||
}
|
||||
else
|
||||
{
|
||||
samplesInRange = pressures;
|
||||
}
|
||||
|
||||
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( samples.begin(), samples.end(), 0.0 ) / samples.size() );
|
||||
}
|
||||
|
||||
return pressurePerCase;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -389,14 +264,14 @@ std::vector<RimParameterRftCrossPlot::CaseData> RimParameterRftCrossPlot::create
|
||||
|
||||
if ( m_samplingMode() == SamplingMode::ALL_SAMPLES )
|
||||
{
|
||||
const std::vector<std::vector<double>> samplesPerCase = computePressureSamplesPerCase( m_ensemble(),
|
||||
m_wellName(),
|
||||
m_selectedTimeStep(),
|
||||
m_eclipseCase(),
|
||||
m_useDepthRange(),
|
||||
m_depthRangeMin(),
|
||||
m_depthRangeMax(),
|
||||
m_depthType() );
|
||||
const std::vector<std::vector<double>> samplesPerCase = RimRftCrossPlotTools::computePressureSamplesPerCase( m_ensemble(),
|
||||
m_wellName(),
|
||||
m_selectedTimeStep(),
|
||||
m_eclipseCase(),
|
||||
m_useDepthRange(),
|
||||
m_depthRangeMin(),
|
||||
m_depthRangeMax(),
|
||||
m_depthType() );
|
||||
|
||||
if ( samplesPerCase.size() != allCases.size() ) return {};
|
||||
|
||||
@@ -413,14 +288,14 @@ std::vector<RimParameterRftCrossPlot::CaseData> RimParameterRftCrossPlot::create
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::vector<double> pressurePerCase = computeMeanPressurePerCase( m_ensemble(),
|
||||
m_wellName(),
|
||||
m_selectedTimeStep(),
|
||||
m_eclipseCase(),
|
||||
m_useDepthRange(),
|
||||
m_depthRangeMin(),
|
||||
m_depthRangeMax(),
|
||||
m_depthType() );
|
||||
const std::vector<double> pressurePerCase = RimRftCrossPlotTools::computeMeanPressurePerCase( m_ensemble(),
|
||||
m_wellName(),
|
||||
m_selectedTimeStep(),
|
||||
m_eclipseCase(),
|
||||
m_useDepthRange(),
|
||||
m_depthRangeMin(),
|
||||
m_depthRangeMax(),
|
||||
m_depthType() );
|
||||
|
||||
if ( pressurePerCase.size() != allCases.size() ) return {};
|
||||
|
||||
@@ -461,7 +336,7 @@ void RimParameterRftCrossPlot::updateAxes()
|
||||
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( RimRftCrossPlotTools::depthTypeAbbreviation( m_depthType() ) )
|
||||
.arg( m_depthRangeMin() )
|
||||
.arg( m_depthRangeMax() )
|
||||
: pressureLabel;
|
||||
@@ -641,7 +516,8 @@ void RimParameterRftCrossPlot::defineUiOrdering( QString uiConfigName, caf::PdmU
|
||||
dataGroup->add( &m_selectedTimeStep );
|
||||
dataGroup->add( &m_eclipseCase );
|
||||
|
||||
auto* depthGroup = uiOrdering.addNewGroup( QString( "Depth Range (%1)" ).arg( depthTypeAbbreviation( m_depthType() ) ) );
|
||||
auto* depthGroup =
|
||||
uiOrdering.addNewGroup( QString( "Depth Range (%1)" ).arg( RimRftCrossPlotTools::depthTypeAbbreviation( m_depthType() ) ) );
|
||||
depthGroup->add( &m_useDepthRange );
|
||||
depthGroup->add( &m_depthRangeMin );
|
||||
depthGroup->add( &m_depthRangeMax );
|
||||
@@ -761,14 +637,14 @@ QList<caf::PdmOptionItemInfo> RimParameterRftCrossPlot::calculateValueOptions( c
|
||||
std::vector<double> pressurePerCase;
|
||||
if ( canComputeCorrelation )
|
||||
{
|
||||
pressurePerCase = computeMeanPressurePerCase( m_ensemble(),
|
||||
m_wellName(),
|
||||
m_selectedTimeStep(),
|
||||
m_eclipseCase(),
|
||||
m_useDepthRange(),
|
||||
m_depthRangeMin(),
|
||||
m_depthRangeMax(),
|
||||
m_depthType() );
|
||||
pressurePerCase = RimRftCrossPlotTools::computeMeanPressurePerCase( m_ensemble(),
|
||||
m_wellName(),
|
||||
m_selectedTimeStep(),
|
||||
m_eclipseCase(),
|
||||
m_useDepthRange(),
|
||||
m_depthRangeMin(),
|
||||
m_depthRangeMax(),
|
||||
m_depthType() );
|
||||
}
|
||||
|
||||
// Compute correlation for each numeric parameter, then sort by abs value descending
|
||||
@@ -878,7 +754,7 @@ void RimParameterRftCrossPlot::updatePlotTitle()
|
||||
{
|
||||
m_description = QString( "%1 vs RFT Pressure [%2 %3 - %4 m], %5" )
|
||||
.arg( m_ensembleParameter() )
|
||||
.arg( depthTypeAbbreviation( m_depthType() ) )
|
||||
.arg( RimRftCrossPlotTools::depthTypeAbbreviation( m_depthType() ) )
|
||||
.arg( m_depthRangeMin() )
|
||||
.arg( m_depthRangeMax() )
|
||||
.arg( m_ensemble->name() );
|
||||
|
||||
@@ -89,29 +89,6 @@ public:
|
||||
|
||||
std::vector<CaseData> createCaseData() const;
|
||||
|
||||
// Computes mean RFT pressure per ensemble case (one entry per case, infinity = no data).
|
||||
// Indices match ensemble->allSummaryCases().
|
||||
static std::vector<double> computeMeanPressurePerCase( RimSummaryEnsemble* ensemble,
|
||||
const QString& wellName,
|
||||
const QDateTime& timeStep,
|
||||
RimEclipseResultCase* eclipseCase,
|
||||
bool useDepthRange,
|
||||
double depthRangeMin,
|
||||
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 {}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 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 "RimRftCrossPlotTools.h"
|
||||
|
||||
#include "RifEclipseRftAddress.h"
|
||||
#include "RifReaderRftInterface.h"
|
||||
|
||||
#include "RiaExtractionTools.h"
|
||||
|
||||
#include "Well/RigEclipseWellLogExtractor.h"
|
||||
|
||||
#include "RimEclipseResultCase.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimSummaryCase.h"
|
||||
#include "RimSummaryEnsemble.h"
|
||||
#include "RimWellPath.h"
|
||||
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimRftCrossPlotTools::depthTypeAbbreviation( RiaDefines::DepthType depthType )
|
||||
{
|
||||
return depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH ? "TVD" : "MD";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<double> RimRftCrossPlotTools::rftCurveDepthValues( RifReaderRftInterface* reader,
|
||||
const QString& wellName,
|
||||
const QDateTime& timeStep,
|
||||
RigEclipseWellLogExtractor* extractor,
|
||||
RiaDefines::DepthType depthType )
|
||||
{
|
||||
if ( !reader ) return {};
|
||||
|
||||
if ( depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH )
|
||||
{
|
||||
auto tvdAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::TVD );
|
||||
std::vector<double> tvdDepths;
|
||||
reader->values( tvdAddress, &tvdDepths );
|
||||
return tvdDepths;
|
||||
}
|
||||
|
||||
auto mdAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::MD );
|
||||
std::vector<double> depths;
|
||||
reader->values( mdAddress, &depths );
|
||||
if ( depths.empty() && extractor ) depths = reader->computeMeasuredDepth( wellName, timeStep, extractor );
|
||||
return depths;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<double> RimRftCrossPlotTools::filterPressuresByDepthRange( const std::vector<double>& depths,
|
||||
const std::vector<double>& pressures,
|
||||
bool useDepthRange,
|
||||
double depthRangeMin,
|
||||
double depthRangeMax )
|
||||
{
|
||||
if ( !useDepthRange ) return pressures;
|
||||
|
||||
// Depth filter requested but no aligned depth data is available; exclude rather than
|
||||
// silently return an unfiltered set of samples.
|
||||
if ( depths.size() != pressures.size() ) return {};
|
||||
|
||||
std::vector<double> samplesInRange;
|
||||
for ( size_t i = 0; i < depths.size(); ++i )
|
||||
if ( depths[i] >= depthRangeMin && depths[i] <= depthRangeMax ) samplesInRange.push_back( pressures[i] );
|
||||
|
||||
return samplesInRange;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
double RimRftCrossPlotTools::computeMean( const std::vector<double>& samples )
|
||||
{
|
||||
if ( samples.empty() ) return std::numeric_limits<double>::infinity();
|
||||
|
||||
return std::accumulate( samples.begin(), samples.end(), 0.0 ) / samples.size();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<std::vector<double>> RimRftCrossPlotTools::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 {};
|
||||
|
||||
RigEclipseWellLogExtractor* extractor = nullptr;
|
||||
if ( eclipseCase )
|
||||
{
|
||||
RimWellPath* wellPath = RimProject::current()->wellPathFromSimWellName( wellName );
|
||||
extractor = RiaExtractionTools::findOrCreateWellLogExtractor( wellPath, eclipseCase );
|
||||
if ( !extractor ) extractor = RiaExtractionTools::findOrCreateSimWellExtractor( eclipseCase, wellName, false, 0 );
|
||||
}
|
||||
|
||||
const auto& allCases = ensemble->allSummaryCases();
|
||||
|
||||
std::vector<std::vector<double>> samplesPerCase;
|
||||
samplesPerCase.reserve( allCases.size() );
|
||||
|
||||
for ( RimSummaryCase* summaryCase : allCases )
|
||||
{
|
||||
if ( !summaryCase )
|
||||
{
|
||||
samplesPerCase.push_back( {} );
|
||||
continue;
|
||||
}
|
||||
|
||||
RifReaderRftInterface* reader = summaryCase->rftReader();
|
||||
if ( !reader )
|
||||
{
|
||||
samplesPerCase.push_back( {} );
|
||||
continue;
|
||||
}
|
||||
|
||||
auto pressureAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::PRESSURE );
|
||||
std::vector<double> pressures;
|
||||
reader->values( pressureAddress, &pressures );
|
||||
if ( pressures.empty() )
|
||||
{
|
||||
samplesPerCase.push_back( {} );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use the same depth values the RFT curves use for their depth axis, so the filter
|
||||
// operates on values consistent with what the user sees in the RFT plot.
|
||||
std::vector<double> depths = rftCurveDepthValues( reader, wellName, timeStep, extractor, depthType );
|
||||
|
||||
samplesPerCase.push_back( filterPressuresByDepthRange( depths, pressures, useDepthRange, depthRangeMin, depthRangeMax ) );
|
||||
}
|
||||
|
||||
return samplesPerCase;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<double> RimRftCrossPlotTools::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 )
|
||||
pressurePerCase.push_back( computeMean( samples ) );
|
||||
|
||||
return pressurePerCase;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 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.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma once
|
||||
|
||||
#include "RiaDefines.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
|
||||
#include <vector>
|
||||
|
||||
class RifReaderRftInterface;
|
||||
class RigEclipseWellLogExtractor;
|
||||
class RimEclipseResultCase;
|
||||
class RimSummaryEnsemble;
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
/// Helpers for filtering and reducing RFT pressure data by depth range, shared by the RFT
|
||||
/// correlation parameter cross plot (RimParameterRftCrossPlot) and tornado plot (RimRftTornadoPlot).
|
||||
///
|
||||
//==================================================================================================
|
||||
namespace RimRftCrossPlotTools
|
||||
{
|
||||
// Short abbreviation used in axis titles and plot titles for the active depth type.
|
||||
QString depthTypeAbbreviation( RiaDefines::DepthType depthType );
|
||||
|
||||
// Returns the depth values for the given well/time step, strictly matching depthType: MD
|
||||
// (native RFT MD channel, falling back to extractor-derived MD if the channel is missing) or
|
||||
// TVD (native RFT TVD channel). Returns an empty vector if the requested depth type is not
|
||||
// available, rather than silently substituting the other depth type — callers that filter by
|
||||
// depth range rely on this to exclude/blank out data instead of comparing a range against the
|
||||
// wrong unit.
|
||||
std::vector<double> rftCurveDepthValues( RifReaderRftInterface* reader,
|
||||
const QString& wellName,
|
||||
const QDateTime& timeStep,
|
||||
RigEclipseWellLogExtractor* extractor,
|
||||
RiaDefines::DepthType depthType = RiaDefines::DepthType::MEASURED_DEPTH );
|
||||
|
||||
// Returns the subset of pressures whose corresponding depth is within [depthRangeMin, depthRangeMax].
|
||||
// If useDepthRange is false, all pressures are returned unfiltered. If useDepthRange is true and
|
||||
// depths/pressures are not the same size, an empty vector is returned (no aligned depth data).
|
||||
std::vector<double> filterPressuresByDepthRange( const std::vector<double>& depths,
|
||||
const std::vector<double>& pressures,
|
||||
bool useDepthRange,
|
||||
double depthRangeMin,
|
||||
double depthRangeMax );
|
||||
|
||||
// Arithmetic mean of the given samples, or infinity if samples is empty.
|
||||
double computeMean( const std::vector<double>& samples );
|
||||
|
||||
// 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.
|
||||
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 );
|
||||
|
||||
// Computes mean RFT pressure per ensemble case (one entry per case, infinity = no data).
|
||||
// Indices match ensemble->allSummaryCases().
|
||||
std::vector<double> computeMeanPressurePerCase( RimSummaryEnsemble* ensemble,
|
||||
const QString& wellName,
|
||||
const QDateTime& timeStep,
|
||||
RimEclipseResultCase* eclipseCase,
|
||||
bool useDepthRange,
|
||||
double depthRangeMin,
|
||||
double depthRangeMax,
|
||||
RiaDefines::DepthType depthType = RiaDefines::DepthType::MEASURED_DEPTH );
|
||||
} // namespace RimRftCrossPlotTools
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#include "RimCorrelationBarChartTools.h"
|
||||
#include "RimEclipseResultCase.h"
|
||||
#include "RimParameterRftCrossPlot.h"
|
||||
#include "RimRftCrossPlotTools.h"
|
||||
#include "RimSummaryEnsemble.h"
|
||||
#include "RimSummaryEnsembleTools.h"
|
||||
|
||||
@@ -46,17 +46,6 @@
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
namespace
|
||||
{
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Short abbreviation used in the plot title for the active depth type.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString depthTypeAbbreviation( RiaDefines::DepthType depthType )
|
||||
{
|
||||
return depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH ? "TVD" : "MD";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CAF_PDM_SOURCE_INIT( RimRftTornadoPlot, "RftTornadoPlot" );
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -383,14 +372,14 @@ std::map<QString, double> RimRftTornadoPlot::addDataToChartBuilder( RiuGroupedBa
|
||||
const auto& allCases = m_ensemble->allSummaryCases();
|
||||
|
||||
// Build pressure vector per case (indices match allCases order)
|
||||
const std::vector<double> pressurePerCase = RimParameterRftCrossPlot::computeMeanPressurePerCase( m_ensemble(),
|
||||
m_wellName(),
|
||||
m_selectedTimeStep(),
|
||||
m_eclipseCase(),
|
||||
m_useDepthRange(),
|
||||
m_depthRangeMin(),
|
||||
m_depthRangeMax(),
|
||||
m_depthType() );
|
||||
const std::vector<double> pressurePerCase = RimRftCrossPlotTools::computeMeanPressurePerCase( m_ensemble(),
|
||||
m_wellName(),
|
||||
m_selectedTimeStep(),
|
||||
m_eclipseCase(),
|
||||
m_useDepthRange(),
|
||||
m_depthRangeMin(),
|
||||
m_depthRangeMax(),
|
||||
m_depthType() );
|
||||
|
||||
// For each numeric parameter, compute Pearson correlation against pressurePerCase
|
||||
for ( const auto& param : RimSummaryEnsembleTools::alphabeticEnsembleParameters( allCases ) )
|
||||
@@ -432,11 +421,12 @@ void RimRftTornadoPlot::updatePlotTitle()
|
||||
|
||||
if ( m_useAutoPlotTitle() && m_ensemble() )
|
||||
{
|
||||
const QString rangeStr =
|
||||
m_useDepthRange()
|
||||
? QString( " [%1 %2 - %3 m]" ).arg( depthTypeAbbreviation( m_depthType() ) ).arg( m_depthRangeMin() ).arg( m_depthRangeMax() )
|
||||
: QString();
|
||||
m_description = QString( "Parameter Correlation vs RFT Pressure%1, %2" ).arg( rangeStr ).arg( m_ensemble->name() );
|
||||
const QString rangeStr = m_useDepthRange() ? QString( " [%1 %2 - %3 m]" )
|
||||
.arg( RimRftCrossPlotTools::depthTypeAbbreviation( m_depthType() ) )
|
||||
.arg( m_depthRangeMin() )
|
||||
.arg( m_depthRangeMax() )
|
||||
: QString();
|
||||
m_description = QString( "Parameter Correlation vs RFT Pressure%1, %2" ).arg( rangeStr ).arg( m_ensemble->name() );
|
||||
}
|
||||
|
||||
m_plotWidget->setPlotTitle( m_description() );
|
||||
|
||||
@@ -1220,32 +1220,6 @@ std::vector<double> RimWellLogRftCurve::measuredDepthValues( QString& prefixText
|
||||
return {};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<double> RimWellLogRftCurve::rftCurveDepthValues( RifReaderRftInterface* reader,
|
||||
const QString& wellName,
|
||||
const QDateTime& timeStep,
|
||||
RigEclipseWellLogExtractor* extractor,
|
||||
RiaDefines::DepthType depthType )
|
||||
{
|
||||
if ( !reader ) return {};
|
||||
|
||||
if ( depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH )
|
||||
{
|
||||
auto tvdAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::TVD );
|
||||
std::vector<double> tvdDepths;
|
||||
reader->values( tvdAddress, &tvdDepths );
|
||||
return tvdDepths;
|
||||
}
|
||||
|
||||
auto mdAddress = RifEclipseRftAddress::createAddress( wellName, timeStep, RifEclipseRftAddress::RftWellLogChannelType::MD );
|
||||
std::vector<double> depths;
|
||||
reader->values( mdAddress, &depths );
|
||||
if ( depths.empty() && extractor ) depths = reader->computeMeasuredDepth( wellName, timeStep, extractor );
|
||||
return depths;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -96,18 +96,6 @@ public:
|
||||
|
||||
void setScaleFactor( double factor );
|
||||
|
||||
// Returns the depth values for the given well/time step, strictly matching depthType: MD
|
||||
// (native RFT MD channel, falling back to extractor-derived MD if the channel is missing) or
|
||||
// TVD (native RFT TVD channel). Returns an empty vector if the requested depth type is not
|
||||
// available, rather than silently substituting the other depth type — callers that filter by
|
||||
// depth range rely on this to exclude/blank out data instead of comparing a range against the
|
||||
// wrong unit.
|
||||
static std::vector<double> rftCurveDepthValues( RifReaderRftInterface* reader,
|
||||
const QString& wellName,
|
||||
const QDateTime& timeStep,
|
||||
RigEclipseWellLogExtractor* extractor,
|
||||
RiaDefines::DepthType depthType = RiaDefines::DepthType::MEASURED_DEPTH );
|
||||
|
||||
protected:
|
||||
QString createCurveAutoName() override;
|
||||
QString createCurveNameFromTemplate( const QString& templateText ) override;
|
||||
|
||||
@@ -94,6 +94,7 @@ set(SOURCE_UNITTEST_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellPathFracture-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellPathValve-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellPathGeometryDef-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimRftCrossPlotTools-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellRftPlot-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellTargetMapping-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWorkflowDescribeTools-Test.cpp
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 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 "gtest/gtest.h"
|
||||
|
||||
#include "RimRftCrossPlotTools.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// When depth-range filtering is disabled, all pressures are returned unmodified.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimRftCrossPlotToolsTest, FilterPressuresByDepthRange_DisabledReturnsAllSamples )
|
||||
{
|
||||
std::vector<double> depths{ 1000.0, 2000.0, 3000.0 };
|
||||
std::vector<double> pressures{ 10.0, 20.0, 30.0 };
|
||||
|
||||
auto filtered = RimRftCrossPlotTools::filterPressuresByDepthRange( depths, pressures, false, 1500.0, 2500.0 );
|
||||
|
||||
EXPECT_EQ( pressures, filtered );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// When enabled, only pressures whose depth falls within [min, max] (inclusive) are returned.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimRftCrossPlotToolsTest, FilterPressuresByDepthRange_FiltersByInclusiveRange )
|
||||
{
|
||||
std::vector<double> depths{ 1000.0, 1500.0, 2000.0, 2500.0, 3000.0 };
|
||||
std::vector<double> pressures{ 10.0, 15.0, 20.0, 25.0, 30.0 };
|
||||
|
||||
auto filtered = RimRftCrossPlotTools::filterPressuresByDepthRange( depths, pressures, true, 1500.0, 2500.0 );
|
||||
|
||||
std::vector<double> expected{ 15.0, 20.0, 25.0 };
|
||||
EXPECT_EQ( expected, filtered );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Depths outside the requested range are excluded.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimRftCrossPlotToolsTest, FilterPressuresByDepthRange_ExcludesOutsideRange )
|
||||
{
|
||||
std::vector<double> depths{ 500.0, 4000.0 };
|
||||
std::vector<double> pressures{ 5.0, 40.0 };
|
||||
|
||||
auto filtered = RimRftCrossPlotTools::filterPressuresByDepthRange( depths, pressures, true, 1000.0, 3000.0 );
|
||||
|
||||
EXPECT_TRUE( filtered.empty() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Mismatched depths/pressures sizes cannot be aligned, so filtering returns an empty result.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimRftCrossPlotToolsTest, FilterPressuresByDepthRange_MismatchedSizesReturnsEmpty )
|
||||
{
|
||||
std::vector<double> depths{ 1000.0, 2000.0 };
|
||||
std::vector<double> pressures{ 10.0 };
|
||||
|
||||
auto filtered = RimRftCrossPlotTools::filterPressuresByDepthRange( depths, pressures, true, 0.0, 5000.0 );
|
||||
|
||||
EXPECT_TRUE( filtered.empty() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// The mean of an empty sample set is defined as infinity (used as a "no data" sentinel).
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimRftCrossPlotToolsTest, ComputeMean_EmptyReturnsInfinity )
|
||||
{
|
||||
std::vector<double> samples;
|
||||
|
||||
EXPECT_EQ( std::numeric_limits<double>::infinity(), RimRftCrossPlotTools::computeMean( samples ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// The mean of a normal set of samples is the arithmetic average.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimRftCrossPlotToolsTest, ComputeMean_NormalAverage )
|
||||
{
|
||||
std::vector<double> samples{ 10.0, 20.0, 30.0 };
|
||||
|
||||
EXPECT_DOUBLE_EQ( 20.0, RimRftCrossPlotTools::computeMean( samples ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// The depth type abbreviation used in axis/plot titles distinguishes MD from TVD.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimRftCrossPlotToolsTest, DepthTypeAbbreviation )
|
||||
{
|
||||
EXPECT_EQ( QString( "MD" ), RimRftCrossPlotTools::depthTypeAbbreviation( RiaDefines::DepthType::MEASURED_DEPTH ) );
|
||||
EXPECT_EQ( QString( "TVD" ), RimRftCrossPlotTools::depthTypeAbbreviation( RiaDefines::DepthType::TRUE_VERTICAL_DEPTH ) );
|
||||
}
|
||||
Reference in new issue
Block a user