diff --git a/ApplicationLibCode/Application/RiaApplication.cpp b/ApplicationLibCode/Application/RiaApplication.cpp index 41a7f4c6934..c3abf9f8791 100644 --- a/ApplicationLibCode/Application/RiaApplication.cpp +++ b/ApplicationLibCode/Application/RiaApplication.cpp @@ -86,6 +86,7 @@ #include "RimOsduWellPathDataLoader.h" #include "RimPlotWindow.h" #include "RimProject.h" +#include "RimReservoirGridEnsemble.h" #include "RimScriptCollection.h" #include "RimSeismicData.h" #include "RimSeismicDataCollection.h" @@ -826,6 +827,22 @@ bool RiaApplication::loadProject( const QString& projectFileName, ProjectLoadAct } } + // Load all reservoir grid ensemble views + { + auto reservoirGridEnsembles = m_project->activeOilField()->analysisModels()->reservoirGridEnsembles.childrenByType(); + + for ( auto gridEnsemble : reservoirGridEnsembles ) + { + gridEnsemble->loadDataAndUpdate(); + + auto views = gridEnsemble->allViews(); + for ( auto view : views ) + { + view->loadDataAndUpdate(); + } + } + } + if ( m_project->viewLinkerCollection() && m_project->viewLinkerCollection()->viewLinker() ) { m_project->viewLinkerCollection()->viewLinker()->updateOverrides(); diff --git a/ApplicationLibCode/CommandFileInterface/RicfComputeCaseGroupStatistics.cpp b/ApplicationLibCode/CommandFileInterface/RicfComputeCaseGroupStatistics.cpp index fd00b136c8d..96673b58aa6 100644 --- a/ApplicationLibCode/CommandFileInterface/RicfComputeCaseGroupStatistics.cpp +++ b/ApplicationLibCode/CommandFileInterface/RicfComputeCaseGroupStatistics.cpp @@ -55,7 +55,7 @@ caf::PdmScriptResponse RicfComputeCaseGroupStatistics::execute() { for ( RimIdenticalGridCaseGroup* group : RimProject::current()->activeOilField()->analysisModels()->caseGroups ) { - for ( RimEclipseCase* c : group->statisticsCaseCollection->reservoirs ) + for ( RimEclipseCase* c : group->statisticsCaseCollection()->reservoirs ) { caseIds.push_back( c->caseId() ); } @@ -67,7 +67,7 @@ caf::PdmScriptResponse RicfComputeCaseGroupStatistics::execute() bool foundCase = false; for ( RimIdenticalGridCaseGroup* group : RimProject::current()->activeOilField()->analysisModels()->caseGroups ) { - for ( RimEclipseCase* c : group->statisticsCaseCollection->reservoirs ) + for ( RimEclipseCase* c : group->statisticsCaseCollection()->reservoirs ) { if ( c->caseId() == caseId ) { diff --git a/ApplicationLibCode/Commands/EclipseCommands/RicComputeStatisticsFeature.cpp b/ApplicationLibCode/Commands/EclipseCommands/RicComputeStatisticsFeature.cpp index f7184ebd9a1..bd720cdd0d2 100644 --- a/ApplicationLibCode/Commands/EclipseCommands/RicComputeStatisticsFeature.cpp +++ b/ApplicationLibCode/Commands/EclipseCommands/RicComputeStatisticsFeature.cpp @@ -23,7 +23,7 @@ #include "RimEclipseCase.h" #include "RimEclipseStatisticsCase.h" #include "RimEclipseStatisticsCaseCollection.h" -#include "RimIdenticalGridCaseGroup.h" +#include "RimReservoirGridEnsembleBase.h" #include "cafCmdFeatureManager.h" #include "cafSelectionManager.h" @@ -43,10 +43,8 @@ bool RicComputeStatisticsFeature::isCommandEnabled() const RimEclipseStatisticsCase* statisticsCase = selection[0]; if ( statisticsCase ) { - RimIdenticalGridCaseGroup* gridCaseGroup = statisticsCase->firstAncestorOrThisOfType(); - - RimCaseCollection* caseCollection = gridCaseGroup ? gridCaseGroup->caseCollection() : nullptr; - return caseCollection ? !caseCollection->reservoirs.empty() : false; + auto* ensembleBase = statisticsCase->gridEnsembleBase(); + return ensembleBase ? !ensembleBase->sourceCases().empty() : false; } } diff --git a/ApplicationLibCode/Commands/EclipseCommands/RicNewStatisticsCaseFeature.cpp b/ApplicationLibCode/Commands/EclipseCommands/RicNewStatisticsCaseFeature.cpp index ea2a9ce12f6..51871357a2e 100644 --- a/ApplicationLibCode/Commands/EclipseCommands/RicNewStatisticsCaseFeature.cpp +++ b/ApplicationLibCode/Commands/EclipseCommands/RicNewStatisticsCaseFeature.cpp @@ -24,6 +24,7 @@ #include "RimEclipseStatisticsCaseCollection.h" #include "RimIdenticalGridCaseGroup.h" #include "RimProject.h" +#include "RimReservoirGridEnsembleBase.h" #include "Riu3DMainWindowTools.h" @@ -93,30 +94,26 @@ caf::PdmUiItem* RicNewStatisticsCaseFeature::selectedValidUIItem() //-------------------------------------------------------------------------------------------------- RimEclipseStatisticsCase* RicNewStatisticsCaseFeature::addStatisticalCalculation( caf::PdmUiItem* uiItem ) { - RimIdenticalGridCaseGroup* caseGroup = nullptr; + RimCaseCollection* caseCollection = nullptr; - if ( dynamic_cast( uiItem ) ) + if ( auto* statCase = dynamic_cast( uiItem ) ) { - RimEclipseStatisticsCase* currentObject = dynamic_cast( uiItem ); - caseGroup = currentObject->parentStatisticsCaseCollection()->parentCaseGroup(); + caseCollection = statCase->parentStatisticsCaseCollection(); } - else if ( dynamic_cast( uiItem ) ) + else if ( auto* caseColl = dynamic_cast( uiItem ) ) { - RimCaseCollection* statColl = dynamic_cast( uiItem ); - caseGroup = statColl->parentCaseGroup(); + caseCollection = caseColl; } - if ( caseGroup ) - { - RimProject* proj = RimProject::current(); - RimEclipseStatisticsCase* createdObject = caseGroup->createAndAppendStatisticsCase(); - proj->assignCaseIdToCase( createdObject ); + if ( !caseCollection ) return nullptr; - caseGroup->updateConnectedEditors(); - return createdObject; - } - else - { - return nullptr; - } + auto* ensembleBase = caseCollection->parentGridEnsembleBase(); + if ( !ensembleBase ) return nullptr; + + auto* createdObject = ensembleBase->createAndAppendStatisticsCase(); + RimProject::current()->assignCaseIdToCase( createdObject ); + + caseCollection->parentField()->ownerObject()->uiCapability()->updateConnectedEditors(); + + return createdObject; } diff --git a/ApplicationLibCode/Commands/EnsembleFileSetCommands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/EnsembleFileSetCommands/CMakeLists_files.cmake index 3fc0f145dcc..658ff1e3e65 100644 --- a/ApplicationLibCode/Commands/EnsembleFileSetCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/EnsembleFileSetCommands/CMakeLists_files.cmake @@ -1,11 +1,13 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RicImportEnsembleFileSetFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicCreateEnsembleFromFileSetFeature.h + ${CMAKE_CURRENT_LIST_DIR}/RicCreateReservoirGridEnsembleFromFileSetFeature.h ) set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicImportEnsembleFileSetFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicCreateEnsembleFromFileSetFeature.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicCreateReservoirGridEnsembleFromFileSetFeature.cpp ) list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/Commands/EnsembleFileSetCommands/RicCreateReservoirGridEnsembleFromFileSetFeature.cpp b/ApplicationLibCode/Commands/EnsembleFileSetCommands/RicCreateReservoirGridEnsembleFromFileSetFeature.cpp new file mode 100644 index 00000000000..342b532bd65 --- /dev/null +++ b/ApplicationLibCode/Commands/EnsembleFileSetCommands/RicCreateReservoirGridEnsembleFromFileSetFeature.cpp @@ -0,0 +1,127 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RicCreateReservoirGridEnsembleFromFileSetFeature.h" + +#include "RiaLogging.h" + +#include "EnsembleFileSet/RimEnsembleFileSet.h" +#include "RimEclipseCaseCollection.h" +#include "RimEclipseView.h" +#include "RimOilField.h" +#include "RimProject.h" +#include "RimReservoirGridEnsemble.h" + +#include "Riu3DMainWindowTools.h" + +#include "cafProgressInfo.h" +#include "cafSelectionManagerTools.h" + +#include +#include + +CAF_CMD_SOURCE_INIT( RicCreateReservoirGridEnsembleFromFileSetFeature, "RicCreateReservoirGridEnsembleFromFileSetFeature" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RicCreateReservoirGridEnsembleFromFileSetFeature::isCommandEnabled() const +{ + return !caf::selectedObjectsByType().empty(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicCreateReservoirGridEnsembleFromFileSetFeature::onActionTriggered( bool isChecked ) +{ + std::vector selectedFileSets = caf::selectedObjectsByType(); + + if ( selectedFileSets.empty() ) return; + + RimProject* project = RimProject::current(); + RimEclipseCaseCollection* eclipseCaseColl = project->activeOilField()->analysisModels(); + + for ( RimEnsembleFileSet* fileSet : selectedFileSets ) + { + // Get grid file paths from the file set + QStringList gridFiles = fileSet->createPaths( ".EGRID" ); + if ( gridFiles.empty() ) + { + gridFiles = fileSet->createPaths( ".GRID" ); + } + + // Filter out non-existing files + QStringList existingGridFiles; + for ( const QString& filePath : gridFiles ) + { + if ( QFileInfo::exists( filePath ) ) + { + existingGridFiles.append( filePath ); + } + else + { + RiaLogging::warning( QString( "Grid file does not exist: %1" ).arg( filePath ) ); + } + } + gridFiles = existingGridFiles; + + if ( gridFiles.empty() ) + { + RiaLogging::warning( QString( "No existing grid files found for ensemble '%1'" ).arg( fileSet->name() ) ); + continue; + } + + // Always create RimReservoirGridEnsemble + RiaLogging::info( + QString( "Creating Reservoir Grid Ensemble for '%1' with %2 grid files." ).arg( fileSet->name() ).arg( gridFiles.size() ) ); + + RimReservoirGridEnsemble* gridEnsemble = new RimReservoirGridEnsemble(); + gridEnsemble->setEnsembleFileSet( fileSet ); + gridEnsemble->createGridCasesFromEnsembleFileSet(); + + project->assignIdToCaseGroup( gridEnsemble ); + eclipseCaseColl->reservoirGridEnsembles.push_back( gridEnsemble ); + + gridEnsemble->loadDataAndUpdate(); + + // Create view for first case if available + auto allCases = gridEnsemble->cases(); + if ( !allCases.empty() ) + { + RimEclipseView* view = gridEnsemble->addViewForCase( allCases[0] ); + if ( view ) + { + view->loadDataAndUpdate(); + } + } + + Riu3DMainWindowTools::selectAsCurrentItem( gridEnsemble ); + } + + eclipseCaseColl->updateAllRequiredEditors(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicCreateReservoirGridEnsembleFromFileSetFeature::setupActionLook( QAction* actionToSetup ) +{ + actionToSetup->setIcon( QIcon( ":/GridCaseGroup16x16.png" ) ); + actionToSetup->setText( "Create Reservoir Grid Ensemble" ); +} diff --git a/ApplicationLibCode/Commands/EnsembleFileSetCommands/RicCreateReservoirGridEnsembleFromFileSetFeature.h b/ApplicationLibCode/Commands/EnsembleFileSetCommands/RicCreateReservoirGridEnsembleFromFileSetFeature.h new file mode 100644 index 00000000000..6ce05c6744c --- /dev/null +++ b/ApplicationLibCode/Commands/EnsembleFileSetCommands/RicCreateReservoirGridEnsembleFromFileSetFeature.h @@ -0,0 +1,34 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafCmdFeature.h" + +//================================================================================================== +/// +//================================================================================================== +class RicCreateReservoirGridEnsembleFromFileSetFeature : public caf::CmdFeature +{ + CAF_CMD_HEADER_INIT; + +protected: + bool isCommandEnabled() const override; + void onActionTriggered( bool isChecked ) override; + void setupActionLook( QAction* actionToSetup ) override; +}; diff --git a/ApplicationLibCode/Commands/RicCloseCaseFeature.cpp b/ApplicationLibCode/Commands/RicCloseCaseFeature.cpp index fff727cd588..ca66c91e903 100644 --- a/ApplicationLibCode/Commands/RicCloseCaseFeature.cpp +++ b/ApplicationLibCode/Commands/RicCloseCaseFeature.cpp @@ -31,6 +31,7 @@ #include "RimMainPlotCollection.h" #include "RimOilField.h" #include "RimProject.h" +#include "RimReservoirGridEnsemble.h" #include "RimSummaryCaseMainCollection.h" #include "RimWellLogPlotCollection.h" @@ -147,10 +148,16 @@ void RicCloseCaseFeature::deleteEclipseCase( RimEclipseCase* eclipseCase ) if ( RimIdenticalGridCaseGroup::isStatisticsCaseCollection( caseCollection ) ) { RimIdenticalGridCaseGroup* caseGroup = caseCollection->parentCaseGroup(); - CVF_ASSERT( caseGroup ); - - caseGroup->statisticsCaseCollection()->reservoirs.removeChild( eclipseCase ); - caseGroup->updateConnectedEditors(); + if ( caseGroup ) + { + caseGroup->statisticsCaseCollection()->reservoirs.removeChild( eclipseCase ); + caseGroup->updateConnectedEditors(); + } + else if ( RimReservoirGridEnsemble* ensemble = caseCollection->parentGridEnsemble() ) + { + caseCollection->reservoirs.removeChild( eclipseCase ); + ensemble->updateConnectedEditors(); + } } else { diff --git a/ApplicationLibCode/FileInterface/RifReaderOpmCommon.cpp b/ApplicationLibCode/FileInterface/RifReaderOpmCommon.cpp index 42203d3a000..3f41b8e660e 100644 --- a/ApplicationLibCode/FileInterface/RifReaderOpmCommon.cpp +++ b/ApplicationLibCode/FileInterface/RifReaderOpmCommon.cpp @@ -72,6 +72,31 @@ RifReaderOpmCommon::~RifReaderOpmCommon() { } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RifReaderOpmCommon::GridDimensions RifReaderOpmCommon::readGridDimensions( const QString& gridFileName ) +{ + GridDimensions result; + + try + { + Opm::EclIO::EGrid opmGrid( gridFileName.toStdString() ); + + const auto& dims = opmGrid.dimension(); + result.i = dims[0]; + result.j = dims[1]; + result.k = dims[2]; + result.activeCellCount = opmGrid.activeCells(); + } + catch ( std::exception& e ) + { + RiaLogging::debug( QString( "Failed to read grid dimensions from %1: %2" ).arg( gridFileName ).arg( e.what() ) ); + } + + return result; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/FileInterface/RifReaderOpmCommon.h b/ApplicationLibCode/FileInterface/RifReaderOpmCommon.h index 5d2c32aa9e1..fa2690a12a3 100644 --- a/ApplicationLibCode/FileInterface/RifReaderOpmCommon.h +++ b/ApplicationLibCode/FileInterface/RifReaderOpmCommon.h @@ -53,6 +53,14 @@ class ProgressInfo; class RifReaderOpmCommon : public RifReaderInterface { public: + struct GridDimensions + { + size_t i = 0; + size_t j = 0; + size_t k = 0; + size_t activeCellCount = 0; + }; + RifReaderOpmCommon(); ~RifReaderOpmCommon() override; @@ -64,6 +72,8 @@ class RifReaderOpmCommon : public RifReaderInterface std::vector timeStepsOnFile( QString gridFileName ); std::set availablePhases() const override; + static GridDimensions readGridDimensions( const QString& gridFileName ); + protected: virtual bool importGrid( RigMainGrid* mainGrid, RigEclipseCaseData* caseData ); diff --git a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake index 2b1a89e546d..104beab41a3 100644 --- a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake @@ -129,6 +129,8 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RimRegularGridCase.h ${CMAKE_CURRENT_LIST_DIR}/RimGeometrySelectionItem.h ${CMAKE_CURRENT_LIST_DIR}/RimCornerPointCase.h + ${CMAKE_CURRENT_LIST_DIR}/RimReservoirGridEnsemble.h + ${CMAKE_CURRENT_LIST_DIR}/RimReservoirGridEnsembleBase.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -256,6 +258,8 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimRegularGridCase.cpp ${CMAKE_CURRENT_LIST_DIR}/RimGeometrySelectionItem.cpp ${CMAKE_CURRENT_LIST_DIR}/RimCornerPointCase.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimReservoirGridEnsemble.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimReservoirGridEnsembleBase.cpp ) list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSet.cpp b/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSet.cpp index 27831f10191..b23d79f8d23 100644 --- a/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSet.cpp +++ b/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSet.cpp @@ -336,6 +336,7 @@ void RimEnsembleFileSet::fieldChangedByUi( const caf::PdmFieldHandle* changedFie void RimEnsembleFileSet::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const { menuBuilder << "RicCreateEnsembleFromFileSetFeature"; + menuBuilder << "RicCreateReservoirGridEnsembleFromFileSetFeature"; } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.cpp b/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.cpp index 184b0547a8b..4da25c239f1 100644 --- a/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.cpp +++ b/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.cpp @@ -25,7 +25,10 @@ #include "EnsembleFileSet/RimEnsembleFileSet.h" #include "EnsembleFileSet/RimEnsembleFileSetCollection.h" #include "RiaEnsembleNameTools.h" +#include "RimEclipseCaseCollection.h" +#include "RimOilField.h" #include "RimProject.h" +#include "RimReservoirGridEnsemble.h" #include "RimSummaryCaseMainCollection.h" namespace RimEnsembleFileSetTools @@ -82,6 +85,33 @@ std::vector createEnsembleFileSets( const QStringList& file return fileSets; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector createGridEnsemblesFromFileSets( const std::vector fileSets ) +{ + RimProject* project = RimProject::current(); + RimEclipseCaseCollection* eclipseCaseColl = project->activeOilField()->analysisModels(); + if ( !eclipseCaseColl ) return {}; + + std::vector ensembles; + for ( auto fileSet : fileSets ) + { + auto ensemble = new RimReservoirGridEnsemble(); + ensemble->setEnsembleFileSet( fileSet ); + + project->assignIdToCaseGroup( ensemble ); + eclipseCaseColl->reservoirGridEnsembles.push_back( ensemble ); + + ensemble->loadDataAndUpdate(); + ensembles.push_back( ensemble ); + } + + eclipseCaseColl->updateAllRequiredEditors(); + + return ensembles; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.h b/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.h index 5424d6954ab..35434c8b5e6 100644 --- a/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.h +++ b/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSetTools.h @@ -28,12 +28,15 @@ class RimSummaryEnsemble; class RimEnsembleFileSet; +class RimReservoirGridEnsemble; namespace RimEnsembleFileSetTools { std::vector createSummaryEnsemblesFromFileSets( const std::vector fileSets ); std::vector createEnsembleFileSets( const QStringList& fileNames, RiaDefines::EnsembleGroupingMode groupingMode ); +std::vector createGridEnsemblesFromFileSets( const std::vector fileSets ); + RimEnsembleFileSet* createEnsembleFileSetFromOpm( const QString& pathPattern, const QString& name ); QList ensembleFileSetOptions(); diff --git a/ApplicationLibCode/ProjectDataModel/RimCaseCollection.cpp b/ApplicationLibCode/ProjectDataModel/RimCaseCollection.cpp index cd62b2ca46b..e3f532abb30 100644 --- a/ApplicationLibCode/ProjectDataModel/RimCaseCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimCaseCollection.cpp @@ -22,6 +22,7 @@ #include "RimEclipseCase.h" #include "RimIdenticalGridCaseGroup.h" +#include "RimReservoirGridEnsemble.h" CAF_PDM_SOURCE_INIT( RimCaseCollection, "RimCaseCollection" ); @@ -61,15 +62,21 @@ RimIdenticalGridCaseGroup* RimCaseCollection::parentCaseGroup() //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -RimEclipseCase* RimCaseCollection::findByDescription( const QString& caseDescription ) const +RimReservoirGridEnsemble* RimCaseCollection::parentGridEnsemble() { - for ( size_t i = 0; i < reservoirs.size(); i++ ) + return dynamic_cast( parentField()->ownerObject() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimReservoirGridEnsembleBase* RimCaseCollection::parentGridEnsembleBase() +{ + auto* owner = parentField()->ownerObject(); + + if ( auto* caseGroup = dynamic_cast( owner ) ) { - if ( caseDescription == reservoirs[i]->caseUserDescription() ) - { - return reservoirs[i]; - } + return caseGroup; } - - return nullptr; + return dynamic_cast( owner ); } diff --git a/ApplicationLibCode/ProjectDataModel/RimCaseCollection.h b/ApplicationLibCode/ProjectDataModel/RimCaseCollection.h index 78066f90344..31d821fd52b 100644 --- a/ApplicationLibCode/ProjectDataModel/RimCaseCollection.h +++ b/ApplicationLibCode/ProjectDataModel/RimCaseCollection.h @@ -23,7 +23,9 @@ #include "cafPdmObject.h" class RimEclipseCase; +class RimReservoirGridEnsembleBase; class RimIdenticalGridCaseGroup; +class RimReservoirGridEnsemble; class RimCase; //================================================================================================== @@ -42,6 +44,7 @@ class RimCaseCollection : public caf::PdmObject caf::PdmChildArrayField reservoirs; - RimIdenticalGridCaseGroup* parentCaseGroup(); - RimEclipseCase* findByDescription( const QString& caseDescription ) const; + RimIdenticalGridCaseGroup* parentCaseGroup(); + RimReservoirGridEnsemble* parentGridEnsemble(); + RimReservoirGridEnsembleBase* parentGridEnsembleBase(); }; diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseCollection.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseCollection.cpp index 1f474e82c70..b7fcde1c87c 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseCollection.cpp @@ -32,6 +32,7 @@ #include "RimEclipseStatisticsCase.h" #include "RimIdenticalGridCaseGroup.h" #include "RimProject.h" +#include "RimReservoirGridEnsemble.h" #include "cafCmdFeatureMenuBuilder.h" @@ -49,6 +50,8 @@ RimEclipseCaseCollection::RimEclipseCaseCollection() CAF_PDM_InitFieldNoDefault( &caseEnsembles, "CaseEnsembles", "" ); + CAF_PDM_InitFieldNoDefault( &reservoirGridEnsembles, "ReservoirGridEnsembles", "" ); + m_gridCollection = new RigGridManager; } @@ -70,6 +73,7 @@ void RimEclipseCaseCollection::close() cases.deleteChildren(); caseGroups.deleteChildren(); caseEnsembles.deleteChildren(); + reservoirGridEnsembles.deleteChildren(); } //-------------------------------------------------------------------------------------------------- @@ -174,7 +178,7 @@ void RimEclipseCaseCollection::recomputeStatisticsForAllCaseGroups() for ( size_t caseGrpIdx = 0; caseGrpIdx < numCaseGroups; ++caseGrpIdx ) { RimIdenticalGridCaseGroup* caseGroup = caseGroups[caseGrpIdx]; - RimCaseCollection* statisticsCaseCollection = caseGroup->statisticsCaseCollection; + RimCaseCollection* statisticsCaseCollection = caseGroup->statisticsCaseCollection(); const size_t numStatisticsCases = statisticsCaseCollection->reservoirs.size(); for ( size_t caseIdx = 0; caseIdx < numStatisticsCases; caseIdx++ ) { diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseCollection.h b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseCollection.h index 2773f09b374..077d32b414e 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseCollection.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseCollection.h @@ -34,6 +34,7 @@ class RimEclipseCase; class RimIdenticalGridCaseGroup; class RimWellPathCollection; class RimEclipseCaseEnsemble; +class RimReservoirGridEnsemble; //================================================================================================== /// @@ -50,6 +51,7 @@ class RimEclipseCaseCollection : public caf::PdmObject caf::PdmChildArrayField cases; caf::PdmChildArrayField caseGroups; caf::PdmChildArrayField caseEnsembles; + caf::PdmChildArrayField reservoirGridEnsembles; void close(); diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.cpp index 25e54b5f867..5179ae1a2f3 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.cpp @@ -105,16 +105,6 @@ bool RimEclipseCaseEnsemble::contains( RimEclipseCase* reservoir ) const return false; } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -RimEclipseCase* RimEclipseCaseEnsemble::findByDescription( const QString& caseDescription ) const -{ - if ( !m_caseCollection ) return nullptr; - - return m_caseCollection->findByDescription( caseDescription ); -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.h b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.h index 8205f003389..4d041c95003 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseCaseEnsemble.h @@ -51,7 +51,6 @@ class RimEclipseCaseEnsemble : public RimNamedObject void removeCase( RimEclipseCase* reservoir ); bool contains( RimEclipseCase* reservoir ) const; - RimEclipseCase* findByDescription( const QString& description ) const; RimEclipseCase* findByFileName( const QString& gridFileName ) const; std::vector cases() const; diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCase.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCase.cpp index 615500bf699..cfa9cdacc40 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCase.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCase.cpp @@ -36,10 +36,10 @@ #include "RimEclipseStatisticsCaseEvaluator.h" #include "RimEclipseView.h" #include "RimGridCalculationCollection.h" -#include "RimIdenticalGridCaseGroup.h" #include "RimIntersectionCollection.h" #include "RimProject.h" #include "RimReservoirCellResultsStorage.h" +#include "RimReservoirGridEnsembleBase.h" #include "RimSimWellInViewCollection.h" #include "RiuMainWindow.h" @@ -126,7 +126,10 @@ RimEclipseStatisticsCase::RimEclipseStatisticsCase() CAF_PDM_InitScriptableField( &m_midPercentile, "MidPercentile", 50.0, "Mid" ); CAF_PDM_InitScriptableField( &m_highPercentile, "HighPercentile", 90.0, "High" ); - CAF_PDM_InitScriptableField( &m_wellDataSourceCase, "WellDataSourceCase", RiaResultNames::undefinedResultName(), "Well Data Source Case" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_wellDataSourceCase, "WellDataSourceCasePtr", "Well Data Source Case" ); + + CAF_PDM_InitFieldNoDefault( &obsoleteField_wellDataSourceCase, "WellDataSourceCase", "Well Data Source Case" ); + obsoleteField_wellDataSourceCase.xmlCapability()->setIOWritable( false ); CAF_PDM_InitScriptableField( &m_useZeroAsInactiveCellValue, "UseZeroAsInactiveCellValue", false, "Use Zero as Inactive Cell Value" ); @@ -147,6 +150,31 @@ RimEclipseStatisticsCase::~RimEclipseStatisticsCase() { } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimEclipseStatisticsCase::initAfterRead() +{ + RimEclipseCase::initAfterRead(); + + if ( !obsoleteField_wellDataSourceCase().isEmpty() && obsoleteField_wellDataSourceCase() != RiaResultNames::undefinedResultName() ) + { + auto* owner = gridEnsembleBase(); + if ( owner ) + { + for ( auto* sourceCase : owner->sourceCases() ) + { + if ( sourceCase->caseUserDescription() == obsoleteField_wellDataSourceCase() ) + { + m_wellDataSourceCase = sourceCase; + break; + } + } + } + obsoleteField_wellDataSourceCase = ""; + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -165,24 +193,24 @@ bool RimEclipseStatisticsCase::openEclipseGridFile() { if ( eclipseCaseData() ) return true; - cvf::ref eclipseCase = new RigEclipseCaseData( this ); - - CVF_ASSERT( parentStatisticsCaseCollection() ); + auto* ensembleBase = gridEnsembleBase(); + if ( !ensembleBase ) return false; - RimIdenticalGridCaseGroup* gridCaseGroup = parentStatisticsCaseCollection()->parentCaseGroup(); - CVF_ASSERT( gridCaseGroup ); - - RigMainGrid* mainGrid = gridCaseGroup->mainGrid(); + RigMainGrid* mainGrid = ensembleBase->mainGrid(); + if ( !mainGrid ) return false; + cvf::ref eclipseCase = new RigEclipseCaseData( this ); eclipseCase->setMainGrid( mainGrid ); eclipseCase->setActiveCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL, - gridCaseGroup->unionOfActiveCells( RiaDefines::PorosityModelType::MATRIX_MODEL ) ); + ensembleBase->unionOfActiveCells( RiaDefines::PorosityModelType::MATRIX_MODEL ) ); eclipseCase->setActiveCellInfo( RiaDefines::PorosityModelType::FRACTURE_MODEL, - gridCaseGroup->unionOfActiveCells( RiaDefines::PorosityModelType::FRACTURE_MODEL ) ); + ensembleBase->unionOfActiveCells( RiaDefines::PorosityModelType::FRACTURE_MODEL ) ); setReservoirData( eclipseCase.p() ); + computeCachedData(); + loadSimulationWellDataFromSourceCase(); if ( m_populateSelectionAfterLoadingGrid ) @@ -256,10 +284,12 @@ void RimEclipseStatisticsCase::setSourceProperties( RiaDefines::ResultCatType pr //-------------------------------------------------------------------------------------------------- void RimEclipseStatisticsCase::selectAllTimeSteps() { - RimIdenticalGridCaseGroup* idgcg = caseGroup(); - if ( idgcg && idgcg->mainCase() ) + auto* ensembleBase = gridEnsembleBase(); + if ( !ensembleBase ) return; + + if ( RimEclipseCase* mainCase = ensembleBase->mainCase() ) { - int timeStepCount = idgcg->mainCase()->timeStepStrings().size(); + int timeStepCount = mainCase->timeStepStrings().size(); if ( timeStepCount > 0 ) { @@ -274,9 +304,9 @@ void RimEclipseStatisticsCase::selectAllTimeSteps() //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RimEclipseStatisticsCase::setWellDataSourceCase( const QString& reservoirDescription ) +void RimEclipseStatisticsCase::setWellDataSourceCase( RimEclipseCase* sourceCase ) { - m_wellDataSourceCase = reservoirDescription; + m_wellDataSourceCase = sourceCase; } //-------------------------------------------------------------------------------------------------- @@ -289,9 +319,10 @@ void RimEclipseStatisticsCase::computeStatistics() openEclipseGridFile(); } - RimIdenticalGridCaseGroup* gridCaseGroup = caseGroup(); - CVF_ASSERT( gridCaseGroup ); - gridCaseGroup->computeUnionOfActiveCells(); + auto* ensembleBase = gridEnsembleBase(); + if ( !ensembleBase ) return; + + ensembleBase->computeUnionOfActiveCells(); std::vector sourceCases = getSourceCases(); @@ -415,8 +446,14 @@ void RimEclipseStatisticsCase::computeStatistics() calculationName ) ); } - bool clearGridCalculationMemory = m_dataSourceForStatistics() == DataSourceType::GRID_CALCULATION; - RimEclipseStatisticsCaseEvaluator stat( sourceCases, timeStepIndices, statisticsConfig, resultCase, gridCaseGroup, clearGridCalculationMemory ); + bool clearGridCalculationMemory = m_dataSourceForStatistics() == DataSourceType::GRID_CALCULATION; + RimEclipseStatisticsCaseEvaluator stat( sourceCases, + timeStepIndices, + statisticsConfig, + resultCase, + ensembleBase->unionOfActiveCells( RiaDefines::PorosityModelType::MATRIX_MODEL ), + ensembleBase->unionOfActiveCells( RiaDefines::PorosityModelType::FRACTURE_MODEL ), + clearGridCalculationMemory ); if ( m_useZeroAsInactiveCellValue ) { @@ -441,36 +478,21 @@ void RimEclipseStatisticsCase::scheduleACTIVEGeometryRegenOnReservoirViews() //-------------------------------------------------------------------------------------------------- std::vector RimEclipseStatisticsCase::getSourceCases() const { - std::vector sourceCases; - - RimIdenticalGridCaseGroup* gridCaseGroup = caseGroup(); - if ( gridCaseGroup ) - { - size_t caseCount = gridCaseGroup->caseCollection->reservoirs.size(); - for ( size_t i = 0; i < caseCount; i++ ) - { - CVF_ASSERT( gridCaseGroup->caseCollection ); - CVF_ASSERT( gridCaseGroup->caseCollection->reservoirs[i] ); + auto* ensembleBase = gridEnsembleBase(); + if ( !ensembleBase ) return {}; - RimEclipseCase* sourceCase = gridCaseGroup->caseCollection->reservoirs[i]; - sourceCases.push_back( sourceCase ); - } - } - - return sourceCases; + return ensembleBase->sourceCases(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -RimIdenticalGridCaseGroup* RimEclipseStatisticsCase::caseGroup() const +RimReservoirGridEnsembleBase* RimEclipseStatisticsCase::gridEnsembleBase() const { - RimCaseCollection* parentCollection = parentStatisticsCaseCollection(); - if ( parentCollection ) + if ( RimCaseCollection* parentCollection = parentStatisticsCaseCollection() ) { - return parentCollection->parentCaseGroup(); + return parentCollection->parentGridEnsembleBase(); } - return nullptr; } @@ -578,8 +600,11 @@ QList RimEclipseStatisticsCase::toOptionList( const QStr //-------------------------------------------------------------------------------------------------- QList RimEclipseStatisticsCase::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) { - RimIdenticalGridCaseGroup* idgcg = caseGroup(); - if ( !( caseGroup() && caseGroup()->mainCase() && caseGroup()->mainCase()->eclipseCaseData() ) ) + auto* ensembleBase = gridEnsembleBase(); + if ( !ensembleBase ) return {}; + + RimEclipseCase* mainCase = ensembleBase->mainCase(); + if ( !( mainCase && mainCase->eclipseCaseData() ) ) { return {}; } @@ -618,13 +643,13 @@ QList RimEclipseStatisticsCase::calculateValueOptions( c return options; } - RigEclipseCaseData* caseData = idgcg->mainCase()->eclipseCaseData(); + RigEclipseCaseData* caseData = mainCase->eclipseCaseData(); if ( &m_selectedTimeSteps == fieldNeedingOptions ) { QList options; - const auto timeStepStrings = idgcg->mainCase()->timeStepStrings(); + const auto timeStepStrings = mainCase->timeStepStrings(); int index = 0; for ( const auto& text : timeStepStrings ) @@ -700,15 +725,15 @@ QList RimEclipseStatisticsCase::calculateValueOptions( c else if ( &m_wellDataSourceCase == fieldNeedingOptions ) { - QStringList sourceCaseNames; - sourceCaseNames += RiaResultNames::undefinedResultName(); + QList options; + options.push_back( caf::PdmOptionItemInfo( RiaResultNames::undefinedResultName(), nullptr ) ); - for ( size_t i = 0; i < caseGroup()->caseCollection()->reservoirs().size(); i++ ) + for ( auto* sourceCase : ensembleBase->sourceCases() ) { - sourceCaseNames += caseGroup()->caseCollection()->reservoirs()[i]->caseUserDescription(); + options.push_back( caf::PdmOptionItemInfo( sourceCase->caseUserDescription(), sourceCase ) ); } - return toOptionList( sourceCaseNames ); + return options; } return RimEclipseCase::calculateValueOptions( fieldNeedingOptions ); @@ -759,8 +784,7 @@ void RimEclipseStatisticsCase::fieldChangedByUi( const caf::PdmFieldHandle* chan //-------------------------------------------------------------------------------------------------- void RimEclipseStatisticsCase::loadSimulationWellDataFromSourceCase() { - // Find or load well data for given case - RimEclipseCase* sourceResultCase = caseGroup()->caseCollection()->findByDescription( m_wellDataSourceCase ); + RimEclipseCase* sourceResultCase = m_wellDataSourceCase(); if ( sourceResultCase ) { sourceResultCase->openEclipseGridFile(); @@ -1006,13 +1030,16 @@ void RimEclipseStatisticsCase::computeStatisticsAndUpdateViews() //-------------------------------------------------------------------------------------------------- void RimEclipseStatisticsCase::populateResultSelection() { - RimIdenticalGridCaseGroup* idgcg = caseGroup(); - if ( !( caseGroup() && caseGroup()->mainCase() && caseGroup()->mainCase()->eclipseCaseData() ) ) + auto* ensembleBase = gridEnsembleBase(); + if ( !ensembleBase ) return; + + RimEclipseCase* mainCase = ensembleBase->mainCase(); + if ( !( mainCase && mainCase->eclipseCaseData() ) ) { return; } - RigEclipseCaseData* caseData = idgcg->mainCase()->eclipseCaseData(); + RigEclipseCaseData* caseData = mainCase->eclipseCaseData(); if ( m_selectedDynamicProperties().empty() ) { diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCase.h b/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCase.h index a12325a4850..95929d2dfde 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCase.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCase.h @@ -28,10 +28,12 @@ #include "cafPdmField.h" #include "cafPdmObject.h" +class RigActiveCellInfo; class RigMainGrid; class RigSimWellData; class RimEclipseResultDefinition; class RimEclipseStatisticsCaseCollection; +class RimReservoirGridEnsembleBase; class RimIdenticalGridCaseGroup; class RimGridCalculation; @@ -80,12 +82,13 @@ class RimEclipseStatisticsCase : public RimEclipseCase void setSourceProperties( RiaDefines::ResultCatType propertyType, const std::vector& propertyNames ); void selectAllTimeSteps(); - void setWellDataSourceCase( const QString& reservoirDescription ); + void setWellDataSourceCase( RimEclipseCase* sourceCase ); + + RimReservoirGridEnsembleBase* gridEnsembleBase() const; private: void scheduleACTIVEGeometryRegenOnReservoirViews(); - RimIdenticalGridCaseGroup* caseGroup() const; std::vector getSourceCases() const; void populateResultSelection(); @@ -99,6 +102,7 @@ class RimEclipseStatisticsCase : public RimEclipseCase void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; void loadSimulationWellDataFromSourceCase(); + void initAfterRead() override; void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override; void initializeSelectedTimeSteps(); @@ -134,9 +138,12 @@ class RimEclipseStatisticsCase : public RimEclipseCase caf::PdmField m_midPercentile; caf::PdmField m_highPercentile; - caf::PdmField m_wellDataSourceCase; + caf::PdmPtrField m_wellDataSourceCase; caf::PdmField m_useZeroAsInactiveCellValue; + // Obsolete + caf::PdmField obsoleteField_wellDataSourceCase; + bool m_populateSelectionAfterLoadingGrid; }; diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.cpp index 423c601dfdd..ddc58042458 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.cpp @@ -22,6 +22,7 @@ #include "RiaLogging.h" +#include "RigActiveCellInfo.h" #include "RigCaseCellResultsData.h" #include "RigEclipseCaseData.h" #include "RigEclipseResultInfo.h" @@ -32,13 +33,10 @@ #include "RigStatisticsMath.h" #include "RimEclipseView.h" -#include "RimIdenticalGridCaseGroup.h" #include "RimReservoirCellResultsStorage.h" #include "cafProgressInfo.h" -#include - #include //-------------------------------------------------------------------------------------------------- @@ -210,6 +208,10 @@ void RimEclipseStatisticsCaseEvaluator::evaluateForResults( const QList visibility = filterView->currentTotalCellVisibility(); } + auto destinationActiveCellInfo = m_destinationCase->activeCellInfo( poroModel ); + auto unionActiveCells = ( poroModel == RiaDefines::PorosityModelType::MATRIX_MODEL ) ? m_unionOfMatrixActiveCells + : m_unionOfFractureActiveCells; + // Loop over the cells in the grid, get the case values, and calculate the cell statistics #pragma omp parallel for schedule( dynamic ) firstprivate( statParams, values ) for ( int cellIdx = 0; cellIdx < cellCount; cellIdx++ ) @@ -218,7 +220,7 @@ void RimEclipseStatisticsCaseEvaluator::evaluateForResults( const QList if ( visibility.notNull() && !visibility->val( reservoirCellIndex ) ) continue; - if ( m_destinationCase->activeCellInfo( poroModel )->isActive( reservoirCellIndex ) ) + if ( destinationActiveCellInfo->isActive( reservoirCellIndex ) ) { // Extract the cell values from each of the cases and assemble them into one vector @@ -230,7 +232,7 @@ void RimEclipseStatisticsCaseEvaluator::evaluateForResults( const QList // Replace huge_val with zero in the statistical computation for the following case if ( m_useZeroAsInactiveCellValue || resultName.toUpper() == "ACTNUM" ) { - if ( m_identicalGridCaseGroup->unionOfActiveCells( poroModel )->isActive( reservoirCellIndex ) && val == HUGE_VAL ) + if ( unionActiveCells && unionActiveCells->isActive( reservoirCellIndex ) && val == HUGE_VAL ) { val = 0.0; } @@ -404,14 +406,16 @@ RimEclipseStatisticsCaseEvaluator::RimEclipseStatisticsCaseEvaluator( const std: const std::vector& timeStepIndices, const RimStatisticsConfig& statisticsConfig, RigEclipseCaseData* destinationCase, - RimIdenticalGridCaseGroup* identicalGridCaseGroup, + RigActiveCellInfo* unionOfMatrixActiveCells, + RigActiveCellInfo* unionOfFractureActiveCells, bool clearGridCalculationMemory ) : m_sourceCases( sourceCases ) , m_statisticsConfig( statisticsConfig ) , m_destinationCase( destinationCase ) , m_reservoirCellCount( 0 ) , m_timeStepIndices( timeStepIndices ) - , m_identicalGridCaseGroup( identicalGridCaseGroup ) + , m_unionOfMatrixActiveCells( unionOfMatrixActiveCells ) + , m_unionOfFractureActiveCells( unionOfFractureActiveCells ) , m_useZeroAsInactiveCellValue( false ) , m_clearGridCalculationMemory( clearGridCalculationMemory ) { diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.h b/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.h index fd59666e537..83554dadc90 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.h @@ -26,6 +26,7 @@ #include #include +class RigActiveCellInfo; class RimEclipseCase; class RigEclipseCaseData; class RigCaseCellResultsData; @@ -57,7 +58,8 @@ class RimEclipseStatisticsCaseEvaluator const std::vector& timeStepIndices, const RimStatisticsConfig& statisticsConfig, RigEclipseCaseData* destinationCase, - RimIdenticalGridCaseGroup* identicalGridCaseGroup, + RigActiveCellInfo* unionOfMatrixActiveCells, + RigActiveCellInfo* unionOfFractureActiveCells, bool clearGridCalculationMemory ); struct ResSpec @@ -108,10 +110,11 @@ class RimEclipseStatisticsCaseEvaluator std::vector m_sourceCases; std::vector m_timeStepIndices; - size_t m_reservoirCellCount; - RimStatisticsConfig m_statisticsConfig; - RigEclipseCaseData* m_destinationCase; - RimIdenticalGridCaseGroup* m_identicalGridCaseGroup; - bool m_useZeroAsInactiveCellValue; - bool m_clearGridCalculationMemory; + size_t m_reservoirCellCount; + RimStatisticsConfig m_statisticsConfig; + RigEclipseCaseData* m_destinationCase; + RigActiveCellInfo* m_unionOfMatrixActiveCells; + RigActiveCellInfo* m_unionOfFractureActiveCells; + bool m_useZeroAsInactiveCellValue; + bool m_clearGridCalculationMemory; }; diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp index dfaaeccb058..d2b0d37a665 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseView.cpp @@ -1626,13 +1626,17 @@ void RimEclipseView::updateLegendRangesTextAndVisibility( RimRegularLegendConfig nativeOrOverrideViewer()->addColorLegendToBottomLeftCorner( legendConfig->titledOverlayFrame(), isUsingOverrideViewer() ); } - size_t maxTimeStepCount = eclResultDef->currentGridCellResults()->maxTimeStepCount(); - if ( eclResultDef->isTernarySaturationSelected() && maxTimeStepCount > 1 ) + if ( RigCaseCellResultsData* cellResultsData = eclResultDef->currentGridCellResults() ) { - if ( ternaryLegendConfig->showLegend() && ternaryLegendConfig->titledOverlayFrame() ) + size_t maxTimeStepCount = cellResultsData->maxTimeStepCount(); + if ( eclResultDef->isTernarySaturationSelected() && maxTimeStepCount > 1 ) { - ternaryLegendConfig->setTitle( legendHeading ); - nativeOrOverrideViewer()->addColorLegendToBottomLeftCorner( ternaryLegendConfig->titledOverlayFrame(), isUsingOverrideViewer() ); + if ( ternaryLegendConfig->showLegend() && ternaryLegendConfig->titledOverlayFrame() ) + { + ternaryLegendConfig->setTitle( legendHeading ); + nativeOrOverrideViewer()->addColorLegendToBottomLeftCorner( ternaryLegendConfig->titledOverlayFrame(), + isUsingOverrideViewer() ); + } } } } @@ -1657,6 +1661,14 @@ RimEclipseCase* RimEclipseView::eclipseCase() const return m_eclipseCase; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimEclipseView::setEclipseCaseProvider( std::function()> provider ) +{ + m_eclipseCaseProvider = provider; +} + //-------------------------------------------------------------------------------------------------- // /* @@ -2088,7 +2100,18 @@ QList RimEclipseView::calculateValueOptions( const caf:: { QList options; - for ( auto eclCase : RimEclipseCaseTools::allEclipseGridCases() ) + // Use callback if provided, otherwise use global case list + std::vector availableCases; + if ( m_eclipseCaseProvider ) + { + availableCases = m_eclipseCaseProvider(); + } + else + { + availableCases = RimEclipseCaseTools::allEclipseGridCases(); + } + + for ( auto eclCase : availableCases ) { options.push_back( caf::PdmOptionItemInfo( eclCase->caseUserDescription(), eclCase, false, eclCase->uiIconProvider() ) ); } diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseView.h b/ApplicationLibCode/ProjectDataModel/RimEclipseView.h index 36247b219d9..b01f747bf84 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseView.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseView.h @@ -36,6 +36,8 @@ #include "cafPdmFieldCvfColor.h" #include "cafPdmFieldCvfMat4d.h" +#include + class RigActiveCellInfo; class RigCaseCellResultsData; class RigGridBase; @@ -130,6 +132,8 @@ class RimEclipseView : public RimGridView, public RimFieldQuickAccessInterface RimEclipseCase* eclipseCase() const; RimCase* ownerCase() const override; + void setEclipseCaseProvider( std::function()> provider ); + RigMainGrid* mainGrid() const; // Display model generation @@ -269,4 +273,7 @@ class RimEclipseView : public RimGridView, public RimFieldQuickAccessInterface caf::PdmChildField m_additionalResultsForResultInfo; caf::PdmChildArrayField m_cameraPositions; + + // Callback for providing available Eclipse cases (used by view collections to filter cases) + std::function()> m_eclipseCaseProvider; }; diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseViewCollection.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseViewCollection.cpp index b9542114d9b..496446835d9 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseViewCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseViewCollection.cpp @@ -93,6 +93,9 @@ RimEclipseView* RimEclipseViewCollection::addView( RimEclipseCase* eclipseCase ) view->setEclipseCase( eclipseCase ); + // Configure case provider callback + applyCallbackToView( view ); + auto prefs = RiaPreferences::current(); view->faultCollection()->setActive( prefs->enableFaultsByDefault() ); @@ -125,6 +128,9 @@ RimEclipseView* RimEclipseViewCollection::addView( RimEclipseCase* eclipseCase ) //-------------------------------------------------------------------------------------------------- void RimEclipseViewCollection::addView( RimEclipseView* view ) { + // Configure case provider callback + applyCallbackToView( view ); + m_views.push_back( view ); updateConnectedEditors(); } @@ -137,3 +143,37 @@ void RimEclipseViewCollection::removeView( RimEclipseView* view ) m_views.removeChild( view ); updateConnectedEditors(); } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimEclipseViewCollection::setEclipseCaseProvider( std::function()> provider ) +{ + // Store the callback for future views + if ( provider ) + { + m_eclipseCaseProvider = provider; + } + else + { + m_eclipseCaseProvider = nullptr; + } + + // Apply the callback to all existing views in the collection + for ( auto view : m_views ) + { + applyCallbackToView( view ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimEclipseViewCollection::applyCallbackToView( RimEclipseView* view ) +{ + if ( m_eclipseCaseProvider ) + { + // Use the stored custom callback + view->setEclipseCaseProvider( m_eclipseCaseProvider ); + } +} diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseViewCollection.h b/ApplicationLibCode/ProjectDataModel/RimEclipseViewCollection.h index 9922c7a08b0..a162cb53eb4 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseViewCollection.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseViewCollection.h @@ -24,6 +24,8 @@ #include +#include + class RimEclipseView; class RimEclipseCase; @@ -44,9 +46,14 @@ class RimEclipseViewCollection : public caf::PdmObject std::vector views() const; + void setEclipseCaseProvider( std::function()> provider = nullptr ); + private: void onChildDeleted( caf::PdmChildArrayFieldHandle* childArray, std::vector& referringObjects ) override; + void applyCallbackToView( RimEclipseView* view ); + private: - caf::PdmChildArrayField m_views; + caf::PdmChildArrayField m_views; + std::function()> m_eclipseCaseProvider; }; diff --git a/ApplicationLibCode/ProjectDataModel/RimIdenticalGridCaseGroup.cpp b/ApplicationLibCode/ProjectDataModel/RimIdenticalGridCaseGroup.cpp index cc280d3ad58..0b6395fcb3f 100644 --- a/ApplicationLibCode/ProjectDataModel/RimIdenticalGridCaseGroup.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimIdenticalGridCaseGroup.cpp @@ -63,7 +63,7 @@ RimIdenticalGridCaseGroup::RimIdenticalGridCaseGroup() groupId.uiCapability()->setUiReadOnly( true ); groupId.capability()->setIOWriteable( false ); - CAF_PDM_InitFieldNoDefault( &statisticsCaseCollection, "StatisticsCaseCollection", "statisticsCaseCollection ChildArrayField" ); + CAF_PDM_InitFieldNoDefault( &m_statisticsCaseCollection, "StatisticsCaseCollection", "statisticsCaseCollection ChildArrayField" ); CAF_PDM_InitFieldNoDefault( &caseCollection, "CaseCollection", "Source Cases ChildArrayField" ); @@ -71,9 +71,9 @@ RimIdenticalGridCaseGroup::RimIdenticalGridCaseGroup() caseCollection->uiCapability()->setUiName( "Source Cases" ); caseCollection->uiCapability()->setUiIconFromResourceString( ":/Cases16x16.png" ); - statisticsCaseCollection = new RimCaseCollection; - statisticsCaseCollection->uiCapability()->setUiName( "Derived Statistics" ); - statisticsCaseCollection->uiCapability()->setUiIconFromResourceString( ":/Histograms16x16.png" ); + m_statisticsCaseCollection = new RimCaseCollection; + m_statisticsCaseCollection->uiCapability()->setUiName( "Derived Statistics" ); + m_statisticsCaseCollection->uiCapability()->setUiIconFromResourceString( ":/Histograms16x16.png" ); m_mainGrid = nullptr; @@ -93,8 +93,8 @@ RimIdenticalGridCaseGroup::~RimIdenticalGridCaseGroup() delete caseCollection; caseCollection = nullptr; - delete statisticsCaseCollection; - statisticsCaseCollection = nullptr; + delete m_statisticsCaseCollection; + m_statisticsCaseCollection = nullptr; } //-------------------------------------------------------------------------------------------------- @@ -344,19 +344,35 @@ void RimIdenticalGridCaseGroup::computeUnionOfActiveCells() //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -RimEclipseStatisticsCase* RimIdenticalGridCaseGroup::createAndAppendStatisticsCase() +RimEclipseStatisticsCase* RimIdenticalGridCaseGroup::createAndAppendEmptyStatisticsCase() { - bool selectDefaultResults = true; - return createStatisticsCase( selectDefaultResults ); + auto* statsColl = statisticsCaseCollection(); + if ( !statsColl ) return nullptr; + + RimEclipseStatisticsCase* newStatisticsCase = new RimEclipseStatisticsCase; + + newStatisticsCase->setCaseUserDescription( QString( "Statistics " ) + QString::number( statsColl->reservoirs.size() + 1 ) ); + statsColl->reservoirs.push_back( newStatisticsCase ); + + auto cases = sourceCases(); + if ( !cases.empty() ) + { + newStatisticsCase->setWellDataSourceCase( cases.front() ); + } + + newStatisticsCase->openEclipseGridFile(); + newStatisticsCase->computeActiveCellsBoundingBox(); + newStatisticsCase->selectAllTimeSteps(); + + return newStatisticsCase; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -RimEclipseStatisticsCase* RimIdenticalGridCaseGroup::createAndAppendEmptyStatisticsCase() +RimCaseCollection* RimIdenticalGridCaseGroup::statisticsCaseCollection() const { - bool selectDefaultResults = false; - return createStatisticsCase( selectDefaultResults ); + return m_statisticsCaseCollection; } //-------------------------------------------------------------------------------------------------- @@ -364,9 +380,9 @@ RimEclipseStatisticsCase* RimIdenticalGridCaseGroup::createAndAppendEmptyStatist //-------------------------------------------------------------------------------------------------- void RimIdenticalGridCaseGroup::updateMainGridAndActiveCellsForStatisticsCases() { - for ( size_t i = 0; i < statisticsCaseCollection->reservoirs().size(); i++ ) + for ( size_t i = 0; i < m_statisticsCaseCollection->reservoirs().size(); i++ ) { - RimEclipseCase* rimStaticsCase = statisticsCaseCollection->reservoirs[i]; + RimEclipseCase* rimStaticsCase = m_statisticsCaseCollection->reservoirs[i]; if ( rimStaticsCase->eclipseCaseData() ) { @@ -385,9 +401,9 @@ void RimIdenticalGridCaseGroup::updateMainGridAndActiveCellsForStatisticsCases() //-------------------------------------------------------------------------------------------------- void RimIdenticalGridCaseGroup::clearStatisticsResults() { - for ( size_t i = 0; i < statisticsCaseCollection->reservoirs().size(); i++ ) + for ( size_t i = 0; i < m_statisticsCaseCollection->reservoirs().size(); i++ ) { - RimEclipseCase* rimStaticsCase = statisticsCaseCollection->reservoirs[i]; + RimEclipseCase* rimStaticsCase = m_statisticsCaseCollection->reservoirs[i]; if ( !rimStaticsCase ) continue; if ( rimStaticsCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL ) ) @@ -419,32 +435,6 @@ void RimIdenticalGridCaseGroup::clearActiveCellUnions() m_unionOfFractureActiveCells->clear(); } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -RimEclipseStatisticsCase* RimIdenticalGridCaseGroup::createStatisticsCase( bool selectDefaultResults ) -{ - RimEclipseStatisticsCase* newStatisticsCase = new RimEclipseStatisticsCase; - - newStatisticsCase->setCaseUserDescription( QString( "Statistics " ) + QString::number( statisticsCaseCollection()->reservoirs.size() + 1 ) ); - statisticsCaseCollection()->reservoirs.push_back( newStatisticsCase ); - - if ( selectDefaultResults ) newStatisticsCase->populateResultSelectionAfterLoadingGrid(); - - auto reservoirs = caseCollection->reservoirs().childrenByType(); - if ( !reservoirs.empty() ) - { - auto caseDescription = reservoirs.front()->caseUserDescription(); - newStatisticsCase->setWellDataSourceCase( caseDescription ); - } - - newStatisticsCase->openEclipseGridFile(); - newStatisticsCase->computeActiveCellsBoundingBox(); - newStatisticsCase->selectAllTimeSteps(); - - return newStatisticsCase; -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -510,3 +500,11 @@ RimEclipseCase* RimIdenticalGridCaseGroup::mainCase() return nullptr; } } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimIdenticalGridCaseGroup::sourceCases() const +{ + return caseCollection->reservoirs.childrenByType(); +} diff --git a/ApplicationLibCode/ProjectDataModel/RimIdenticalGridCaseGroup.h b/ApplicationLibCode/ProjectDataModel/RimIdenticalGridCaseGroup.h index c5d463d9376..4af579fe400 100644 --- a/ApplicationLibCode/ProjectDataModel/RimIdenticalGridCaseGroup.h +++ b/ApplicationLibCode/ProjectDataModel/RimIdenticalGridCaseGroup.h @@ -21,6 +21,7 @@ #pragma once #include "RiaPorosityModel.h" +#include "RimReservoirGridEnsembleBase.h" #include "cafPdmChildField.h" #include "cafPdmField.h" @@ -40,7 +41,7 @@ class RimEclipseStatisticsCase; // // //================================================================================================== -class RimIdenticalGridCaseGroup : public caf::PdmObject +class RimIdenticalGridCaseGroup : public caf::PdmObject, public RimReservoirGridEnsembleBase { CAF_PDM_HEADER_INIT; @@ -51,23 +52,24 @@ class RimIdenticalGridCaseGroup : public caf::PdmObject caf::PdmField name; caf::PdmField groupId; caf::PdmChildField caseCollection; - caf::PdmChildField statisticsCaseCollection; void addCase( RimEclipseCase* reservoir ); void removeCase( RimEclipseCase* reservoir ); bool contains( RimEclipseCase* reservoir ) const; - RimEclipseStatisticsCase* createAndAppendStatisticsCase(); RimEclipseStatisticsCase* createAndAppendEmptyStatisticsCase(); - RimEclipseCase* mainCase(); - void loadMainCaseAndActiveCellInfo(); + RimEclipseCase* mainCase() override; + RimCaseCollection* statisticsCaseCollection() const override; + void loadMainCaseAndActiveCellInfo(); - RigMainGrid* mainGrid(); + RigMainGrid* mainGrid() override; - RigActiveCellInfo* unionOfActiveCells( RiaDefines::PorosityModelType porosityType ); - void computeUnionOfActiveCells(); + std::vector sourceCases() const override; + + RigActiveCellInfo* unionOfActiveCells( RiaDefines::PorosityModelType porosityType ) override; + void computeUnionOfActiveCells() override; static bool isStatisticsCaseCollection( RimCaseCollection* rimCaseCollection ); @@ -79,9 +81,9 @@ class RimIdenticalGridCaseGroup : public caf::PdmObject void clearStatisticsResults(); void clearActiveCellUnions(); - RimEclipseStatisticsCase* createStatisticsCase( bool selectDefaultResults ); - private: + caf::PdmChildField m_statisticsCaseCollection; + RigMainGrid* m_mainGrid; cvf::ref m_unionOfMatrixActiveCells; diff --git a/ApplicationLibCode/ProjectDataModel/RimProject.cpp b/ApplicationLibCode/ProjectDataModel/RimProject.cpp index b1d14f032d3..572bf660550 100644 --- a/ApplicationLibCode/ProjectDataModel/RimProject.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimProject.cpp @@ -75,6 +75,7 @@ #include "RimPlotWindow.h" #include "RimPltPlotCollection.h" #include "RimPolylinesFromFileAnnotation.h" +#include "RimReservoirGridEnsemble.h" #include "RimRftPlotCollection.h" #include "RimSaturationPressurePlotCollection.h" #include "RimScriptCollection.h" @@ -716,6 +717,30 @@ void RimProject::assignIdToCaseGroup( RimIdenticalGridCaseGroup* caseGroup ) } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimProject::assignIdToCaseGroup( RimReservoirGridEnsemble* gridEnsemble ) +{ + if ( gridEnsemble ) + { + std::vector identicalCaseGroups = descendantsIncludingThisOfType(); + std::vector gridEnsembles = descendantsIncludingThisOfType(); + + for ( RimIdenticalGridCaseGroup* existingCaseGroup : identicalCaseGroups ) + { + m_nextValidCaseGroupId = std::max( m_nextValidCaseGroupId, existingCaseGroup->groupId() + 1 ); + } + + for ( RimReservoirGridEnsemble* existingEnsemble : gridEnsembles ) + { + m_nextValidCaseGroupId = std::max( m_nextValidCaseGroupId, existingEnsemble->groupId() + 1 ); + } + + gridEnsemble->setGroupId( m_nextValidCaseGroupId++ ); + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -1507,6 +1532,7 @@ void RimProject::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, Q { if ( oilField->analysisModels() ) uiTreeOrdering.add( oilField->analysisModels() ); } + uiTreeOrdering.add( &m_ensembleFileSetCollection ); } else { diff --git a/ApplicationLibCode/ProjectDataModel/RimProject.h b/ApplicationLibCode/ProjectDataModel/RimProject.h index 176fcb45170..7ced46e1442 100644 --- a/ApplicationLibCode/ProjectDataModel/RimProject.h +++ b/ApplicationLibCode/ProjectDataModel/RimProject.h @@ -46,6 +46,7 @@ class RimEclipseCase; class RimGeoMechCase; class RimIdenticalGridCaseGroup; class RimMainPlotCollection; +class RimReservoirGridEnsemble; class RimMeasurement; class RimAdvancedSnapshotExportDefinition; class RimObservedSummaryData; @@ -125,6 +126,7 @@ class RimProject : public caf::PdmDocument void assignCaseIdToCase( RimCase* reservoirCase ); void assignIdToCaseGroup( RimIdenticalGridCaseGroup* caseGroup ); + void assignIdToCaseGroup( RimReservoirGridEnsemble* gridEnsemble ); void assignViewIdToView( Rim3dView* view ); void assignPlotIdToPlotWindow( RimPlotWindow* plotWindow ); void assignCaseIdToSummaryCase( RimSummaryCase* summaryCase ); diff --git a/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.cpp b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.cpp new file mode 100644 index 00000000000..ffcf666c15a --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.cpp @@ -0,0 +1,865 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimReservoirGridEnsemble.h" + +#include "RiaLogging.h" +#include "RiaResultNames.h" + +#include "RifReaderOpmCommon.h" +#include "RigActiveCellInfo.h" +#include "RigEclipseCaseData.h" +#include "RigGridBase.h" +#include "RigGridManager.h" +#include "RigMainGrid.h" + +#include "ContourMap/RimStatisticsContourMap.h" +#include "ContourMap/RimStatisticsContourMapView.h" +#include "EnsembleFileSet/RimEnsembleFileSet.h" +#include "RimCaseCollection.h" +#include "RimEclipseCase.h" +#include "RimEclipseCellColors.h" +#include "RimEclipseResultCase.h" +#include "RimEclipseStatisticsCase.h" +#include "RimEclipseView.h" +#include "RimEclipseViewCollection.h" +#include "RimProject.h" +#include "RimWellTargetMapping.h" + +#include "cafCmdFeatureMenuBuilder.h" +#include "cafPdmFieldScriptingCapability.h" +#include "cafPdmObjectScriptingCapability.h" +#include "cafProgressInfo.h" + +#include "RigCaseCellResultsData.h" + +CAF_PDM_SOURCE_INIT( RimReservoirGridEnsemble, "RimReservoirGridEnsemble" ); + +namespace caf +{ +template <> +void caf::AppEnum::setUp() +{ + addItem( RimReservoirGridEnsemble::GridModeType::AUTO_DETECT, "AutoDetect", "Auto Detect" ); + addItem( RimReservoirGridEnsemble::GridModeType::SHARED_GRID, "SharedGrid", "Shared Grid" ); + addItem( RimReservoirGridEnsemble::GridModeType::INDIVIDUAL_GRIDS, "IndividualGrids", "Individual Grids" ); + setDefault( RimReservoirGridEnsemble::GridModeType::AUTO_DETECT ); +} +} // namespace caf + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimReservoirGridEnsemble::RimReservoirGridEnsemble() + : m_mainGrid( nullptr ) +{ + CAF_PDM_InitScriptableObjectWithNameAndComment( "Reservoir Grid Ensemble", + ":/GridCaseGroup16x16.png", + "", + "", + "ReservoirGridEnsemble", + "Grid Ensemble from File Set" ); + + CAF_PDM_InitScriptableField( &m_groupId, "GroupId", -1, "Ensemble ID" ); + m_groupId.uiCapability()->setUiReadOnly( true ); + m_groupId.capability()->setIOWriteable( false ); + + CAF_PDM_InitFieldNoDefault( &m_ensembleFileSet, "EnsembleFileSet", "Ensemble File Set" ); + m_ensembleFileSet.uiCapability()->setUiReadOnly( true ); + + CAF_PDM_InitField( &m_gridMode, "GridMode", GridModeType::AUTO_DETECT, "Grid Mode" ); + + CAF_PDM_InitFieldNoDefault( &m_caseCollection, "CaseCollection", "Source Cases" ); + m_caseCollection = new RimCaseCollection; + m_caseCollection->uiCapability()->setUiName( "Realizations" ); + m_caseCollection->uiCapability()->setUiIconFromResourceString( ":/Cases16x16.png" ); + m_caseCollection.xmlCapability()->disableIO(); + + CAF_PDM_InitFieldNoDefault( &m_statisticsCaseCollection, "StatisticsCaseCollection", "Statistics Cases" ); + m_statisticsCaseCollection = new RimCaseCollection; + m_statisticsCaseCollection->uiCapability()->setUiName( "Derived Statistics" ); + m_statisticsCaseCollection->uiCapability()->setUiIconFromResourceString( ":/Histograms16x16.png" ); + + CAF_PDM_InitFieldNoDefault( &m_viewCollection, "ViewCollection", "Views" ); + m_viewCollection = new RimEclipseViewCollection; + m_viewCollection->setEclipseCaseProvider( [this]() { return this->cases(); } ); + + CAF_PDM_InitFieldNoDefault( &m_wellTargetMappings, "WellTargetMappings", "Well Target Mappings" ); + CAF_PDM_InitFieldNoDefault( &m_statisticsContourMaps, "StatisticsContourMaps", "Statistics Contour Maps" ); + + m_unionOfMatrixActiveCells = new RigActiveCellInfo; + m_unionOfFractureActiveCells = new RigActiveCellInfo; + + setDeletable( true ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int RimReservoirGridEnsemble::groupId() const +{ + return m_groupId; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::setGroupId( int id ) +{ + m_groupId = id; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::setEnsembleFileSet( RimEnsembleFileSet* ensembleFileSet ) +{ + if ( m_ensembleFileSet ) + { + m_ensembleFileSet->fileSetChanged.disconnect( this ); + m_ensembleFileSet->nameChanged.disconnect( this ); + } + + m_ensembleFileSet = ensembleFileSet; + + if ( m_ensembleFileSet ) + { + m_ensembleFileSet->fileSetChanged.connect( this, &RimReservoirGridEnsemble::onFileSetChanged ); + m_ensembleFileSet->nameChanged.connect( this, + [this]( const caf::SignalEmitter* ) + { + if ( m_ensembleFileSet ) setName( m_ensembleFileSet->name() ); + } ); + setName( m_ensembleFileSet->name() ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimEnsembleFileSet* RimReservoirGridEnsemble::ensembleFileSet() const +{ + return m_ensembleFileSet; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::addCase( RimEclipseCase* reservoir ) +{ + CVF_ASSERT( reservoir ); + + if ( !m_mainGrid && reservoir->eclipseCaseData() ) + { + m_mainGrid = reservoir->eclipseCaseData()->mainGrid(); + } + else if ( hasSharedGrid() && reservoir->eclipseCaseData() ) + { + // Share the main grid for identical grids + reservoir->eclipseCaseData()->setMainGrid( m_mainGrid ); + } + + m_caseCollection()->reservoirs().push_back( reservoir ); + + clearActiveCellUnions(); + clearStatisticsResults(); + updateMainGridAndActiveCellsForStatisticsCases(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::removeCase( RimEclipseCase* reservoir ) +{ + if ( m_caseCollection()->reservoirs().count( reservoir ) == 0 ) return; + + m_caseCollection()->reservoirs().removeChild( reservoir ); + + if ( m_caseCollection()->reservoirs().empty() ) + { + m_mainGrid = nullptr; + } + + clearActiveCellUnions(); + clearStatisticsResults(); + updateMainGridAndActiveCellsForStatisticsCases(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimReservoirGridEnsemble::contains( RimEclipseCase* reservoir ) const +{ + CVF_ASSERT( reservoir ); + + for ( RimEclipseCase* rimReservoir : cases() ) + { + if ( reservoir->gridFileName() == rimReservoir->gridFileName() ) return true; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimReservoirGridEnsemble::cases() const +{ + if ( !m_caseCollection ) return {}; + + return m_caseCollection->reservoirs.childrenByType(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimEclipseCase* RimReservoirGridEnsemble::mainCase() +{ + if ( !m_caseCollection()->reservoirs().empty() ) + { + return m_caseCollection()->reservoirs()[0]; + } + return nullptr; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimReservoirGridEnsemble::sourceCases() const +{ + return cases(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimEclipseCase* RimReservoirGridEnsemble::findByFileName( const QString& gridFileName ) const +{ + for ( RimEclipseCase* rimReservoir : cases() ) + { + if ( gridFileName == rimReservoir->gridFileName() ) return rimReservoir; + } + + return nullptr; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimReservoirGridEnsemble::hasSharedGrid() const +{ + return m_gridMode.v() == GridModeType::SHARED_GRID; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimReservoirGridEnsemble::isGridDataLoaded() const +{ + return m_mainGrid != nullptr; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimReservoirGridEnsemble::GridModeType RimReservoirGridEnsemble::effectiveGridMode() const +{ + return m_gridMode.v(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::setGridMode( GridModeType mode ) +{ + m_gridMode = mode; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RigMainGrid* RimReservoirGridEnsemble::mainGrid() +{ + // Trigger deferred loading if needed + if ( !m_mainGrid && !cases().empty() ) + { + const_cast( this )->loadGridDataFromFiles(); + } + + return m_mainGrid; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::setupSharedGrid() +{ + if ( !hasSharedGrid() ) return; + + auto allCases = cases(); + if ( allCases.empty() ) return; + + // Use the first case's grid as the shared grid + RimEclipseCase* firstCase = allCases[0]; + if ( !firstCase || !firstCase->eclipseCaseData() ) return; + + m_mainGrid = firstCase->eclipseCaseData()->mainGrid(); + + // Set the shared grid on all other cases + for ( size_t i = 1; i < allCases.size(); i++ ) + { + if ( allCases[i] && allCases[i]->eclipseCaseData() ) + { + allCases[i]->eclipseCaseData()->setMainGrid( m_mainGrid ); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RigActiveCellInfo* RimReservoirGridEnsemble::unionOfActiveCells( RiaDefines::PorosityModelType porosityType ) +{ + if ( porosityType == RiaDefines::PorosityModelType::MATRIX_MODEL ) + { + return m_unionOfMatrixActiveCells.p(); + } + else + { + return m_unionOfFractureActiveCells.p(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::computeUnionOfActiveCells() +{ + if ( !hasSharedGrid() ) return; + + if ( m_unionOfMatrixActiveCells->reservoirActiveCellCount() > 0 ) + { + return; + } + + if ( m_caseCollection->reservoirs.empty() || !m_mainGrid ) + { + clearActiveCellUnions(); + return; + } + + m_unionOfMatrixActiveCells->setReservoirCellCount( m_mainGrid->totalCellCount() ); + m_unionOfFractureActiveCells->setReservoirCellCount( m_mainGrid->totalCellCount() ); + m_unionOfMatrixActiveCells->setGridCount( m_mainGrid->gridCount() ); + m_unionOfFractureActiveCells->setGridCount( m_mainGrid->gridCount() ); + + size_t globalActiveMatrixIndex = 0; + size_t globalActiveFractureIndex = 0; + + for ( size_t gridIdx = 0; gridIdx < m_mainGrid->gridCount(); gridIdx++ ) + { + RigGridBase* grid = m_mainGrid->gridByIndex( gridIdx ); + + std::vector activeM( grid->cellCount(), 0 ); + std::vector activeF( grid->cellCount(), 0 ); + + for ( size_t gridLocalCellIndex = 0; gridLocalCellIndex < grid->cellCount(); gridLocalCellIndex++ ) + { + for ( size_t caseIdx = 0; caseIdx < m_caseCollection->reservoirs.size(); caseIdx++ ) + { + size_t reservoirCellIndex = grid->reservoirCellIndex( gridLocalCellIndex ); + + if ( activeM[gridLocalCellIndex] == 0 ) + { + if ( m_caseCollection->reservoirs[caseIdx] + ->eclipseCaseData() + ->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ) + ->isActive( reservoirCellIndex ) ) + { + activeM[gridLocalCellIndex] = 1; + } + } + + if ( activeF[gridLocalCellIndex] == 0 ) + { + if ( m_caseCollection->reservoirs[caseIdx] + ->eclipseCaseData() + ->activeCellInfo( RiaDefines::PorosityModelType::FRACTURE_MODEL ) + ->isActive( reservoirCellIndex ) ) + { + activeF[gridLocalCellIndex] = 1; + } + } + } + } + + size_t activeMatrixIndex = 0; + size_t activeFractureIndex = 0; + + for ( size_t gridLocalCellIndex = 0; gridLocalCellIndex < grid->cellCount(); gridLocalCellIndex++ ) + { + size_t reservoirCellIndex = grid->reservoirCellIndex( gridLocalCellIndex ); + + if ( activeM[gridLocalCellIndex] != 0 ) + { + m_unionOfMatrixActiveCells->setCellResultIndex( reservoirCellIndex, globalActiveMatrixIndex++ ); + activeMatrixIndex++; + } + + if ( activeF[gridLocalCellIndex] != 0 ) + { + m_unionOfFractureActiveCells->setCellResultIndex( reservoirCellIndex, globalActiveFractureIndex++ ); + activeFractureIndex++; + } + } + + m_unionOfMatrixActiveCells->setGridActiveCellCounts( gridIdx, activeMatrixIndex ); + m_unionOfFractureActiveCells->setGridActiveCellCounts( gridIdx, activeFractureIndex ); + } + + m_unionOfMatrixActiveCells->computeDerivedData(); + m_unionOfFractureActiveCells->computeDerivedData(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimCaseCollection* RimReservoirGridEnsemble::statisticsCaseCollection() const +{ + return m_statisticsCaseCollection; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimEclipseStatisticsCase* RimReservoirGridEnsemble::createAndAppendStatisticsCase() +{ + if ( !hasSharedGrid() ) + { + RiaLogging::warning( QString( "Cannot create statistics case for ensemble '%1': grids are not identical." ).arg( name() ) ); + return nullptr; + } + + return RimReservoirGridEnsembleBase::createAndAppendStatisticsCase(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::addView( RimEclipseView* view ) +{ + m_viewCollection->addView( view ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimEclipseView* RimReservoirGridEnsemble::addViewForCase( RimEclipseCase* eclipseCase ) +{ + return m_viewCollection->addView( eclipseCase ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimReservoirGridEnsemble::allViews() const +{ + std::vector views; + + for ( auto view : m_viewCollection->views() ) + { + views.push_back( view ); + } + + for ( auto statCase : m_statisticsCaseCollection->reservoirs() ) + { + for ( auto view : statCase->reservoirViews() ) + { + views.push_back( view ); + } + } + + for ( auto cmap : m_statisticsContourMaps ) + { + for ( auto view : cmap->views() ) + { + views.push_back( view ); + } + } + + return views; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::set RimReservoirGridEnsemble::casesInViews() const +{ + if ( !m_caseCollection ) return {}; + if ( !m_viewCollection || m_viewCollection->isEmpty() ) return {}; + + std::set retCases; + + for ( auto view : m_viewCollection->views() ) + { + if ( view->eclipseCase() != nullptr ) retCases.insert( view->eclipseCase() ); + } + + return retCases; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::addWellTargetMapping( RimWellTargetMapping* wellTargetMapping ) +{ + m_wellTargetMappings.push_back( wellTargetMapping ); + wellTargetMapping->updateResultDefinition(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimReservoirGridEnsemble::wellTargetMappings() const +{ + return m_wellTargetMappings.childrenByType(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::addStatisticsContourMap( RimStatisticsContourMap* statisticsContourMap ) +{ + m_statisticsContourMaps.push_back( statisticsContourMap ); + statisticsContourMap->setName( QString( "Ensemble Contour Map #%1" ).arg( m_statisticsContourMaps.size() ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::loadDataAndUpdate() +{ + if ( !isGridDataLoaded() ) + { + loadGridDataFromFiles(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::createGridCasesFromEnsembleFileSet() +{ + if ( !m_ensembleFileSet ) return; + + // Clear existing cases and statistics + m_caseCollection->reservoirs.deleteChildren(); + m_statisticsCaseCollection->reservoirs.deleteChildren(); + m_mainGrid = nullptr; + clearActiveCellUnions(); + + // Create case objects without loading grids + createCaseObjectsFromEnsembleFileSet(); + + updateConnectedEditors(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const +{ + menuBuilder << "RicNewViewForGridEnsembleFeature"; + menuBuilder << "RicNewStatisticsContourMapFeature"; + + if ( hasSharedGrid() ) + { + menuBuilder << "RicNewStatisticsCaseFeature"; + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) +{ + uiOrdering.add( nameField() ); + uiOrdering.add( &m_groupId ); + uiOrdering.add( &m_ensembleFileSet ); + uiOrdering.add( &m_gridMode ); + + uiOrdering.skipRemainingFields(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::initAfterRead() +{ + if ( m_ensembleFileSet ) + { + m_ensembleFileSet->fileSetChanged.connect( this, &RimReservoirGridEnsemble::onFileSetChanged ); + m_ensembleFileSet->nameChanged.connect( this, + [this]( const caf::SignalEmitter* ) + { + if ( m_ensembleFileSet ) setName( m_ensembleFileSet->name() ); + } ); + + // Create case objects WITHOUT loading grid data (deferred loading) + if ( m_caseCollection && m_caseCollection->reservoirs().empty() ) + { + createCaseObjectsFromEnsembleFileSet(); + } + + // NB! This code must be run AFTER the grid case objects are created. + for ( auto view : m_viewCollection->views() ) + { + if ( view ) + { + // Resolve the grid case reference for the view after grids are loaded + view->resolveReferencesRecursively(); + + // Propagate the eclipse case to child objects to ensure all references are updated. setEclipseCase() calls + // propagateEclipseCaseToChildObjects() internally, but we need to call it here to ensure propagation after loading and + // reference resolution. + auto eclipseCase = view->eclipseCase(); + view->setEclipseCase( eclipseCase ); + } + } + } + + // Set the case provider for views in the view collection + if ( m_viewCollection ) + { + m_viewCollection->setEclipseCaseProvider( [this]() { return this->cases(); } ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::onFileSetChanged( const caf::SignalEmitter* emitter ) +{ + createGridCasesFromEnsembleFileSet(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::clearActiveCellUnions() +{ + m_unionOfMatrixActiveCells->clear(); + m_unionOfFractureActiveCells->clear(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::clearStatisticsResults() +{ + for ( size_t i = 0; i < m_statisticsCaseCollection->reservoirs().size(); i++ ) + { + RimEclipseCase* rimStaticsCase = m_statisticsCaseCollection->reservoirs[i]; + if ( !rimStaticsCase ) continue; + + if ( rimStaticsCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL ) ) + { + rimStaticsCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->clearAllResults(); + } + if ( rimStaticsCase->results( RiaDefines::PorosityModelType::FRACTURE_MODEL ) ) + { + rimStaticsCase->results( RiaDefines::PorosityModelType::FRACTURE_MODEL )->clearAllResults(); + } + + auto views = rimStaticsCase->reservoirViews(); + for ( size_t j = 0; j < views.size(); j++ ) + { + RimEclipseView* rimReservoirView = views[j]; + rimReservoirView->cellResult()->setResultVariable( RiaResultNames::undefinedResultName() ); + rimReservoirView->loadDataAndUpdate(); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::updateMainGridAndActiveCellsForStatisticsCases() +{ + for ( size_t i = 0; i < m_statisticsCaseCollection->reservoirs().size(); i++ ) + { + RimEclipseCase* rimStaticsCase = m_statisticsCaseCollection->reservoirs[i]; + + if ( rimStaticsCase->eclipseCaseData() ) + { + rimStaticsCase->eclipseCaseData()->setMainGrid( mainGrid() ); + + if ( i == 0 ) + { + rimStaticsCase->computeActiveCellsBoundingBox(); + } + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::createCaseObjectsFromEnsembleFileSet() +{ + if ( !m_ensembleFileSet ) return; + + // Get grid file paths from the file set + QStringList gridFiles = m_ensembleFileSet->createPaths( ".EGRID" ); + if ( gridFiles.empty() ) + { + // Try .GRID extension + gridFiles = m_ensembleFileSet->createPaths( ".GRID" ); + } + + if ( gridFiles.empty() ) + { + RiaLogging::warning( QString( "No grid files found for ensemble '%1'" ).arg( name() ) ); + return; + } + + // Create case objects without loading grids + for ( const QString& gridFile : gridFiles ) + { + RimEclipseResultCase* resultCase = new RimEclipseResultCase(); + resultCase->setGridFileName( gridFile ); + // DO NOT call openEclipseGridFile() here - deferred loading + + RimProject::current()->assignCaseIdToCase( resultCase ); + m_caseCollection->reservoirs().push_back( resultCase ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::loadGridDataFromFiles() +{ + // Guard: Only load once + if ( m_mainGrid != nullptr ) return; + + auto allCases = cases(); + if ( allCases.empty() ) return; + + // Determine effective grid mode + GridModeType effectiveMode = m_gridMode.v(); + + if ( effectiveMode == GridModeType::AUTO_DETECT ) + { + // Run dimension detection + bool identical = detectGridDimensionEquality(); + effectiveMode = identical ? GridModeType::SHARED_GRID : GridModeType::INDIVIDUAL_GRIDS; + m_gridMode = effectiveMode; // Update to detected mode + } + + // Load grids based on effective mode + if ( effectiveMode == GridModeType::SHARED_GRID ) + { + loadGridsInSharedMode(); + } + else + { + // TODO: Probably not needed load grids here, they are loaded on demand in individual mode + // loadGridsInIndividualMode(); + } + + updateConnectedEditors(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimReservoirGridEnsemble::detectGridDimensionEquality() +{ + auto allCases = cases(); + if ( allCases.size() < 2 ) return true; + + RifReaderOpmCommon::GridDimensions firstDim; + + for ( int i = 0; i < static_cast( allCases.size() ); i++ ) + { + auto dim = RifReaderOpmCommon::readGridDimensions( allCases[i]->gridFileName() ); + + if ( i == 0 ) + { + firstDim = dim; + } + else if ( dim.i != firstDim.i || dim.j != firstDim.j || dim.k != firstDim.k ) + { + return false; // Different dimensions + } + } + + return true; // All dimensions identical +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::loadGridsInSharedMode() +{ + auto allCases = cases(); + + RiaLogging::info( QString( "Grid ensemble '%1': Loading grid in shared mode for %2 cases." ).arg( name() ).arg( allCases.size() ) ); + + // Load first case fully + RimEclipseCase* firstCase = allCases[0]; + if ( firstCase->openEclipseGridFile() ) + { + m_mainGrid = firstCase->eclipseCaseData()->mainGrid(); + + // Load remaining cases and share grid + for ( size_t i = 1; i < allCases.size(); i++ ) + { + RimEclipseCase* eclipseCase = allCases[i]; + if ( auto resultCase = dynamic_cast( eclipseCase ) ) + { + resultCase->openAndReadActiveCellData( firstCase->eclipseCaseData() ); + } + eclipseCase->eclipseCaseData()->setMainGrid( m_mainGrid ); + } + + computeUnionOfActiveCells(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimReservoirGridEnsemble::loadGridsInIndividualMode() +{ + auto allCases = cases(); + + RiaLogging::info( QString( "Grid ensemble '%1': Loading grids in individual mode for %2 cases." ).arg( name() ).arg( allCases.size() ) ); + + for ( auto eclipseCase : allCases ) + { + eclipseCase->openEclipseGridFile(); + } + + // Store first grid as reference + if ( !allCases.empty() && allCases[0]->eclipseCaseData() ) + { + m_mainGrid = allCases[0]->eclipseCaseData()->mainGrid(); + } +} diff --git a/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.h b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.h new file mode 100644 index 00000000000..9f4b232ceff --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.h @@ -0,0 +1,163 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "RiaPorosityModel.h" +#include "RimNamedObject.h" +#include "RimReservoirGridEnsembleBase.h" + +#include "cafAppEnum.h" +#include "cafPdmChildArrayField.h" +#include "cafPdmChildField.h" +#include "cafPdmField.h" +#include "cafPdmPtrField.h" + +#include "cvfObject.h" + +#include + +class RigActiveCellInfo; +class RigMainGrid; +class RimCaseCollection; +class RimEclipseCase; +class RimEclipseStatisticsCase; +class RimEclipseView; +class RimEclipseViewCollection; +class RimEnsembleFileSet; +class RimStatisticsContourMap; +class RimWellTargetMapping; + +//================================================================================================== +/// +/// RimReservoirGridEnsemble - Grid ensemble created from an RimEnsembleFileSet +/// +/// This class creates and manages grid cases from an ensemble file set pattern. It detects +/// whether all grids have identical geometry and enables shared grid operations (statistics, +/// shared main grid) when they do. +/// +//================================================================================================== +class RimReservoirGridEnsemble : public RimNamedObject, public RimReservoirGridEnsembleBase +{ + CAF_PDM_HEADER_INIT; + +public: + enum class GridModeType + { + AUTO_DETECT, + SHARED_GRID, + INDIVIDUAL_GRIDS + }; + + RimReservoirGridEnsemble(); + + // Ensemble file set connection + void setEnsembleFileSet( RimEnsembleFileSet* ensembleFileSet ); + RimEnsembleFileSet* ensembleFileSet() const; + + // Group ID + int groupId() const; + void setGroupId( int id ); + + // Case management + void addCase( RimEclipseCase* reservoir ); + void removeCase( RimEclipseCase* reservoir ); + bool contains( RimEclipseCase* reservoir ) const; + std::vector cases() const; + RimEclipseCase* mainCase() override; + std::vector sourceCases() const override; + RimEclipseCase* findByFileName( const QString& gridFileName ) const; + + // Grid detection and shared grid + RigMainGrid* mainGrid() override; + void setupSharedGrid(); + + // Deferred loading control + void loadGridDataFromFiles(); + bool isGridDataLoaded() const; + void setGridMode( GridModeType mode ); + + // Helper methods + bool hasSharedGrid() const; + GridModeType effectiveGridMode() const; + + // Active cells + RigActiveCellInfo* unionOfActiveCells( RiaDefines::PorosityModelType porosityType ) override; + void computeUnionOfActiveCells() override; + + // Statistics + RimCaseCollection* statisticsCaseCollection() const override; + RimEclipseStatisticsCase* createAndAppendStatisticsCase() override; + + // Views + void addView( RimEclipseView* view ); + RimEclipseView* addViewForCase( RimEclipseCase* eclipseCase ); + std::vector allViews() const; + std::set casesInViews() const; + + // Well target mapping + void addWellTargetMapping( RimWellTargetMapping* wellTargetMapping ); + std::vector wellTargetMappings() const; + + // Statistics contour maps + void addStatisticsContourMap( RimStatisticsContourMap* statisticsContourMap ); + + // Load and initialization + void loadDataAndUpdate(); + void createGridCasesFromEnsembleFileSet(); + +protected: + void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + void initAfterRead() override; + +private: + void onFileSetChanged( const caf::SignalEmitter* emitter ); + void clearActiveCellUnions(); + void clearStatisticsResults(); + void updateMainGridAndActiveCellsForStatisticsCases(); + + void createCaseObjectsFromEnsembleFileSet(); + bool detectGridDimensionEquality(); + void loadGridsInSharedMode(); + void loadGridsInIndividualMode(); + +private: + // File set reference + caf::PdmPtrField m_ensembleFileSet; + + // Ensemble id + caf::PdmField m_groupId; + + // Cases + caf::PdmChildField m_caseCollection; + caf::PdmChildField m_statisticsCaseCollection; + + // Grid mode + caf::PdmField> m_gridMode; + + // Views and mappings + caf::PdmChildField m_viewCollection; + caf::PdmChildArrayField m_wellTargetMappings; + caf::PdmChildArrayField m_statisticsContourMaps; + + // Shared grid data (for identical grids) + RigMainGrid* m_mainGrid; + cvf::ref m_unionOfMatrixActiveCells; + cvf::ref m_unionOfFractureActiveCells; +}; diff --git a/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsembleBase.cpp b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsembleBase.cpp new file mode 100644 index 00000000000..20d35d1d7bc --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsembleBase.cpp @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimReservoirGridEnsembleBase.h" + +#include "RimCaseCollection.h" +#include "RimEclipseStatisticsCase.h" + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimEclipseStatisticsCase* RimReservoirGridEnsembleBase::createAndAppendStatisticsCase() +{ + auto* statsColl = statisticsCaseCollection(); + if ( !statsColl ) return nullptr; + + RimEclipseStatisticsCase* newStatisticsCase = new RimEclipseStatisticsCase; + + newStatisticsCase->setCaseUserDescription( QString( "Statistics " ) + QString::number( statsColl->reservoirs.size() + 1 ) ); + statsColl->reservoirs.push_back( newStatisticsCase ); + + newStatisticsCase->populateResultSelectionAfterLoadingGrid(); + + auto cases = sourceCases(); + if ( !cases.empty() ) + { + newStatisticsCase->setWellDataSourceCase( cases.front() ); + } + + newStatisticsCase->openEclipseGridFile(); + newStatisticsCase->computeActiveCellsBoundingBox(); + newStatisticsCase->selectAllTimeSteps(); + + return newStatisticsCase; +} diff --git a/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsembleBase.h b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsembleBase.h new file mode 100644 index 00000000000..e563ed93b25 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsembleBase.h @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "RiaPorosityModel.h" + +#include + +class RigActiveCellInfo; +class RigMainGrid; +class RimCaseCollection; +class RimEclipseCase; +class RimEclipseStatisticsCase; + +class RimReservoirGridEnsembleBase +{ +public: + virtual ~RimReservoirGridEnsembleBase() = default; + + virtual RigMainGrid* mainGrid() = 0; + virtual RimEclipseCase* mainCase() = 0; + virtual std::vector sourceCases() const = 0; + virtual RigActiveCellInfo* unionOfActiveCells( RiaDefines::PorosityModelType porosityType ) = 0; + virtual void computeUnionOfActiveCells() = 0; + virtual RimCaseCollection* statisticsCaseCollection() const = 0; + + virtual RimEclipseStatisticsCase* createAndAppendStatisticsCase(); +}; diff --git a/ApplicationLibCode/ProjectDataModel/RimWellTargetMapping.cpp b/ApplicationLibCode/ProjectDataModel/RimWellTargetMapping.cpp index b510536a9b9..3ac8de98526 100644 --- a/ApplicationLibCode/ProjectDataModel/RimWellTargetMapping.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimWellTargetMapping.cpp @@ -42,6 +42,7 @@ #include "RimEclipseResultDefinition.h" #include "RimEclipseView.h" #include "RimRegularGridCase.h" +#include "RimReservoirGridEnsemble.h" #include "RimTools.h" #include "cafPdmUiDoubleSliderEditor.h" @@ -458,7 +459,8 @@ void RimWellTargetMapping::defineUiOrdering( QString uiConfigName, caf::PdmUiOrd resultGroup->add( &m_volumesType ); - auto hasEnsembleParent = firstAncestorOrThisOfType() != nullptr; + auto hasEnsembleParent = firstAncestorOrThisOfType() != nullptr || + firstAncestorOrThisOfType() != nullptr; if ( !hasEnsembleParent ) uiOrdering.add( &m_filterView ); caf::PdmUiGroup* minimumCellValuesGroup = uiOrdering.addNewGroup( "Minimum Cell Values" ); @@ -515,7 +517,8 @@ std::vector RimWellTargetMapping::getVisibilityFilter() const std::vector filter = {}; // Visibility filter is only valid in the single case setting - auto hasEnsembleParent = firstAncestorOrThisOfType() != nullptr; + auto hasEnsembleParent = firstAncestorOrThisOfType() != nullptr || + firstAncestorOrThisOfType() != nullptr; if ( !hasEnsembleParent ) { auto fc = firstCase(); @@ -548,10 +551,12 @@ std::vector RimWellTargetMapping::getVisibilityFilter() const RimEclipseCase* RimWellTargetMapping::firstCase() const { auto ensemble = firstAncestorOrThisOfType(); - if ( ensemble && !ensemble->cases().empty() ) - return ensemble->cases()[0]; - else - return firstAncestorOrThisOfType(); + if ( ensemble && !ensemble->cases().empty() ) return ensemble->cases()[0]; + + auto reservoirGridEnsemble = firstAncestorOrThisOfType(); + if ( reservoirGridEnsemble && !reservoirGridEnsemble->cases().empty() ) return reservoirGridEnsemble->cases()[0]; + + return firstAncestorOrThisOfType(); } //-------------------------------------------------------------------------------------------------- @@ -637,11 +642,22 @@ void RimWellTargetMapping::resetMinimumCellValuesToDefault() //-------------------------------------------------------------------------------------------------- void RimWellTargetMapping::onGenerateButtonClicked() { - auto hasEnsembleParent = firstAncestorOrThisOfType() != nullptr; - if ( hasEnsembleParent ) + auto hasEclipseCaseEnsembleParent = firstAncestorOrThisOfType() != nullptr; + auto hasGridEnsembleParent = firstAncestorOrThisOfType() != nullptr; + if ( hasEclipseCaseEnsembleParent ) { generateEnsembleStatistics(); } + else if ( hasGridEnsembleParent ) + { + // For RimReservoirGridEnsemble, generate for first case + // Full ensemble statistics support can be added later + if ( auto eclipseCase = firstCase() ) + { + bool setTimeStepInView = true; + generateCandidates( eclipseCase, setTimeStepInView ); + } + } else if ( auto eclipseCase = firstCase() ) { generateCandidates( eclipseCase ); diff --git a/ApplicationLibCode/UserInterface/RiuDockWidgetTools.cpp b/ApplicationLibCode/UserInterface/RiuDockWidgetTools.cpp index 21764ae3ce5..56e1b0c2f3d 100644 --- a/ApplicationLibCode/UserInterface/RiuDockWidgetTools.cpp +++ b/ApplicationLibCode/UserInterface/RiuDockWidgetTools.cpp @@ -393,7 +393,7 @@ QIcon RiuDockWidgetTools::dockIcon( const QString dockWidgetName ) else if ( dockWidgetName == mainWindowProjectTreeName() ) return QIcon( ":/standard.svg" ); else if ( dockWidgetName == mainWindowDataSourceTreeName() ) - return QIcon( ":/Calculator.svg" ); + return QIcon( ":/data-sources.svg" ); else if ( dockWidgetName == mainWindowScriptsTreeName() ) return QIcon( ":/scripts.svg" ); else if ( dockWidgetName == mainPlotWindowName() ) diff --git a/ApplicationLibCode/UserInterface/RiuMainWindow.cpp b/ApplicationLibCode/UserInterface/RiuMainWindow.cpp index fc1e5c6a125..6473b7d0a64 100644 --- a/ApplicationLibCode/UserInterface/RiuMainWindow.cpp +++ b/ApplicationLibCode/UserInterface/RiuMainWindow.cpp @@ -749,14 +749,14 @@ void RiuMainWindow::createToolBars() void RiuMainWindow::createDockPanels() { const int nTreeViews = 3; - const std::vector treeViewTitles = { "Project Tree", "Calculator Data ", "Scripts/Jobs" }; + const std::vector treeViewTitles = { "Project Tree", "Data Sources", "Scripts/Jobs" }; const std::vector treeViewConfigs = { "MainWindow.ProjectTree", "MainWindow.DataSources", "MainWindow.Scripts" }; const std::vector treeViewDockNames = { RiuDockWidgetTools::mainWindowProjectTreeName(), RiuDockWidgetTools::mainWindowDataSourceTreeName(), RiuDockWidgetTools::mainWindowScriptsTreeName() }; const std::vector defaultDockWidgetArea{ ads::DockWidgetArea::LeftDockWidgetArea, - ads::DockWidgetArea::LeftDockWidgetArea, + ads::DockWidgetArea::RightDockWidgetArea, ads::DockWidgetArea::LeftDockWidgetArea }; createTreeViews( nTreeViews );