Well target updates (#14619)

Support well target mapping calculations for ensembles.
This commit is contained in:
JJ authored and GitHub committed 2026-08-28 13:36:02 +02:00
1 parent 04be720491
commit 75ebd7f20b
19 files changed
+248 -81

No files matched your search

@@ -614,6 +614,11 @@ void RimStatisticsContourMap::onComputeStatisticsClicked()
void RimStatisticsContourMap::computeStatistics()
{
computeStatisticsForMaps( { this } );
if ( auto ensemble = firstAncestorOrThisOfType<RimReservoirGridEnsemble>() )
{
ensemble->reloadMetaDataIfNeeded();
}
}
//--------------------------------------------------------------------------------------------------
@@ -682,7 +687,7 @@ void RimStatisticsContourMap::computeStatisticsForMaps( const std::vector<RimSta
// The bounding box is empty unless the primary case is open with active cell info
cvf::BoundingBox gridBoundingBox = primaryCase->activeCellsBoundingBox();
gridBoundingBox.expandPercent( map->m_boundingBoxExpPercent() );
gridBoundingBox.expandPercent( map->m_boundingBoxExpPercent(), map->m_boundingBoxExpPercent() );
double sampleSpacing = 1.0;
if ( auto mainGrid = primaryCase->mainGrid() ) sampleSpacing = map->sampleSpacingFactor() * mainGrid->characteristicIJCellSize();
@@ -722,12 +727,7 @@ void RimStatisticsContourMap::computeStatisticsForMaps( const std::vector<RimSta
RiaLogging::info( std::format( "Computing statistics for {} ensemble contour map(s)", contexts.size() ) );
// All maps belong to the same ensemble, so the realization cases are shared
auto cases = contexts.front().map->ensembleCases();
auto casesInViews = contexts.front().map->ensembleCasesInViews();
std::set<RimEclipseCase*> primaryCases;
for ( const auto& ctx : contexts )
primaryCases.insert( ctx.map->eclipseCase() );
auto cases = contexts.front().map->ensembleCases();
const size_t nCases = cases.size();
caf::ProgressInfo progInfo( nCases, QString( "Reading Eclipse Ensemble" ) );
@@ -739,6 +739,8 @@ void RimStatisticsContourMap::computeStatisticsForMaps( const std::vector<RimSta
{
auto task = progInfo.task( QString( "Processing Case %1 of %2" ).arg( i++ ).arg( nCases ) );
bool closeCase = !eCase->isReservoirCaseOpen();
RifReaderSettings oldSettings = eCase->readerSettings();
eCase->setReaderSettings( readerSettings );
@@ -796,7 +798,7 @@ void RimStatisticsContourMap::computeStatisticsForMaps( const std::vector<RimSta
// Release the grid data for cases that were opened only to compute statistics. A case is kept open if it has
// its own views, if it is the primary case of one of the contour maps, or if it is displayed in one of the
// ensemble views.
if ( eCase->views().empty() && !primaryCases.contains( eCase ) && !casesInViews.contains( eCase ) )
if ( closeCase )
{
eCase->closeReservoirCase();
}
@@ -981,6 +983,12 @@ void RimStatisticsContourMap::ensureResultsComputed()
}
computeStatisticsForMaps( maps );
// contour map calculations on shared grids clears the ensemble meta data, reload it
if ( auto ensemble = firstAncestorOrThisOfType<RimReservoirGridEnsemble>() )
{
ensemble->reloadMetaDataIfNeeded();
}
}
//--------------------------------------------------------------------------------------------------
@@ -1119,6 +1119,15 @@ bool RimEclipseCase::ensureReservoirCaseIsOpen()
return openReservoirCase();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimEclipseCase::isReservoirCaseOpen() const
{
// for most grid case types, this is a sufficient test
return eclipseCaseData() != nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -1468,3 +1477,11 @@ RimFaultDistanceCollection* RimEclipseCase::faultDistanceCollection() const
{
return m_dataAnalyticsCollection ? m_dataAnalyticsCollection->faultDistanceCollection() : nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimReservoirGridEnsemble* RimEclipseCase::ensemble() const
{
return firstAncestorOrThisOfType<RimReservoirGridEnsemble>();
}
@@ -60,6 +60,7 @@ class RimReservoirCellResultsStorage;
class RimEclipseResultAddressCollection;
class RimEclipseViewCollection;
class RimResultNameAlias;
class RimReservoirGridEnsemble;
namespace caf
{
@@ -87,6 +88,7 @@ public:
bool ensureReservoirCaseIsOpen();
bool openReservoirCase();
virtual bool isReservoirCaseOpen() const;
virtual void closeReservoirCase();
virtual bool openEclipseGridFile() = 0;
virtual void reloadEclipseGridFile();
@@ -155,6 +157,8 @@ public:
void updateFormationNamesData() override;
RimReservoirGridEnsemble* ensemble() const;
protected:
void initAfterRead() override;
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
@@ -415,6 +415,14 @@ void RimEclipseResultCase::closeReservoirCase()
m_activeCellInfoIsReadFromFile = false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimEclipseResultCase::isReservoirCaseOpen() const
{
return RimEclipseCase::isReservoirCaseOpen() && m_gridAndWellDataIsReadFromFile && m_activeCellInfoIsReadFromFile;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -58,6 +58,8 @@ public:
bool hasSourSimFile();
bool openEclipseGridFile() override;
bool isReservoirCaseOpen() const override;
void closeReservoirCase() override;
bool importGridAndResultMetaData( bool showTimeStepFilter );
@@ -141,6 +141,8 @@ RimEclipseStatisticsCase::RimEclipseStatisticsCase()
m_activeFormationNames.uiCapability()->setUiHidden( true );
m_displayNameOption = RimCaseDisplayNameTools::DisplayName::CUSTOM;
setDeletable( true );
}
//--------------------------------------------------------------------------------------------------
@@ -405,10 +405,9 @@ void RimGridView::onCreatePartCollectionFromSelection( cvf::Collection<cvf::Part
{
RiuEclipseSelectionItem* eclipseSelItem = static_cast<RiuEclipseSelectionItem*>( items[i] );
if ( eclipseSelItem && eclipseSelItem->m_view == this && eclipseSelItem->m_resultDefinition->eclipseCase() )
if ( eclipseSelItem && eclipseSelItem->m_view == this && eclipseSelItem->m_resultDefinition->eclipseCase() &&
eclipseSelItem->m_resultDefinition->eclipseCase()->eclipseCaseData() )
{
CAF_ASSERT( eclipseSelItem->m_resultDefinition->eclipseCase()->eclipseCaseData() );
RivSingleCellPartGenerator partGen( eclipseSelItem->m_resultDefinition->eclipseCase()->eclipseCaseData(),
eclipseSelItem->m_gridIndex,
eclipseSelItem->m_gridLocalCellIndex,
@@ -108,6 +108,9 @@ RimReservoirGridEnsemble::RimReservoirGridEnsemble()
m_statisticsCaseCollection->uiCapability()->setUiName( "Derived Statistics" );
m_statisticsCaseCollection->uiCapability()->setUiIconFromResourceString( ":/Histograms16x16.png" );
CAF_PDM_InitFieldNoDefault( &m_ensembleCase, "EnsembleCase", "Ensemble Grid" );
m_ensembleCase = nullptr;
CAF_PDM_InitFieldNoDefault( &m_viewCollection, "ViewCollection", "Views" );
m_viewCollection = new RimEclipseViewCollection;
m_viewCollection->setEclipseCaseProvider( [this]() { return this->cases(); } );
@@ -563,6 +566,7 @@ std::set<RimEclipseCase*> RimReservoirGridEnsemble::casesInViews() const
void RimReservoirGridEnsemble::addWellTargetMapping( RimWellTargetMapping* wellTargetMapping )
{
m_wellTargetMappings.push_back( wellTargetMapping );
wellTargetMapping->setName( QString( "Well Target Mapping #%1" ).arg( m_wellTargetMappings.size() ) );
wellTargetMapping->updateResultDefinition();
}
@@ -927,6 +931,17 @@ void RimReservoirGridEnsemble::loadGridDataFromFiles()
updateConnectedEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimReservoirGridEnsemble::reloadMetaDataIfNeeded()
{
if ( gridMode() == GridModeType::INDIVIDUAL_GRIDS ) return;
m_mainGrid = nullptr;
loadGridDataFromFiles();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -84,6 +84,7 @@ public:
// Deferred loading control
void loadGridDataFromFiles();
bool isGridDataLoaded() const;
void reloadMetaDataIfNeeded();
// Helper methods
bool hasSharedGrid() const;
@@ -157,6 +158,7 @@ private:
// Cases
caf::PdmChildField<RimCaseCollection*> m_caseCollection;
caf::PdmChildField<RimCaseCollection*> m_statisticsCaseCollection;
caf::PdmChildField<RimEclipseCase*> m_ensembleCase;
// Grid mode
caf::PdmField<bool> m_autoDetectGridType;
@@ -41,12 +41,14 @@
#include "RimEclipsePropertyFilterCollection.h"
#include "RimEclipseResultDefinition.h"
#include "RimEclipseView.h"
#include "RimEclipseViewCollection.h"
#include "RimRegularGridCase.h"
#include "RimReservoirGridEnsemble.h"
#include "RimTools.h"
#include "cafPdmUiDoubleSliderEditor.h"
#include "cafPdmUiSliderTools.h"
#include "cafPdmUiTreeOrdering.h"
#include <cmath>
#include <limits>
@@ -93,6 +95,8 @@ RimWellTargetMapping::RimWellTargetMapping()
{
CAF_PDM_InitObject( "Well Target Mapping", ":/WellTargets.png" );
setName( "Well Target Mapping" );
CAF_PDM_InitField( &m_timeStep, "TimeStep", 0, "Time Step" );
CAF_PDM_InitFieldNoDefault( &m_volumeType, "VolumeType", "Volume" );
@@ -141,6 +145,21 @@ RimWellTargetMapping::RimWellTargetMapping()
CAF_PDM_InitField( &m_cellCountJ, "CellCountJ", 100, "Cell Count J" );
CAF_PDM_InitField( &m_cellCountK, "CellCountK", 10, "Cell Count K" );
CAF_PDM_InitField( &m_expandBoundingBoxXYPercent,
"ExpandBoundingBoxXYPercent",
5.0,
"Expand Bounding Box XY [%]",
"",
"How much to increase the bounding box of the first case to cover for any grid size differences across the "
"ensemble." );
CAF_PDM_InitField( &m_expandBoundingBoxZPercent,
"ExpandBoundingBoxZPercent",
10.0,
"Expand Bounding Box Z [%]",
"",
"How much to increase the bounding box of the first case to cover for any grid size differences across the "
"ensemble." );
CAF_PDM_InitFieldNoDefault( &m_filterView, "FilterView", "Filter By View" );
CAF_PDM_InitFieldNoDefault( &m_ensembleStatisticsCase, "EnsembleStatisticsCase", "Ensemble Statistics Case" );
@@ -180,7 +199,10 @@ RimWellTargetMapping::~RimWellTargetMapping()
//--------------------------------------------------------------------------------------------------
void RimWellTargetMapping::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
updateAllBoundaries();
if ( changedField != nameField() )
{
updateAllBoundaries();
}
}
//--------------------------------------------------------------------------------------------------
@@ -384,15 +406,16 @@ void RimWellTargetMapping::generateCandidates( RimEclipseCase* eclipseCase, bool
//--------------------------------------------------------------------------------------------------
void RimWellTargetMapping::generateEnsembleStatistics()
{
// See RicNewWellTargetMappingFeature::isCommandEnabled() for comment on future support for ensembles with varying geometry.
auto ensemble = firstAncestorOrThisOfType<RimEclipseCaseEnsemble>();
auto ensemble = firstAncestorOrThisOfType<RimReservoirGridEnsemble>();
if ( !ensemble ) return;
const cvf::Vec3st& resultGridCellCount = getResultGridCellCount();
RigWellTargetMapping::ClusteringLimits limits = getClusteringLimits();
RigFloodingSettings floodingSettings( m_oilFloodingType(), m_userDefinedFloodingOil(), m_gasFloodingType(), m_userDefinedFloodingGas() );
double expandXY = ensemble->hasSharedGrid() ? 0.0 : m_expandBoundingBoxXYPercent();
double expandZ = ensemble->hasSharedGrid() ? 0.0 : m_expandBoundingBoxZPercent();
RimRegularGridCase* regularGridCase = RigWellTargetMapping::generateEnsembleCandidates( ensemble->cases(),
m_timeStep(),
resultGridCellCount,
@@ -400,9 +423,17 @@ void RimWellTargetMapping::generateEnsembleStatistics()
m_volumesType(),
m_volumeResultType(),
floodingSettings,
limits );
limits,
expandXY,
expandZ );
regularGridCase->setCustomCaseName( "Ensemble Grid" );
regularGridCase->setDeletable( false );
if ( m_ensembleStatisticsCase() != nullptr )
{
delete m_ensembleStatisticsCase();
}
m_ensembleStatisticsCase = regularGridCase;
@@ -411,14 +442,17 @@ void RimWellTargetMapping::generateEnsembleStatistics()
eclipseView->cellResult()->setResultType( RiaDefines::ResultCatType::GENERATED );
eclipseView->cellResult()->setResultVariable( "TOTAL_PORV_SOIL_P10" );
eclipseView->loadDataAndUpdate();
updateConnectedEditors();
m_ensembleStatisticsCase->updateConnectedEditors();
ensemble->reloadMetaDataIfNeeded();
if ( RiaGuiApplication::isRunning() || RiuMainWindow::instance() )
{
RiuMainWindow::instance()->selectAsCurrentItem( eclipseView->cellResult() );
}
eclipseView->loadDataAndUpdate();
m_ensembleStatisticsCase->updateConnectedEditors();
}
//--------------------------------------------------------------------------------------------------
@@ -426,6 +460,9 @@ void RimWellTargetMapping::generateEnsembleStatistics()
//--------------------------------------------------------------------------------------------------
void RimWellTargetMapping::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( nameField() );
uiOrdering.addNewButton( "Compute", [this]() { onGenerateButtonClicked(); } );
caf::PdmUiGroup* resultGroup = uiOrdering.addNewGroup( "Result" );
resultGroup->add( &m_timeStep );
resultGroup->add( &m_volumeType );
@@ -458,8 +495,9 @@ void RimWellTargetMapping::defineUiOrdering( QString uiConfigName, caf::PdmUiOrd
resultGroup->add( &m_volumesType );
auto hasEnsembleParent = firstAncestorOrThisOfType<RimEclipseCaseEnsemble>() != nullptr ||
firstAncestorOrThisOfType<RimReservoirGridEnsemble>() != nullptr;
auto gridEnsemble = firstAncestorOrThisOfType<RimReservoirGridEnsemble>();
auto hasEnsembleParent = firstAncestorOrThisOfType<RimEclipseCaseEnsemble>() != nullptr || gridEnsemble != nullptr;
if ( !hasEnsembleParent ) uiOrdering.add( &m_filterView );
caf::PdmUiGroup* minimumCellValuesGroup = uiOrdering.addNewGroup( "Minimum Cell Values" );
@@ -476,14 +514,18 @@ void RimWellTargetMapping::defineUiOrdering( QString uiConfigName, caf::PdmUiOrd
ensembleGridGroup->add( &m_cellCountI );
ensembleGridGroup->add( &m_cellCountJ );
ensembleGridGroup->add( &m_cellCountK );
if ( gridEnsemble && !gridEnsemble->hasSharedGrid() )
{
ensembleGridGroup->add( &m_expandBoundingBoxXYPercent );
ensembleGridGroup->add( &m_expandBoundingBoxZPercent );
}
}
caf::PdmUiGroup* advancedGroup = uiOrdering.addNewGroup( "Advanced" );
advancedGroup->add( &m_maxNumTargets );
advancedGroup->setCollapsedByDefault();
uiOrdering.addNewButton( "Generate", [this]() { onGenerateButtonClicked(); } );
uiOrdering.skipRemainingFields();
if ( m_minimumPressure == cvf::UNDEFINED_DOUBLE || m_maximumPressure == cvf::UNDEFINED_DOUBLE )
@@ -492,6 +534,19 @@ void RimWellTargetMapping::defineUiOrdering( QString uiConfigName, caf::PdmUiOrd
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellTargetMapping::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName )
{
if ( ensembleStatisticsCase() )
{
uiTreeOrdering.add( &m_ensembleStatisticsCase );
}
uiTreeOrdering.skipRemainingChildren();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -563,12 +618,15 @@ RimEclipseCase* RimWellTargetMapping::firstCase() const
//--------------------------------------------------------------------------------------------------
void RimWellTargetMapping::initAfterRead()
{
if ( RimEclipseCase* eclipseCase = firstCase() )
if ( firstAncestorOrThisOfType<RimReservoirGridEnsemble>() )
{
return; // no need to to do anything for ensembles, as the data have been cached
}
else if ( RimEclipseCase* eclipseCase = firstCase() )
{
// Automatically generate results on project load
m_resultDefinition->setEclipseCase( eclipseCase );
// Automatically generate results on project load
// Consider to also do this for ensemble cases, but this will be more expensive
bool setTimeStepInView = false;
generateCandidates( eclipseCase, setTimeStepInView );
}
@@ -641,20 +699,10 @@ void RimWellTargetMapping::resetMinimumCellValuesToDefault()
//--------------------------------------------------------------------------------------------------
void RimWellTargetMapping::onGenerateButtonClicked()
{
auto hasEclipseCaseEnsembleParent = firstAncestorOrThisOfType<RimEclipseCaseEnsemble>() != nullptr;
auto hasGridEnsembleParent = firstAncestorOrThisOfType<RimReservoirGridEnsemble>() != nullptr;
if ( hasEclipseCaseEnsembleParent )
if ( firstAncestorOrThisOfType<RimReservoirGridEnsemble>() != nullptr )
{
generateEnsembleStatistics();
}
else if ( hasGridEnsembleParent )
{
// For RimReservoirGridEnsemble implement two variants, shared grid and individual grids. Individual grids variant will require more
// work, and is not currently prioritized, so for now just show a warning if user tries to generate candidates for a
// RimReservoirGridEnsemble
//
// See RicNewWellTargetMappingFeature::isCommandEnabled() for comment on future support for ensembles with varying geometry.
}
else if ( auto eclipseCase = firstCase() )
{
generateCandidates( eclipseCase );
@@ -18,6 +18,8 @@
#pragma once
#include "RimNamedObject.h"
#include "cafAppEnum.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
@@ -34,7 +36,7 @@ class RimEclipseView;
///
///
//==================================================================================================
class RimWellTargetMapping : public caf::PdmObject
class RimWellTargetMapping : public RimNamedObject
{
CAF_PDM_HEADER_INIT;
@@ -53,9 +55,11 @@ public:
protected:
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) override;
void initAfterRead() override;
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
void initAfterRead() override;
private:
void generateCandidates( RimEclipseCase* eclipseCase, bool setTimeStepInView = true );
@@ -90,6 +94,9 @@ private:
caf::PdmField<double> m_permeability;
caf::PdmField<double> m_transmissibility;
caf::PdmField<double> m_expandBoundingBoxXYPercent;
caf::PdmField<double> m_expandBoundingBoxZPercent;
caf::PdmField<int> m_maxIterations;
caf::PdmField<int> m_maxNumTargets;