From 94a5bf5d2af195b36a1c2ea5a1129496d6ddcc07 Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Fri, 13 Feb 2026 07:36:46 +0100 Subject: [PATCH 1/3] Add readGridDimensions --- .../FileInterface/RifReaderOpmCommon.cpp | 25 +++++++++++++++++++ .../FileInterface/RifReaderOpmCommon.h | 10 ++++++++ 2 files changed, 35 insertions(+) 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 ); From 74c89c7fe628d77db56be102c89f80d024b04988 Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Fri, 13 Feb 2026 07:37:35 +0100 Subject: [PATCH 2/3] Rename to dock window in 3D to Data Sources --- ApplicationLibCode/UserInterface/RiuDockWidgetTools.cpp | 2 +- ApplicationLibCode/UserInterface/RiuMainWindow.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 ); From 5031a804b4fc7a344c69331ec88d861bd2847356 Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Fri, 13 Feb 2026 08:28:34 +0100 Subject: [PATCH 3/3] Add support for Reservoir Grid Ensembles Introduce RimReservoirGridEnsemble and RimReservoirGridEnsembleBase to manage ensembles of grid-only Eclipse cases. Enable detection of identical grids for shared operations and statistics. Integrate grid ensembles into project structure, UI, and menu system. Refactor statistics and view handling to support both case groups and grid ensembles via a common interface. Add deferred grid loading, robust case referencing, and migration for well data source fields. New features and menu items allow creation and management of grid ensembles from file sets, enabling new workflows for grid-only ensemble analysis. --- .../Application/RiaApplication.cpp | 17 + .../RicfComputeCaseGroupStatistics.cpp | 4 +- .../RicComputeStatisticsFeature.cpp | 8 +- .../RicNewStatisticsCaseFeature.cpp | 35 +- .../CMakeLists_files.cmake | 2 + ...eservoirGridEnsembleFromFileSetFeature.cpp | 127 +++ ...eReservoirGridEnsembleFromFileSetFeature.h | 34 + .../Commands/RicCloseCaseFeature.cpp | 15 +- .../ProjectDataModel/CMakeLists_files.cmake | 4 + .../EnsembleFileSet/RimEnsembleFileSet.cpp | 1 + .../RimEnsembleFileSetTools.cpp | 30 + .../EnsembleFileSet/RimEnsembleFileSetTools.h | 3 + .../ProjectDataModel/RimCaseCollection.cpp | 23 +- .../ProjectDataModel/RimCaseCollection.h | 7 +- .../RimEclipseCaseCollection.cpp | 6 +- .../RimEclipseCaseCollection.h | 2 + .../RimEclipseCaseEnsemble.cpp | 10 - .../ProjectDataModel/RimEclipseCaseEnsemble.h | 1 - .../RimEclipseStatisticsCase.cpp | 139 +-- .../RimEclipseStatisticsCase.h | 13 +- .../RimEclipseStatisticsCaseEvaluator.cpp | 18 +- .../RimEclipseStatisticsCaseEvaluator.h | 17 +- .../ProjectDataModel/RimEclipseView.cpp | 35 +- .../ProjectDataModel/RimEclipseView.h | 7 + .../RimEclipseViewCollection.cpp | 40 + .../RimEclipseViewCollection.h | 9 +- .../RimIdenticalGridCaseGroup.cpp | 82 +- .../RimIdenticalGridCaseGroup.h | 22 +- .../ProjectDataModel/RimProject.cpp | 26 + .../ProjectDataModel/RimProject.h | 2 + .../RimReservoirGridEnsemble.cpp | 865 ++++++++++++++++++ .../RimReservoirGridEnsemble.h | 163 ++++ .../RimReservoirGridEnsembleBase.cpp | 50 + .../RimReservoirGridEnsembleBase.h | 44 + .../ProjectDataModel/RimWellTargetMapping.cpp | 32 +- 35 files changed, 1701 insertions(+), 192 deletions(-) create mode 100644 ApplicationLibCode/Commands/EnsembleFileSetCommands/RicCreateReservoirGridEnsembleFromFileSetFeature.cpp create mode 100644 ApplicationLibCode/Commands/EnsembleFileSetCommands/RicCreateReservoirGridEnsembleFromFileSetFeature.h create mode 100644 ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.cpp create mode 100644 ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.h create mode 100644 ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsembleBase.cpp create mode 100644 ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsembleBase.h 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/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 );