///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2024- 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 "RimStatisticsContourMap.h" #include "RiaLogging.h" #include "RiaPreferencesGrid.h" #include "RiaQStringFormatter.h" #include "RigStatisticsTools.h" #include "RicNewStatisticsContourMapViewFeature.h" #include "RifReaderSettings.h" #include "ContourMap/RigContourMapCalculator.h" #include "ContourMap/RigContourMapGrid.h" #include "ContourMap/RigEclipseContourMapProjection.h" #include "RigCaseCellResultsData.h" #include "RigEclipseCaseData.h" #include "RigEclipseResultAddress.h" #include "RigFormationNames.h" #include "RigMainGrid.h" #include "RigPolyLinesData.h" #include "RigStatisticsMath.h" #include "Formations/RimFormationNames.h" #include "Polygons/RimPolygon.h" #include "Polygons/RimPolygonCollection.h" #include "RimEclipseCase.h" #include "RimEclipseCaseEnsemble.h" #include "RimEclipseContourMapProjection.h" #include "RimEclipseResultCase.h" #include "RimEclipseResultDefinition.h" #include "RimReservoirGridEnsemble.h" #include "RimSimWellInViewCollection.h" #include "RimStatisticsContourMapProjection.h" #include "RimStatisticsContourMapView.h" #include "RimTools.h" #include "Riu3DMainWindowTools.h" #include "cafCmdFeatureMenuBuilder.h" #include "cafPdmUiButton.h" #include "cafPdmUiDoubleSliderEditor.h" #include "cafPdmUiTreeSelectionEditor.h" #include "cafProgressInfo.h" #include #include #include #include namespace { std::optional> findKLayersForFormations( RimEclipseCase* eCase, const std::vector& selectedFormations, RimFormationNames* fallbackFormationNames ) { if ( selectedFormations.empty() ) return std::set{}; auto formationNames = eCase->activeFormationNames(); if ( !formationNames ) formationNames = fallbackFormationNames; if ( !formationNames ) return std::nullopt; auto fData = formationNames->formationNamesData(); if ( !fData ) return std::nullopt; return fData->findKLayers( selectedFormations ); } void extractCaseResults( RigEclipseContourMapProjection& projection, const RigEclipseResultAddress& resultAddress, bool hasDynamicResult, RigContourMapCalculator::ResultAggregationType resultAggregation, RigFloodingSettings& floodSettings, const std::vector>& localToGlobalTimeSteps, std::map>>& timestepResults ) { if ( hasDynamicResult ) { for ( auto [localTs, globalTs] : localToGlobalTimeSteps ) { timestepResults[globalTs].push_back( projection.generateResults( resultAddress, resultAggregation, localTs, floodSettings ) ); } } else { timestepResults[0].push_back( projection.generateResults( resultAddress, resultAggregation, 0, floodSettings ) ); } } } // namespace CAF_PDM_SOURCE_INIT( RimStatisticsContourMap, "RimStatisticalContourMap" ); namespace caf { template <> void caf::AppEnum::setUp() { addItem( RimStatisticsContourMap::GridImportMode::SHARED_GRID, "SHARED_GRID", "Reuse Grid from First Realization" ); addItem( RimStatisticsContourMap::GridImportMode::INDIVIDUAL_GRIDS, "INDIVIDUAL_GRIDS", "Import All Grids" ); setDefault( RimStatisticsContourMap::GridImportMode::SHARED_GRID ); } template <> void caf::AppEnum::setUp() { addItem( RimStatisticsContourMap::StatisticsType::P10, "P10", "P10" ); addItem( RimStatisticsContourMap::StatisticsType::P50, "P50", "P50" ); addItem( RimStatisticsContourMap::StatisticsType::P90, "P90", "P90" ); addItem( RimStatisticsContourMap::StatisticsType::MEAN, "MEAN", "Mean" ); addItem( RimStatisticsContourMap::StatisticsType::MIN, "MIN", "Minimum" ); addItem( RimStatisticsContourMap::StatisticsType::MAX, "MAX", "Maximum" ); setDefault( RimStatisticsContourMap::StatisticsType::MEAN ); } }; // namespace caf //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimStatisticsContourMap::RimStatisticsContourMap() : m_openEclipseCase( nullptr ) { CAF_PDM_InitObject( "Ensemble Contour Map", ":/Histogram16x16.png" ); CAF_PDM_InitField( &m_boundingBoxExpPercent, "BoundingBoxExpPercent", 5.0, "Bounding Box Expansion (%)", "", "How much to increase the bounding box of the primary case to cover for any grid size differences across the " "ensemble." ); CAF_PDM_InitFieldNoDefault( &m_resolution, "Resolution", "Sampling Resolution" ); CAF_PDM_InitFieldNoDefault( &m_gridImportMode, "GridImportMode", "Grid Import Mode" ); CAF_PDM_InitFieldNoDefault( &m_resultAggregation, "ResultAggregation", "Result Aggregation" ); CAF_PDM_InitFieldNoDefault( &m_oilFloodingType, "OilFloodingType", "Residual Oil Given By" ); m_oilFloodingType.setValue( RigFloodingSettings::FloodingType::WATER_FLOODING ); CAF_PDM_InitField( &m_userDefinedFloodingOil, "UserDefinedFloodingOil", 0.0, "" ); m_userDefinedFloodingOil.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() ); CAF_PDM_InitField( &m_gasFloodingType, "GasFloodingType", RigFloodingSettings::FloodingType::GAS_FLOODING, "Residual Gas Given By" ); caf::AppEnum::setEnumSubset( &m_gasFloodingType, { RigFloodingSettings::FloodingType::GAS_FLOODING, RigFloodingSettings::FloodingType::USER_DEFINED } ); CAF_PDM_InitField( &m_userDefinedFloodingGas, "UserDefinedFloodingGas", 0.0, "" ); m_userDefinedFloodingGas.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() ); CAF_PDM_InitFieldNoDefault( &m_selectedTimeSteps, "SelectedTimeSteps", "Time Step Selection" ); m_selectedTimeSteps.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); CAF_PDM_InitFieldNoDefault( &m_resultDefinition, "ResultDefinition", "" ); m_resultDefinition.uiCapability()->setUiTreeChildrenHidden( true ); m_resultDefinition = new RimEclipseResultDefinition; m_resultDefinition->findField( "MResultType" )->uiCapability()->setUiName( "Result" ); m_resultDefinition->setResultType( RiaDefines::ResultCatType::DYNAMIC_NATIVE ); m_resultDefinition->setResultVariable( "SOIL" ); CAF_PDM_InitFieldNoDefault( &m_primaryCase, "PrimaryEclipseCase", "Primary Case", "", "Eclipse Case used for wells and faults shown in views, initializing available result list, timesteps, " "etc." ); CAF_PDM_InitFieldNoDefault( &m_views, "ContourMapViews", "Contour Maps", ":/CrossSection16x16.png" ); CAF_PDM_InitField( &m_enableFormationFilter, "EnableFormationFilter", false, "Enable Formation Filter" ); CAF_PDM_InitFieldNoDefault( &m_selectedFormations, "Formations", "Select Formations" ); m_selectedFormations.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); m_selectedFormations.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::TOP ); CAF_PDM_InitFieldNoDefault( &m_selectedPolygons, "Polygons", "Select Polygons" ); m_selectedPolygons.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); m_selectedPolygons.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::TOP ); setDeletable( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { if ( ( eclipseCase() == nullptr ) && ( !ensembleCases().empty() ) ) { auto selCase = ensembleCases().front(); setEclipseCase( selCase ); } bool computeOK = !( m_enableFormationFilter && m_selectedFormations().empty() ); computeOK = computeOK && !selectedTimeSteps().empty(); uiOrdering.add( nameField() ); { auto* btn = uiOrdering.addNewButton( "Compute", [this]() { onComputeStatisticsClicked(); } ); btn->setUiToolTip( computeOK ? "Start statistics computations." : "Please check your time step and/or formation filter selections." ); btn->setUiReadOnly( !computeOK ); } auto genGrp = uiOrdering.addNewGroup( "General" ); genGrp->add( &m_resultAggregation ); if ( RigContourMapCalculator::isMobileColumnResult( m_resultAggregation() ) ) { if ( m_resultAggregation() != RigContourMapCalculator::MOBILE_GAS_COLUMN ) { genGrp->add( &m_oilFloodingType ); if ( m_oilFloodingType() == RigFloodingSettings::FloodingType::USER_DEFINED ) { genGrp->add( &m_userDefinedFloodingOil ); } } if ( m_resultAggregation() != RigContourMapCalculator::MOBILE_OIL_COLUMN ) { genGrp->add( &m_gasFloodingType ); if ( m_gasFloodingType() == RigFloodingSettings::FloodingType::USER_DEFINED ) { genGrp->add( &m_userDefinedFloodingGas ); } } } genGrp->add( &m_resolution ); if ( auto* gridEnsemble = firstAncestorOrThisOfType() ) { if ( gridEnsemble->gridMode() == RimReservoirGridEnsembleBase::GridModeType::SHARED_GRID ) genGrp->add( &m_gridImportMode ); } genGrp->add( &m_primaryCase ); genGrp->add( &m_boundingBoxExpPercent ); auto tsGroup = uiOrdering.addNewGroup( "Time Step Selection" ); tsGroup->setCollapsedByDefault(); tsGroup->add( &m_selectedTimeSteps ); if ( activeFormationNames() ) { auto formationGrp = uiOrdering.addNewGroup( "Formation Selection" ); if ( !m_enableFormationFilter ) formationGrp->setCollapsedByDefault(); formationGrp->add( &m_enableFormationFilter ); if ( m_enableFormationFilter ) formationGrp->add( &m_selectedFormations ); } if ( auto polygonCollection = RimTools::polygonCollection() ) { if ( !polygonCollection->allPolygons().empty() ) { auto polyGrp = uiOrdering.addNewGroup( "Polygon Selection" ); polyGrp->setCollapsedByDefault(); polyGrp->add( &m_selectedPolygons ); } } if ( !isColumnResult() ) { auto resultDefinitionGroup = uiOrdering.addNewGroup( "Result Definition" ); m_resultDefinition->uiOrdering( uiConfigName, *resultDefinitionGroup ); } uiOrdering.skipRemainingFields(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::setEclipseCase( RimEclipseCase* eCase ) { m_resultDefinition->setEclipseCase( eCase ); m_primaryCase = eCase; if ( eCase != nullptr ) { if ( m_selectedTimeSteps().empty() ) { int nSteps = (int)eCase->timeStepStrings().size(); if ( nSteps > 0 ) { m_selectedTimeSteps.setValue( { nSteps - 1 } ); } } } for ( auto& view : m_views ) { view->setEclipseCase( eCase ); } m_resultDefinition->updateConnectedEditors(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::setGridImportMode( GridImportMode mode ) { m_gridImportMode = mode; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimStatisticsContourMap::ensembleName() const { if ( auto* ens = firstAncestorOrThisOfType() ) return ens->ensembleName(); return {}; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimFormationNames* RimStatisticsContourMap::activeFormationNames() const { if ( auto* gridCase = eclipseCase() ) { if ( auto* formationNames = gridCase->activeFormationNames() ) return formationNames; } if ( auto* ensemble = firstAncestorOrThisOfType() ) { return ensemble->activeFormationNames(); } return nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimStatisticsContourMap::ensembleCases() const { if ( auto* ens = firstAncestorOrThisOfType() ) return ens->sourceCases(); return {}; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::set RimStatisticsContourMap::ensembleCasesInViews() const { if ( auto* ens = firstAncestorOrThisOfType() ) return ens->casesInViews(); return {}; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { if ( &m_primaryCase == changedField ) { switchToSelectedSourceCase(); // Update well views as wells might have changed from last case for ( auto& view : m_views ) { view->wellCollection()->wells.deleteChildren(); view->updateDisplayModelForWellResults(); view->wellCollection()->updateConnectedEditors(); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::switchToSelectedSourceCase() { auto newCase = eclipseCase(); if ( newCase == nullptr ) return; if ( m_openEclipseCase != newCase ) { newCase->ensureReservoirCaseIsOpen(); if ( m_openEclipseCase && !ensembleCasesInViews().contains( m_openEclipseCase ) ) { m_openEclipseCase->closeReservoirCase(); } m_openEclipseCase = newCase; setEclipseCase( newCase ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimStatisticsContourMap::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) { QList options; if ( &m_selectedTimeSteps == fieldNeedingOptions ) { if ( auto eCase = eclipseCase() ) { const auto timeStepStrings = eCase->timeStepStrings(); int index = 0; for ( const auto& text : timeStepStrings ) { options.push_back( caf::PdmOptionItemInfo( text, index++ ) ); } } return options; } else if ( &m_primaryCase == fieldNeedingOptions ) { for ( auto eCase : ensembleCases() ) { options.push_back( caf::PdmOptionItemInfo( eCase->caseUserDescription(), eCase, false, eCase->uiIconProvider() ) ); } return options; } else if ( &m_selectedFormations == fieldNeedingOptions ) { if ( auto formations = activeFormationNames() ) { if ( formations->formationNamesData() ) { for ( auto& f : formations->formationNamesData()->formationNames() ) { options.push_back( caf::PdmOptionItemInfo( f, f, false ) ); } } } } else if ( &m_selectedPolygons == fieldNeedingOptions ) { if ( auto polygonCollection = RimTools::polygonCollection() ) { for ( auto p : polygonCollection->allPolygons() ) { options.push_back( caf::PdmOptionItemInfo( p->name(), p, false ) ); } } } return options; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) { if ( ( &m_userDefinedFloodingOil == field ) || ( &m_userDefinedFloodingGas == field ) ) { if ( auto myAttr = dynamic_cast( attribute ) ) { myAttr->m_minimum = 0.0; myAttr->m_maximum = 1.0; myAttr->m_sliderTickCount = 20; myAttr->m_delaySliderUpdateUntilRelease = true; } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::initAfterRead() { if ( ensembleCases().empty() ) return; switchToSelectedSourceCase(); for ( auto view : m_views.childrenByType() ) { view->loadDataAndUpdate(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::doStatisticsCalculation( TimestepResultsMap& timestepResults ) { m_timeResults.clear(); for ( const auto& [timeStep, res] : timestepResults ) { if ( res.empty() ) continue; int nCells = static_cast( res[0].size() ); std::vector p10Results( nCells, std::numeric_limits::infinity() ); std::vector p50Results( nCells, std::numeric_limits::infinity() ); std::vector p90Results( nCells, std::numeric_limits::infinity() ); std::vector meanResults( nCells, std::numeric_limits::infinity() ); std::vector minResults( nCells, std::numeric_limits::infinity() ); std::vector maxResults( nCells, std::numeric_limits::infinity() ); const size_t numSamples = res.size(); // Clang version 16.0.6 does not handle OpenMP here, the compiler crashes. #ifndef __clang__ #pragma omp parallel for #endif for ( int i = 0; i < nCells; i++ ) { std::vector samples( numSamples, 0.0 ); for ( size_t s = 0; s < numSamples; s++ ) { samples[s] = res[s][i]; } double p10 = std::numeric_limits::infinity(); double p50 = std::numeric_limits::infinity(); double p90 = std::numeric_limits::infinity(); double mean = std::numeric_limits::infinity(); RigStatisticsMath::calculateStatisticsCurves( samples, &p10, &p50, &p90, &mean, RigStatisticsMath::PercentileStyle::SWITCHED ); if ( RigStatisticsTools::isValidNumber( p10 ) ) p10Results[i] = p10; if ( RigStatisticsTools::isValidNumber( p50 ) ) p50Results[i] = p50; if ( RigStatisticsTools::isValidNumber( p90 ) ) p90Results[i] = p90; if ( RigStatisticsTools::isValidNumber( mean ) ) meanResults[i] = mean; double minValue = RigStatisticsTools::minimumValue( samples ); if ( RigStatisticsTools::isValidNumber( minValue ) && minValue < std::numeric_limits::max() ) minResults[i] = minValue; double maxValue = RigStatisticsTools::maximumValue( samples ); if ( RigStatisticsTools::isValidNumber( maxValue ) && maxValue > -std::numeric_limits::max() ) maxResults[i] = maxValue; } m_timeResults[timeStep][StatisticsType::P10] = p10Results; m_timeResults[timeStep][StatisticsType::P50] = p50Results; m_timeResults[timeStep][StatisticsType::P90] = p90Results; m_timeResults[timeStep][StatisticsType::MEAN] = meanResults; m_timeResults[timeStep][StatisticsType::MIN] = minResults; m_timeResults[timeStep][StatisticsType::MAX] = maxResults; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::onComputeStatisticsClicked() { computeStatistics(); if ( m_views.empty() ) { auto view = RicNewStatisticsContourMapViewFeature::createAndAddView( this ); updateConnectedEditors(); Riu3DMainWindowTools::selectAsCurrentItem( view ); Riu3DMainWindowTools::setExpanded( this ); Riu3DMainWindowTools::setExpanded( view ); } else { for ( auto& view : m_views ) { auto proj = dynamic_cast( view->contourMapProjection() ); if ( proj != nullptr ) proj->clearGridMappingAndRedraw(); else view->scheduleCreateDisplayModelAndRedraw(); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::computeStatistics() { computeStatisticsForMaps( { this } ); } cvf::BoundingBox gridBoundingBox = eclipseCase()->activeCellsBoundingBox(); gridBoundingBox.expandPercent( m_boundingBoxExpPercent(), m_boundingBoxExpPercent() ); //-------------------------------------------------------------------------------------------------- /// Compute statistics for several contour maps in one sweep over the ensemble realizations, so that /// each realization is opened once instead of once per contour map. All maps must belong to the /// same ensemble. //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::computeStatisticsForMaps( const std::vector& maps ) { struct MapContext { explicit MapContext( RimStatisticsContourMap* m ) : map( m ) , floodSettings( m->m_oilFloodingType(), m->m_userDefinedFloodingOil(), m->m_gasFloodingType(), m->m_userDefinedFloodingGas() ) , resultAggregation( m->m_resultAggregation() ) { } RimStatisticsContourMap* map; RigFloodingSettings floodSettings; RigContourMapCalculator::ResultAggregationType resultAggregation; std::unique_ptr contourMapGrid; TimestepResultsMap timestepResults; bool useSharedGrid = false; std::unique_ptr sharedProjection; std::set kLayers; bool active = false; }; auto readerSettings = RiaPreferencesGrid::gridOnlyReaderSettings(); readerSettings.onlyLoadActiveCells = true; auto oldReaderType = RiaPreferencesGrid::current()->gridModelReaderOverride(); RiaPreferencesGrid::current()->setGridModelReaderOverride( RiaDefines::GridModelReader::OPM_COMMON ); std::map primaryOldSettings; std::vector contexts; std::set uniqueMaps; for ( RimStatisticsContourMap* map : maps ) { if ( map == nullptr || !uniqueMaps.insert( map ).second ) continue; if ( map->ensembleCases().empty() ) continue; RimEclipseCase* primaryCase = map->eclipseCase(); if ( primaryCase == nullptr ) continue; if ( !primaryOldSettings.contains( primaryCase ) ) { primaryOldSettings[primaryCase] = primaryCase->readerSettings(); primaryCase->setReaderSettings( readerSettings ); } // A sibling map can be computed before its own initAfterRead() has run, and then the result // definition has no case to resolve the result address against, producing empty results if ( map->m_resultDefinition->eclipseCase() == nullptr ) map->m_resultDefinition->setEclipseCase( primaryCase ); MapContext ctx( map ); auto gridEnsemble = map->firstAncestorOrThisOfType(); ctx.useSharedGrid = gridEnsemble && gridEnsemble->gridMode() == RimReservoirGridEnsembleBase::GridModeType::SHARED_GRID && map->m_gridImportMode() == GridImportMode::SHARED_GRID; ctx.active = primaryCase->ensureReservoirCaseIsOpen(); // The bounding box is empty unless the primary case is open with active cell info cvf::BoundingBox gridBoundingBox = primaryCase->activeCellsBoundingBox(); gridBoundingBox.expandPercent( map->m_boundingBoxExpPercent() ); double sampleSpacing = 1.0; if ( auto mainGrid = primaryCase->mainGrid() ) sampleSpacing = map->sampleSpacingFactor() * mainGrid->characteristicIJCellSize(); ctx.contourMapGrid = std::make_unique( gridBoundingBox, sampleSpacing ); if ( ctx.active ) { if ( ctx.useSharedGrid ) { if ( auto kLayers = findKLayersForFormations( primaryCase, map->selectedFormations(), map->activeFormationNames() ) ) { ctx.kLayers = *kLayers; auto primaryCaseData = primaryCase->eclipseCaseData(); auto primaryResultData = primaryCaseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); ctx.sharedProjection = std::make_unique( ctx.contourMapGrid.get(), primaryCaseData, primaryResultData ); ctx.sharedProjection->generateGridMapping( ctx.resultAggregation, {}, ctx.kLayers, map->selectedPolygons() ); } else { RiaLogging::warning( "Formation names are missing for primary case, skipping statistics computation." ); ctx.active = false; } } } contexts.push_back( std::move( ctx ) ); } const bool anyActive = std::any_of( contexts.begin(), contexts.end(), []( const MapContext& ctx ) { return ctx.active; } ); if ( anyActive ) { RiaLogging::info( std::format( "Computing statistics for {} ensemble contour map(s)", contexts.size() ) ); // All maps belong to the same ensemble, so the realization cases are shared auto cases = contexts.front().map->ensembleCases(); auto casesInViews = contexts.front().map->ensembleCasesInViews(); std::set primaryCases; for ( const auto& ctx : contexts ) primaryCases.insert( ctx.map->eclipseCase() ); const size_t nCases = cases.size(); caf::ProgressInfo progInfo( nCases, QString( "Reading Eclipse Ensemble" ) ); int i = 1; // The key point of this loop is that each realization is opened once, contributes to all pending contour maps, // and is then closed again to release memory. for ( RimEclipseCase* eCase : cases ) { auto task = progInfo.task( QString( "Processing Case %1 of %2" ).arg( i++ ).arg( nCases ) ); RifReaderSettings oldSettings = eCase->readerSettings(); eCase->setReaderSettings( readerSettings ); if ( eCase->ensureReservoirCaseIsOpen() ) { RiaLogging::info( std::format( "Processing Grid: {}", eCase->caseUserDescription() ) ); auto eclipseCaseData = eCase->eclipseCaseData(); auto activeCellInfo = eclipseCaseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); auto resultData = eclipseCaseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); for ( auto& ctx : contexts ) { if ( !ctx.active ) continue; RimStatisticsContourMap* map = ctx.map; auto localToGlobalTimeSteps = map->mapLocalToGlobalTimeSteps( eCase->timeStepDates() ); if ( ctx.useSharedGrid ) { ctx.sharedProjection->updateRealizationData( activeCellInfo, resultData ); extractCaseResults( *ctx.sharedProjection, map->m_resultDefinition()->eclipseResultAddress(), map->m_resultDefinition()->hasDynamicResult(), ctx.resultAggregation, ctx.floodSettings, localToGlobalTimeSteps, ctx.timestepResults ); } else { if ( auto kLayers = findKLayersForFormations( eCase, map->selectedFormations(), map->activeFormationNames() ) ) { RigEclipseContourMapProjection contourMapProjection( ctx.contourMapGrid.get(), eclipseCaseData, resultData ); contourMapProjection.generateGridMapping( ctx.resultAggregation, {}, *kLayers, map->selectedPolygons() ); extractCaseResults( contourMapProjection, map->m_resultDefinition()->eclipseResultAddress(), map->m_resultDefinition()->hasDynamicResult(), ctx.resultAggregation, ctx.floodSettings, localToGlobalTimeSteps, ctx.timestepResults ); } else { RiaLogging::warning( std::format( "Formation names are missing for case {}, skipping case.", eCase->caseUserDescription() ) ); } } } } eCase->setReaderSettings( oldSettings ); // Release the grid data for cases that were opened only to compute statistics. A case is kept open if it has // its own views, if it is the primary case of one of the contour maps, or if it is displayed in one of the // ensemble views. if ( eCase->views().empty() && !primaryCases.contains( eCase ) && !casesInViews.contains( eCase ) ) { eCase->closeReservoirCase(); } } } for ( auto& [primaryCase, settings] : primaryOldSettings ) primaryCase->setReaderSettings( settings ); RiaPreferencesGrid::current()->setGridModelReaderOverride( oldReaderType ); for ( auto& ctx : contexts ) { ctx.map->m_contourMapGrid = std::move( ctx.contourMapGrid ); ctx.map->doStatisticsCalculation( ctx.timestepResults ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimEclipseCase* RimStatisticsContourMap::eclipseCase() const { return m_primaryCase(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const { menuBuilder << "RicNewStatisticsContourMapViewFeature"; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigContourMapGrid* RimStatisticsContourMap::contourMapGrid() const { return m_contourMapGrid.get(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimStatisticsContourMap::result( size_t timeStep, StatisticsType statisticsType ) const { auto realTimeSteps = selectedTimeSteps(); if ( timeStep >= realTimeSteps.size() ) return {}; timeStep = (size_t)realTimeSteps[timeStep]; if ( !m_timeResults.contains( timeStep ) ) return {}; if ( !m_timeResults.at( timeStep ).contains( statisticsType ) ) return {}; return m_timeResults.at( timeStep ).at( statisticsType ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimStatisticsContourMap::selectedTimeSteps() const { if ( !m_resultDefinition->hasDynamicResult() ) { return { 0 }; } auto steps = m_selectedTimeSteps(); std::sort( steps.begin(), steps.end() ); return steps; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimStatisticsContourMap::selectedTimeStepDates() const { std::vector retDates; auto eCase = eclipseCase(); if ( eCase != nullptr ) { auto allDates = eCase->timeStepDates(); for ( auto i : selectedTimeSteps() ) { if ( i < (int)allDates.size() ) retDates.push_back( allDates[i] ); } } return retDates; } //-------------------------------------------------------------------------------------------------- /// returns pair of (local date index, matching global date index) //-------------------------------------------------------------------------------------------------- std::vector> RimStatisticsContourMap::mapLocalToGlobalTimeSteps( std::vector localDates ) const { std::vector> indexSubset; auto globalDates = selectedTimeStepDates(); auto globalIndexes = selectedTimeSteps(); for ( int i = 0; i < (int)localDates.size(); i++ ) { auto pos = std::find( globalDates.begin(), globalDates.end(), localDates[i] ); if ( pos == globalDates.end() ) continue; int foundIdx = (int)( pos - globalDates.begin() ); indexSubset.emplace_back( i, globalIndexes[foundIdx] ); } return indexSubset; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimStatisticsContourMap::selectedFormations() const { if ( !m_enableFormationFilter ) return {}; return m_selectedFormations(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector> RimStatisticsContourMap::selectedPolygons() { std::vector> allLines; for ( auto p : m_selectedPolygons() ) { auto pData = p->polyLinesData(); if ( pData.isNull() ) continue; const std::vector> lines = pData->completePolyLines(); for ( auto l : lines ) { allLines.push_back( l ); } } return allLines; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimStatisticsContourMap::timeStepName( int timeStep ) const { if ( eclipseCase() == nullptr ) return ""; if ( ( timeStep < 0 ) || ( timeStep >= eclipseCase()->timeStepStrings().size() ) ) return ""; return eclipseCase()->timeStepName( timeStep ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::ensureResultsComputed() { // The contour map grid is not stored in the project file, and is used as the flag telling if statistics have been // computed in this session. It is created by computeStatisticsForMaps(), and is never cleared. // Use the Compute button to force a recomputation after changing settings. if ( m_contourMapGrid ) return; // Compute all pending sibling contour maps in the same sweep over the ensemble realizations, so // that each realization is opened once instead of once per contour map std::vector maps = { this }; if ( auto ensemble = firstAncestorOrThisOfType() ) { for ( auto sibling : ensemble->statisticsContourMaps() ) { if ( sibling != this && !sibling->m_contourMapGrid && !sibling->views().empty() ) maps.push_back( sibling ); } } computeStatisticsForMaps( maps ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimStatisticsContourMap::resultAggregationText() const { return m_resultAggregation().uiText(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimStatisticsContourMap::resultVariable() const { if ( m_resultDefinition().isNull() ) return ""; return m_resultDefinition()->resultVariable(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimStatisticsContourMap::isColumnResult() const { return RigContourMapCalculator::isColumnResult( m_resultAggregation() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RimStatisticsContourMap::sampleSpacingFactor() const { return RimContourMapResolutionTools::resolutionFromEnumValue( m_resolution() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimStatisticsContourMap::views() const { return m_views.childrenByType(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimStatisticsContourMap::addView( RimStatisticsContourMapView* view ) { // make sure to update the other views as the calculated data might have changed for ( auto view : m_views ) { view->scheduleCreateDisplayModelAndRedraw(); } m_views.push_back( view ); }