///////////////////////////////////////////////////////////////////////////////// // // 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 "cafPdmUiTreeOrdering.h" #include "RiaLogging.h" #include "RiaQStringFormatter.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 "EnsembleFileSet/RimEnsembleFileSetTools.h" #include "Formations/RimFormationNames.h" #include "Formations/RimFormationNamesCollection.h" #include "Formations/RimFormationTools.h" #include "Rim2dIntersectionViewCollection.h" #include "RimCaseCollection.h" #include "RimDataFilterCollection.h" #include "RimDataFilterInViewCollection.h" #include "RimEclipseCase.h" #include "RimEclipseCellColors.h" #include "RimEclipseResultCase.h" #include "RimEclipseStatisticsCase.h" #include "RimEclipseView.h" #include "RimEclipseViewCollection.h" #include "RimOilField.h" #include "RimProject.h" #include "RimWellTargetMapping.h" #include "cafCmdFeatureMenuBuilder.h" #include "cafIconProvider.h" #include "cafPdmFieldScriptingCapability.h" #include "cafPdmObjectScriptingCapability.h" #include "cafPdmUiTreeAttributes.h" #include "cafPdmUiTreeOrdering.h" #include "cafPdmUiTreeViewEditor.h" #include "cafProgressInfo.h" #include "RigCaseCellResultsData.h" CAF_PDM_SOURCE_INIT( RimReservoirGridEnsemble, "RimReservoirGridEnsemble" ); namespace caf { template <> void caf::AppEnum::setUp() { addItem( RimReservoirGridEnsembleBase::GridModeType::SHARED_GRID, "SharedGrid", "Shared Grid" ); addItem( RimReservoirGridEnsembleBase::GridModeType::INDIVIDUAL_GRIDS, "IndividualGrids", "Individual Grids" ); setDefault( RimReservoirGridEnsembleBase::GridModeType::SHARED_GRID ); } } // namespace caf //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimReservoirGridEnsemble::RimReservoirGridEnsemble() { 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_autoDetectGridType, "AutoDetectGridType", true, "Auto Detect Grid Type" ); CAF_PDM_InitField( &m_gridMode, "GridMode", GridModeType::SHARED_GRID, "Grid Mode" ); CAF_PDM_InitField( &m_detectedGridMode, "DetectedGridMode", GridModeType::SHARED_GRID, "Detected Grid Mode" ); m_detectedGridMode.uiCapability()->setUiReadOnly( true ); CAF_PDM_InitFieldNoDefault( &m_activeFormationNames, "DefaultFormationNames", "Formation Names" ); 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_ensembleCase, "EnsembleCase", "Ensemble Grid" ); m_ensembleCase = nullptr; CAF_PDM_InitScriptableFieldNoDefault( &m_dataFilterCollection, "DataFilterCollection", "Data Filters" ); m_dataFilterCollection = new RimDataFilterCollection; 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 ); } //-------------------------------------------------------------------------------------------------- /// Defined here (rather than defaulted in the header) so that the cvf::ref member is /// destroyed where RigMainGrid is a complete type. //-------------------------------------------------------------------------------------------------- RimReservoirGridEnsemble::~RimReservoirGridEnsemble() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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 ) { CAF_ASSERT( reservoir ); if ( m_mainGrid.isNull() && reservoir->eclipseCaseData() ) { m_mainGrid = reservoir->eclipseCaseData()->mainGrid(); } else if ( hasSharedGrid() && reservoir->eclipseCaseData() ) { // Share the main grid for identical grids reservoir->eclipseCaseData()->setMainGrid( m_mainGrid.p() ); } m_caseCollection()->reservoirs().push_back( reservoir ); clearActiveCellUnions(); clearStatisticsResults(); updateMainGridAndActiveCellsForStatisticsCases(); updateDataFilterCollectionCase(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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(); updateDataFilterCollectionCase(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimReservoirGridEnsemble::contains( RimEclipseCase* reservoir ) const { CAF_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 gridMode() == GridModeType::SHARED_GRID; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimReservoirGridEnsemble::isGridDataLoaded() const { return m_mainGrid.notNull(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimReservoirGridEnsembleBase::GridModeType RimReservoirGridEnsemble::gridMode() const { if ( m_autoDetectGridType ) return m_detectedGridMode.v(); return m_gridMode.v(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimReservoirGridEnsemble::ensembleName() const { return RimNamedObject::name(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigMainGrid* RimReservoirGridEnsemble::mainGrid() { // Trigger deferred loading if needed if ( m_mainGrid.isNull() && !cases().empty() ) { loadGridDataFromFiles(); } return m_mainGrid.p(); } //-------------------------------------------------------------------------------------------------- /// True when the two grids have the same IJK dimensions, which is what makes it safe to use one of them /// in place of the other. The total cell count also guards against a grid with LGRs. //-------------------------------------------------------------------------------------------------- bool RimReservoirGridEnsemble::hasMatchingDimensions( const RigMainGrid* lhs, const RigMainGrid* rhs ) { if ( !lhs || !rhs ) return false; return lhs->cellCountI() == rhs->cellCountI() && lhs->cellCountJ() == rhs->cellCountJ() && lhs->cellCountK() == rhs->cellCountK() && lhs->totalCellCount() == rhs->totalCellCount(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigMainGrid* RimReservoirGridEnsemble::shareOrAdoptMainGrid( RigMainGrid* candidate ) { if ( !candidate || !hasSharedGrid() ) return candidate; // The first realization to be opened provides the grid the rest of the ensemble shares. if ( m_mainGrid.isNull() ) { m_mainGrid = candidate; return candidate; } // The dimensions are checked when the ensemble is created, so this only triggers when the meta data // and the grid itself disagree. Let the realization keep its own grid rather than failing to open it. if ( !hasMatchingDimensions( m_mainGrid.p(), candidate ) ) { RiaLogging::warning( std::format( "Grid ensemble '{}': grid dimensions differ from the shared grid. " "Keeping a separate grid for this realization.", name().toStdString() ) ); return candidate; } return m_mainGrid.p(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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.p() ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigActiveCellInfo* RimReservoirGridEnsemble::unionOfActiveCells( RiaDefines::PorosityModelType porosityType ) { if ( porosityType == RiaDefines::PorosityModelType::MATRIX_MODEL ) { return m_unionOfMatrixActiveCells.p(); } else { return m_unionOfFractureActiveCells.p(); } } //-------------------------------------------------------------------------------------------------- /// Open every realization. Realizations can be opened lazily, and opening one can mean downloading a /// grid, so this is only called by the operations that genuinely read them all. //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::ensureAllCasesAreOpen() { auto allCases = cases(); std::vector casesToOpen; for ( auto* eclipseCase : allCases ) { if ( eclipseCase && !eclipseCase->eclipseCaseData() ) casesToOpen.push_back( eclipseCase ); } if ( casesToOpen.empty() ) return; caf::ProgressInfo progInfo( casesToOpen.size(), "Loading realizations" ); for ( auto* eclipseCase : casesToOpen ) { eclipseCase->openReservoirCase(); progInfo.incrementProgress(); } // More realizations contribute now, so any union computed earlier is incomplete. clearActiveCellUnions(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::computeUnionOfActiveCells() { if ( !hasSharedGrid() ) return; if ( m_unionOfMatrixActiveCells->reservoirActiveCellCount() > 0 ) { return; } // The union is only correct when every realization has been read, and the result is cached by the // check above. Realizations can be opened lazily, so make sure they are all open first. ensureAllCasesAreOpen(); if ( m_caseCollection->reservoirs.empty() || m_mainGrid.isNull() ) { clearActiveCellUnions(); return; } // Only realizations that are open and actually use the shared grid can contribute: a realization // whose dimensions did not match kept a grid of its own, and its cell indices mean nothing here. std::vector contributingCases; for ( auto* eclipseCase : m_caseCollection->reservoirs.childrenByType() ) { auto* caseData = eclipseCase ? eclipseCase->eclipseCaseData() : nullptr; if ( caseData && caseData->mainGrid() == m_mainGrid.p() ) contributingCases.push_back( caseData ); } if ( contributingCases.empty() ) { 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 ( auto* caseData : contributingCases ) { const auto* matrixActiveCells = caseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); const auto* fractureActiveCells = caseData->activeCellInfo( RiaDefines::PorosityModelType::FRACTURE_MODEL ); for ( size_t gridLocalCellIndex = 0; gridLocalCellIndex < grid->cellCount(); gridLocalCellIndex++ ) { size_t reservoirCellIndex = grid->reservoirCellIndex( gridLocalCellIndex ); if ( activeM[gridLocalCellIndex] == 0 && matrixActiveCells && matrixActiveCells->isActive( ReservoirCellIndex( reservoirCellIndex ) ) ) { activeM[gridLocalCellIndex] = 1; } if ( activeF[gridLocalCellIndex] == 0 && fractureActiveCells && fractureActiveCells->isActive( ReservoirCellIndex( 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( reservoirCellIndex ), ActiveCellIndex( globalActiveMatrixIndex++ ) ); activeMatrixIndex++; } if ( activeF[gridLocalCellIndex] != 0 ) { m_unionOfFractureActiveCells->setCellResultIndex( ReservoirCellIndex( reservoirCellIndex ), ActiveCellIndex( 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( std::format( "Cannot create statistics case for ensemble '{}': grids are not identical.", name() ) ); return nullptr; } return RimReservoirGridEnsembleBase::createAndAppendStatisticsCase(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::addView( RimEclipseView* view ) { m_viewCollection->addView( view ); view->dataFiltersInView()->setSourceCollection( m_dataFilterCollection() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimEclipseView* RimReservoirGridEnsemble::addViewForCase( RimEclipseCase* eclipseCase ) { auto* view = m_viewCollection->addView( eclipseCase ); view->dataFiltersInView()->setSourceCollection( m_dataFilterCollection() ); return view; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimEclipseViewCollection* RimReservoirGridEnsemble::viewCollection() const { return m_viewCollection; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimReservoirGridEnsemble::allViews() const { std::vector views; // Use childrenByType() and null checks here, as this function can be called while child objects // are being deleted during project close. PdmPointer entries in the child array fields are // nulled one by one as the objects are destroyed. if ( m_viewCollection ) { for ( auto view : m_viewCollection->views() ) { views.push_back( view ); } } if ( m_statisticsCaseCollection ) { for ( auto statCase : m_statisticsCaseCollection->reservoirs.childrenByType() ) { for ( auto view : statCase->reservoirViews() ) { views.push_back( view ); } } } for ( auto cmap : m_statisticsContourMaps.childrenByType() ) { for ( auto view : cmap->views() ) { views.push_back( view ); } } for ( auto wellTargetMapping : m_wellTargetMappings.childrenByType() ) { if ( !wellTargetMapping->ensembleStatisticsCase() ) continue; for ( auto view : wellTargetMapping->ensembleStatisticsCase()->reservoirViews() ) { 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->setName( QString( "Well Target Mapping #%1" ).arg( m_wellTargetMappings.size() ) ); 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() ) ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimReservoirGridEnsemble::statisticsContourMaps() const { return m_statisticsContourMaps.childrenByType(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::loadDataAndUpdate() { if ( !isGridDataLoaded() ) { loadGridDataFromFiles(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::createGridCasesFromEnsembleFileSet() { if ( !m_ensembleFileSet ) return; recreateCaseObjects(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::recreateCaseObjects() { // Clear existing cases and statistics m_caseCollection->reservoirs.deleteChildren(); m_statisticsCaseCollection->reservoirs.deleteChildren(); m_mainGrid = nullptr; clearActiveCellUnions(); // Create case objects without loading grids createCaseObjects(); updateDataFilterCollectionCase(); updateConnectedEditors(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimCaseCollection* RimReservoirGridEnsemble::caseCollection() const { return m_caseCollection; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const { menuBuilder << "RicNewViewForGridEnsembleFeature"; menuBuilder.addSeparator(); menuBuilder << "RicNewStatisticsContourMapFeature"; menuBuilder << "RicNewWellTargetMappingFeature"; if ( hasSharedGrid() ) { menuBuilder << "RicNewStatisticsCaseFeature"; } // The "Data Filters" collection node is hidden while empty, so expose the filter creation // commands on the ensemble node to allow creating the first filter. menuBuilder << "Separator"; menuBuilder.subMenuStart( "Data Filters", QIcon( ":/CellFilter.png" ) ); m_dataFilterCollection->appendMenuItems( menuBuilder ); menuBuilder.subMenuEnd(); menuBuilder.addSeparator(); menuBuilder << "RicNewEnsembleJobFeature"; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimFormationNames* RimReservoirGridEnsemble::activeFormationNames() const { if ( !hasSharedGrid() ) return nullptr; return m_activeFormationNames(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimDataFilterCollection* RimReservoirGridEnsemble::dataFilterCollection() const { return m_dataFilterCollection(); } //-------------------------------------------------------------------------------------------------- /// The filters in the collection need a source case for configuration (result meta data, grid /// geometry). Use the main case of the ensemble. Ensemble views evaluate the filters using their /// current case. //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::updateDataFilterCollectionCase() { if ( m_dataFilterCollection() ) m_dataFilterCollection->setCase( mainCase() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimReservoirGridEnsemble::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) { QList options; if ( fieldNeedingOptions == &m_ensembleFileSet ) { return RimEnsembleFileSetTools::ensembleFileSetOptions(); } if ( fieldNeedingOptions == &m_activeFormationNames ) { RimProject* proj = RimProject::current(); if ( proj && proj->activeOilField() && proj->activeOilField()->formationNamesCollection() ) { for ( RimFormationNames* fnames : proj->activeOilField()->formationNamesCollection()->formationNamesList() ) { options.push_back( caf::PdmOptionItemInfo( fnames->shortName(), fnames, false, fnames->uiCapability()->uiIconProvider() ) ); } } options.push_front( caf::PdmOptionItemInfo( "None", nullptr ) ); } return options; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { uiOrdering.add( nameField() ); uiOrdering.add( &m_groupId ); // A subclass can take the realizations from somewhere else than a file set, see RimReservoirGridEnsembleSumo. if ( m_ensembleFileSet ) uiOrdering.add( &m_ensembleFileSet ); uiOrdering.add( &m_autoDetectGridType ); if ( m_autoDetectGridType ) { uiOrdering.add( &m_detectedGridMode ); } else { uiOrdering.add( &m_gridMode ); } if ( hasSharedGrid() ) { uiOrdering.add( &m_activeFormationNames ); } uiOrdering.skipRemainingFields(); } //-------------------------------------------------------------------------------------------------- /// The 2D intersection views are owned by the case, but the views of an ensemble are located in the ensemble. /// Show the 2D intersection views next to the views they are created from. //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) { if ( uiConfigName != "MainWindow.ProjectTree" ) return; uiTreeOrdering.add( &m_caseCollection ); uiTreeOrdering.add( &m_statisticsCaseCollection ); if ( m_dataFilterCollection() && m_dataFilterCollection()->shouldBeVisibleInTree() ) { uiTreeOrdering.add( m_dataFilterCollection() ); } uiTreeOrdering.add( &m_viewCollection ); for ( auto eclipseCase : cases() ) { if ( !eclipseCase ) continue; auto intersectionViewCollection = eclipseCase->intersectionViewCollection(); if ( intersectionViewCollection && !intersectionViewCollection->views().empty() ) { uiTreeOrdering.add( intersectionViewCollection ); } } uiTreeOrdering.add( &m_wellTargetMappings ); uiTreeOrdering.add( &m_statisticsContourMaps ); uiTreeOrdering.skipRemainingChildren( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { if ( changedField == &m_autoDetectGridType || changedField == &m_gridMode ) { updateGridModeToolTip(); updateStatisticsVisibility(); updateConnectedEditors(); } else if ( changedField == &m_activeFormationNames ) { for ( auto rimCase : cases() ) { rimCase->updateFormationNamesData(); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::createDerivedObjects() { createCaseObjects(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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() ); } ); } // Set the case provider for views in the view collection if ( m_viewCollection ) { m_viewCollection->setEclipseCaseProvider( [this]() { return this->cases(); } ); } updateDataFilterCollectionCase(); updateStatisticsVisibility(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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::updateStatisticsVisibility() { bool hidden = !hasSharedGrid(); m_statisticsCaseCollection->uiCapability()->setUiTreeHidden( hidden ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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::createCaseObjects() { 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( std::format( "No grid files found for ensemble '{}'", 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 // Auto-import per-realization formation zone file (.lyr), if present next to the grid file. auto folderNames = RimFormationTools::formationFoldersFromCaseFileName( gridFile ); if ( RimFormationNames* formations = RimFormationTools::loadFormationNamesFromFolder( folderNames ) ) { resultCase->setFormationNames( formations ); } RimProject::current()->assignCaseIdToCase( resultCase ); m_caseCollection->reservoirs().push_back( resultCase ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::loadGridDataFromFiles() { // Guard: Only load once if ( m_mainGrid.notNull() ) return; auto allCases = cases(); if ( allCases.empty() ) return; // Determine effective grid mode if ( m_autoDetectGridType ) { bool identical = detectGridDimensionEquality(); m_detectedGridMode = identical ? GridModeType::SHARED_GRID : GridModeType::INDIVIDUAL_GRIDS; } GridModeType effectiveMode = gridMode(); // 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(); } updateGridModeToolTip(); updateStatisticsVisibility(); updateConnectedEditors(); // Re-initialize data filters now that the case data is loaded (needed for e.g. FORMATION_NAMES). See #14022. if ( m_dataFilterCollection() ) m_dataFilterCollection->loadAndInitializeFilters(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::reloadMetaDataIfNeeded() { if ( gridMode() == GridModeType::INDIVIDUAL_GRIDS ) return; m_mainGrid = nullptr; loadGridDataFromFiles(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::updateGridModeToolTip() { const bool individualGrids = ( gridMode() == GridModeType::INDIVIDUAL_GRIDS ); uiCapability()->setUiToolTip( individualGrids ? QString( "The realizations of this ensemble have grids with differing dimensions, " "so each realization keeps its own grid." ) : QString() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimReservoirGridEnsemble::treeItemTags() { std::vector tags; if ( gridMode() == GridModeType::INDIVIDUAL_GRIDS ) { auto tag = caf::PdmUiTreeViewItemTag::create(); tag.icon = caf::IconProvider( ":/info16x16.png" ); tags.push_back( tag ); } return tags; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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(); if ( allCases.empty() ) return; RiaLogging::info( std::format( "Grid ensemble '{}': Loading grid in shared mode for {} cases.", name(), allCases.size() ) ); // Load first case fully RimEclipseCase* firstCase = allCases[0]; if ( firstCase->openEclipseGridFile() && firstCase->eclipseCaseData() ) { 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() ); } // Reading of active cell data can fail, and no case data is created for the case if ( auto caseData = eclipseCase->eclipseCaseData() ) { caseData->setMainGrid( m_mainGrid.p() ); } else { RiaLogging::warning( std::format( "Grid ensemble '{}': Failed to load grid data for '{}'.", name(), eclipseCase->gridFileName() ) ); } } RigCaseCellResultsData::copyResultsMetaDataFromMainCase( firstCase->eclipseCaseData(), RiaDefines::PorosityModelType::MATRIX_MODEL, allCases ); computeUnionOfActiveCells(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimReservoirGridEnsemble::loadGridsInIndividualMode() { auto allCases = cases(); RiaLogging::info( std::format( "Grid ensemble '{}': Loading grids in individual mode for {} cases.", name(), 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(); } }