Files
ResInsight/ApplicationLibCode/ProjectDataModel/ContourMap/RimStatisticsContourMap.cpp
T
JJ 75ebd7f20b Well target updates (#14619)
Support well target mapping calculations for ensembles.
2026-08-28 13:36:02 +02:00

1346 lines
57 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// 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 <http://www.gnu.org/licenses/gpl.html>
// 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 "RifSurfio.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 <QCryptographicHash>
#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QRegularExpression>
#include <QUuid>
#include <algorithm>
#include <cmath>
#include <limits>
#include <optional>
#include <set>
namespace
{
std::optional<std::set<int>>
findKLayersForFormations( RimEclipseCase* eCase, const std::vector<QString>& selectedFormations, RimFormationNames* fallbackFormationNames )
{
if ( selectedFormations.empty() ) return std::set<int>{};
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<std::pair<int, int>>& localToGlobalTimeSteps,
std::map<size_t, std::vector<std::vector<double>>>& 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<RimStatisticsContourMap::GridImportMode>::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<RimStatisticsContourMap::StatisticsType>::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<RigFloodingSettings::FloodingType>::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 );
CAF_PDM_InitField( &m_cacheFileBaseName, "CacheFileBaseName", QString(), "Cache File Base Name" );
m_cacheFileBaseName.uiCapability()->setUiHidden( true );
CAF_PDM_InitField( &m_cacheValidityKey, "CacheValidityKey", QString(), "Cache Validity Key" );
m_cacheValidityKey.uiCapability()->setUiHidden( true );
CAF_PDM_InitFieldNoDefault( &m_cacheTimeSteps, "CacheTimeSteps", "Cache Time Steps" );
m_cacheTimeSteps.uiCapability()->setUiHidden( true );
CAF_PDM_InitField( &m_cacheSampleSpacing, "CacheSampleSpacing", 0.0, "Cache Sample Spacing" );
m_cacheSampleSpacing.uiCapability()->setUiHidden( true );
CAF_PDM_InitFieldNoDefault( &m_cacheOriginalBoundingBox, "CacheOriginalBoundingBox", "Cache Original Bounding Box" );
m_cacheOriginalBoundingBox.uiCapability()->setUiHidden( true );
CAF_PDM_InitFieldNoDefault( &m_cacheExpandedBoundingBox, "CacheExpandedBoundingBox", "Cache Expanded Bounding Box" );
m_cacheExpandedBoundingBox.uiCapability()->setUiHidden( true );
CAF_PDM_InitFieldNoDefault( &m_cacheMapSize, "CacheMapSize", "Cache Map Size" );
m_cacheMapSize.uiCapability()->setUiHidden( true );
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<RimReservoirGridEnsembleBase>() )
{
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<RimReservoirGridEnsembleBase>() ) return ens->ensembleName();
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimFormationNames* RimStatisticsContourMap::activeFormationNames() const
{
if ( auto* gridCase = eclipseCase() )
{
if ( auto* formationNames = gridCase->activeFormationNames() ) return formationNames;
}
if ( auto* ensemble = firstAncestorOrThisOfType<RimReservoirGridEnsembleBase>() )
{
return ensemble->activeFormationNames();
}
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimEclipseCase*> RimStatisticsContourMap::ensembleCases() const
{
if ( auto* ens = firstAncestorOrThisOfType<RimReservoirGridEnsembleBase>() ) return ens->sourceCases();
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::set<RimEclipseCase*> RimStatisticsContourMap::ensembleCasesInViews() const
{
if ( auto* ens = firstAncestorOrThisOfType<RimReservoirGridEnsembleBase>() ) 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<caf::PdmOptionItemInfo> RimStatisticsContourMap::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
QList<caf::PdmOptionItemInfo> 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<caf::PdmUiDoubleSliderEditorAttribute*>( 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<int>( res[0].size() );
std::vector<double> p10Results( nCells, std::numeric_limits<double>::infinity() );
std::vector<double> p50Results( nCells, std::numeric_limits<double>::infinity() );
std::vector<double> p90Results( nCells, std::numeric_limits<double>::infinity() );
std::vector<double> meanResults( nCells, std::numeric_limits<double>::infinity() );
std::vector<double> minResults( nCells, std::numeric_limits<double>::infinity() );
std::vector<double> maxResults( nCells, std::numeric_limits<double>::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<double> samples( numSamples, 0.0 );
for ( size_t s = 0; s < numSamples; s++ )
{
samples[s] = res[s][i];
}
double p10 = std::numeric_limits<double>::infinity();
double p50 = std::numeric_limits<double>::infinity();
double p90 = std::numeric_limits<double>::infinity();
double mean = std::numeric_limits<double>::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<double>::max() ) minResults[i] = minValue;
double maxValue = RigStatisticsTools::maximumValue( samples );
if ( RigStatisticsTools::isValidNumber( maxValue ) && maxValue > -std::numeric_limits<double>::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<RimStatisticsContourMapProjection*>( view->contourMapProjection() );
if ( proj != nullptr )
proj->clearGridMappingAndRedraw();
else
view->scheduleCreateDisplayModelAndRedraw();
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStatisticsContourMap::computeStatistics()
{
computeStatisticsForMaps( { this } );
if ( auto ensemble = firstAncestorOrThisOfType<RimReservoirGridEnsemble>() )
{
ensemble->reloadMetaDataIfNeeded();
}
}
//--------------------------------------------------------------------------------------------------
/// 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<RimStatisticsContourMap*>& 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<RigContourMapGrid> contourMapGrid;
TimestepResultsMap timestepResults;
bool useSharedGrid = false;
std::unique_ptr<RigEclipseContourMapProjection> sharedProjection;
std::set<int> 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<RimEclipseCase*, RifReaderSettings> primaryOldSettings;
std::vector<MapContext> contexts;
std::set<RimStatisticsContourMap*> 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<RimReservoirGridEnsembleBase>();
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(), map->m_boundingBoxExpPercent() );
double sampleSpacing = 1.0;
if ( auto mainGrid = primaryCase->mainGrid() ) sampleSpacing = map->sampleSpacingFactor() * mainGrid->characteristicIJCellSize();
ctx.contourMapGrid = std::make_unique<RigContourMapGrid>( 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<RigEclipseContourMapProjection>( 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();
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 ) );
bool closeCase = !eCase->isReservoirCaseOpen();
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 ( closeCase )
{
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 );
ctx.map->m_computedValidityKey = ctx.active ? ctx.map->computeCacheValidityKey() : QString();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
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<double> 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<int> RimStatisticsContourMap::selectedTimeSteps() const
{
if ( !m_resultDefinition->hasDynamicResult() )
{
return { 0 };
}
auto steps = m_selectedTimeSteps();
std::sort( steps.begin(), steps.end() );
return steps;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<QDateTime> RimStatisticsContourMap::selectedTimeStepDates() const
{
std::vector<QDateTime> 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<std::pair<int, int>> RimStatisticsContourMap::mapLocalToGlobalTimeSteps( std::vector<QDateTime> localDates ) const
{
std::vector<std::pair<int, int>> 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<QString> RimStatisticsContourMap::selectedFormations() const
{
if ( !m_enableFormationFilter ) return {};
return m_selectedFormations();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::vector<cvf::Vec3d>> RimStatisticsContourMap::selectedPolygons() const
{
std::vector<std::vector<cvf::Vec3d>> allLines;
for ( auto p : m_selectedPolygons.ptrReferencedObjectsByType() )
{
auto pData = p->polyLinesData();
if ( pData.isNull() ) continue;
const std::vector<std::vector<cvf::Vec3d>> 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;
if ( loadCachedResults() ) 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<RimStatisticsContourMap*> maps = { this };
if ( auto ensemble = firstAncestorOrThisOfType<RimReservoirGridEnsembleBase>() )
{
for ( auto sibling : ensemble->statisticsContourMaps() )
{
if ( sibling != this && !sibling->m_contourMapGrid && !sibling->views().empty() && !sibling->loadCachedResults() )
maps.push_back( sibling );
}
}
computeStatisticsForMaps( maps );
// contour map calculations on shared grids clears the ensemble meta data, reload it
if ( auto ensemble = firstAncestorOrThisOfType<RimReservoirGridEnsemble>() )
{
ensemble->reloadMetaDataIfNeeded();
}
}
//--------------------------------------------------------------------------------------------------
/// Hash of all settings and input grid files that affect the computed statistics. Cached results are only reused when
/// the stored key matches the key computed from the current settings.
//--------------------------------------------------------------------------------------------------
QString RimStatisticsContourMap::computeCacheValidityKey() const
{
QStringList parts;
parts << m_resultAggregation().text();
parts << m_resolution().text();
parts << m_gridImportMode().text();
parts << QString::number( m_boundingBoxExpPercent(), 'g', 12 );
parts << m_oilFloodingType().text() << QString::number( m_userDefinedFloodingOil(), 'g', 12 );
parts << m_gasFloodingType().text() << QString::number( m_userDefinedFloodingGas(), 'g', 12 );
if ( m_resultDefinition() != nullptr )
{
parts << caf::AppEnum<RiaDefines::ResultCatType>::text( m_resultDefinition->resultType() );
parts << m_resultDefinition->resultVariable();
parts << caf::AppEnum<RiaDefines::PorosityModelType>::text( m_resultDefinition->porosityModel() );
}
for ( int timeStep : selectedTimeSteps() )
parts << QString::number( timeStep );
parts << ( m_enableFormationFilter() ? "formationFilter" : "noFormationFilter" );
for ( const QString& formation : m_selectedFormations() )
parts << formation;
for ( const auto& polygonLine : selectedPolygons() )
{
for ( const auto& point : polygonLine )
parts << QString( "%1,%2,%3" ).arg( point.x(), 0, 'f', 3 ).arg( point.y(), 0, 'f', 3 ).arg( point.z(), 0, 'f', 3 );
}
// Identify a grid file by path, size and modification time, so that the cache is invalidated both when the
// ensemble is replaced or its cases are added/removed, and when a grid file is overwritten in place
auto gridFileFingerprint = []( const RimEclipseCase* gridCase ) -> QString
{
const QString gridFileName = gridCase->gridFileName();
const QFileInfo fileInfo( gridFileName );
if ( !fileInfo.exists() ) return gridFileName;
return QString( "%1,%2,%3" ).arg( gridFileName ).arg( fileInfo.size() ).arg( fileInfo.lastModified().toMSecsSinceEpoch() );
};
// Label and count the two sets of cases, so that a primary case can not be mistaken for the first ensemble case
parts << "primaryCase" << ( eclipseCase() != nullptr ? gridFileFingerprint( eclipseCase() ) : QString() );
const std::vector<RimEclipseCase*> cases = ensembleCases();
parts << "ensembleCases" << QString::number( cases.size() );
for ( const RimEclipseCase* eCase : cases )
parts << gridFileFingerprint( eCase );
const QByteArray hash = QCryptographicHash::hash( parts.join( ";" ).toUtf8(), QCryptographicHash::Md5 );
return QString::fromLatin1( hash.toHex() );
}
//--------------------------------------------------------------------------------------------------
/// Restore the contour map grid and statistics results from the project-adjacent cache files.
/// Returns false when no valid cache exists, and computation is needed.
//--------------------------------------------------------------------------------------------------
bool RimStatisticsContourMap::loadCachedResults()
{
if ( m_cacheFileBaseName().isEmpty() || m_cacheTimeSteps().empty() ) return false;
if ( m_cacheSampleSpacing() <= 0.0 || m_cacheOriginalBoundingBox().size() != 6 || m_cacheExpandedBoundingBox().size() != 6 )
return false;
// The map size is stored explicitly, as recomputing it from the extent of the expanded bounding box is sensitive to
// the limited precision of the doubles in the project file
if ( m_cacheMapSize().size() != 2 || m_cacheMapSize()[0] <= 0 || m_cacheMapSize()[1] <= 0 ) return false;
const QString expectedKey = computeCacheValidityKey();
if ( m_cacheValidityKey().isEmpty() || m_cacheValidityKey() != expectedKey ) return false;
auto toBoundingBox = []( const std::vector<double>& c )
{ return cvf::BoundingBox( cvf::Vec3d( c[0], c[1], c[2] ), cvf::Vec3d( c[3], c[4], c[5] ) ); };
const cvf::BoundingBox originalBoundingBox = toBoundingBox( m_cacheOriginalBoundingBox() );
const cvf::BoundingBox expandedBoundingBox = toBoundingBox( m_cacheExpandedBoundingBox() );
const cvf::Vec2ui storedMapSize( static_cast<cvf::uint>( m_cacheMapSize()[0] ), static_cast<cvf::uint>( m_cacheMapSize()[1] ) );
auto contourMapGrid =
std::make_unique<RigContourMapGrid>( originalBoundingBox, expandedBoundingBox, m_cacheSampleSpacing(), storedMapSize );
const cvf::Vec2ui& mapSize = contourMapGrid->mapSize();
std::map<size_t, std::map<StatisticsType, std::vector<double>>> timeResults;
for ( int timeStep : m_cacheTimeSteps() )
{
for ( size_t statisticsTypeIndex = 0; statisticsTypeIndex < caf::AppEnum<StatisticsType>::size(); ++statisticsTypeIndex )
{
const StatisticsType statisticsType = caf::AppEnum<StatisticsType>::fromIndex( statisticsTypeIndex );
const QString fileName = cacheFileName( m_cacheFileBaseName(), statisticsType, timeStep );
// The surface reader throws on malformed files, fall back to recomputation
std::expected<std::pair<RigRegularSurfaceData, std::vector<float>>, std::string> surfaceData;
try
{
surfaceData = RifSurfio::importSurfaceData( fileName.toStdString() );
}
catch ( ... )
{
surfaceData = std::unexpected( "Unexpected file content" );
}
if ( !surfaceData.has_value() )
{
RiaLogging::warning( std::format( "Failed to read ensemble contour map statistics cache, recomputing: {}", fileName ) );
return false;
}
const auto& [regularSurface, values] = surfaceData.value();
if ( regularSurface.nx != static_cast<int>( mapSize.x() ) || regularSurface.ny != static_cast<int>( mapSize.y() ) ||
values.size() != static_cast<size_t>( mapSize.x() ) * static_cast<size_t>( mapSize.y() ) )
{
RiaLogging::warning(
std::format( "Ensemble contour map statistics cache does not match the sample grid, recomputing: {}", fileName ) );
return false;
}
// Undefined cells are stored as undefined surface values, imported as NaN
std::vector<double> doubleValues;
doubleValues.reserve( values.size() );
for ( float value : values )
{
doubleValues.push_back( std::isnan( value ) ? std::numeric_limits<double>::infinity() : value );
}
timeResults[timeStep][statisticsType] = std::move( doubleValues );
}
}
if ( timeResults.empty() ) return false;
m_contourMapGrid = std::move( contourMapGrid );
m_timeResults = std::move( timeResults );
m_computedValidityKey = expectedKey;
RiaLogging::info(
std::format( "Loaded ensemble contour map statistics from cache: {}/{}_*.gri", getCacheDirectoryPath(), m_cacheFileBaseName() ) );
return true;
}
//--------------------------------------------------------------------------------------------------
/// Write one GRI surface file per statistics type per time step. The surface nodes are placed at
/// the cell centers of the contour map sample grid, so the files can be imported as regular
/// surfaces in ResInsight or other tools.
//--------------------------------------------------------------------------------------------------
bool RimStatisticsContourMap::writeCachedResults( const QString& baseName ) const
{
if ( !m_contourMapGrid || m_timeResults.empty() ) return false;
const cvf::Vec2ui& mapSize = m_contourMapGrid->mapSize();
const double sampleSpacing = m_contourMapGrid->sampleSpacing();
RigRegularSurfaceData surfaceData;
surfaceData.nx = static_cast<int>( mapSize.x() );
surfaceData.ny = static_cast<int>( mapSize.y() );
surfaceData.originX = m_contourMapGrid->expandedBoundingBox().min().x() + sampleSpacing / 2.0;
surfaceData.originY = m_contourMapGrid->expandedBoundingBox().min().y() + sampleSpacing / 2.0;
surfaceData.incrementX = sampleSpacing;
surfaceData.incrementY = sampleSpacing;
surfaceData.rotation = 0.0;
for ( const auto& [timeStep, statisticsResults] : m_timeResults )
{
for ( const auto& [statisticsType, values] : statisticsResults )
{
if ( values.size() != static_cast<size_t>( surfaceData.nx ) * static_cast<size_t>( surfaceData.ny ) ) return false;
// Undefined cells are stored as NaN, written as the undefined surface value
std::vector<float> floatValues;
floatValues.reserve( values.size() );
for ( double value : values )
{
floatValues.push_back( std::isfinite( value ) ? static_cast<float>( value ) : std::numeric_limits<float>::quiet_NaN() );
}
const QString fileName = cacheFileName( baseName, statisticsType, timeStep );
if ( !RifSurfio::exportToGri( fileName.toStdString(), surfaceData, floatValues ) ) return false;
}
}
return true;
}
//--------------------------------------------------------------------------------------------------
/// Write the computed statistics to cache files next to the project file, so that the ensemble
/// sweep can be skipped when the project is loaded again with unchanged settings
//--------------------------------------------------------------------------------------------------
void RimStatisticsContourMap::setupBeforeSave()
{
// Delete any existing cache files, they are rewritten below if valid results exist
deleteCacheFiles();
// Only write results computed from (or previously cached for) the current settings
if ( !m_contourMapGrid || m_timeResults.empty() || m_computedValidityKey.isEmpty() )
{
clearCacheFields();
return;
}
QDir::root().mkpath( getCacheDirectoryPath() );
const QString baseName = getValidCacheFileBaseName();
if ( writeCachedResults( baseName ) )
{
auto boundingBoxCoords = []( const cvf::BoundingBox& boundingBox ) -> std::vector<double>
{
const cvf::Vec3d& min = boundingBox.min();
const cvf::Vec3d& max = boundingBox.max();
return { min.x(), min.y(), min.z(), max.x(), max.y(), max.z() };
};
std::vector<int> timeStepIndices;
for ( const auto& [timeStep, statisticsResults] : m_timeResults )
timeStepIndices.push_back( static_cast<int>( timeStep ) );
m_cacheFileBaseName = baseName;
m_cacheValidityKey = m_computedValidityKey;
m_cacheTimeSteps = timeStepIndices;
m_cacheSampleSpacing = m_contourMapGrid->sampleSpacing();
m_cacheOriginalBoundingBox = boundingBoxCoords( m_contourMapGrid->originalBoundingBox() );
m_cacheExpandedBoundingBox = boundingBoxCoords( m_contourMapGrid->expandedBoundingBox() );
m_cacheMapSize =
std::vector<int>{ static_cast<int>( m_contourMapGrid->mapSize().x() ), static_cast<int>( m_contourMapGrid->mapSize().y() ) };
}
else
{
RiaLogging::warning( std::format( "Failed to write ensemble contour map statistics cache for '{}'", name() ) );
deleteCacheFiles();
clearCacheFields();
}
}
//--------------------------------------------------------------------------------------------------
/// Full path of the cache file for one statistics type and time step, e.g.
/// "<project>_cache/Ensemble_Contour_Map_1-1a2b3c4d_MEAN_36.gri"
//--------------------------------------------------------------------------------------------------
QString RimStatisticsContourMap::cacheFileName( const QString& baseName, StatisticsType statisticsType, size_t timeStep ) const
{
return QString( "%1/%2_%3_%4.gri" )
.arg( getCacheDirectoryPath(), baseName, caf::AppEnum<StatisticsType>::text( statisticsType ), QString::number( timeStep ) );
}
//--------------------------------------------------------------------------------------------------
/// Base file name of the cache files within the cache directory, derived from the contour map name
/// with a unique suffix. The name is kept stable once assigned.
//--------------------------------------------------------------------------------------------------
QString RimStatisticsContourMap::getValidCacheFileBaseName() const
{
if ( !m_cacheFileBaseName().isEmpty() ) return m_cacheFileBaseName();
QString sanitizedName = name();
sanitizedName.replace( QRegularExpression( "[^a-zA-Z0-9-]+" ), "_" );
return sanitizedName + "-" + QUuid::createUuid().toString( QUuid::WithoutBraces ).left( 8 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStatisticsContourMap::deleteCacheFiles() const
{
if ( m_cacheFileBaseName().isEmpty() ) return;
QDir cacheDir( getCacheDirectoryPath() );
for ( const QString& fileName : cacheDir.entryList( { m_cacheFileBaseName() + "_*.gri" }, QDir::Files ) )
{
cacheDir.remove( fileName );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStatisticsContourMap::clearCacheFields()
{
m_cacheFileBaseName = QString();
m_cacheValidityKey = QString();
m_cacheTimeSteps = std::vector<int>();
m_cacheSampleSpacing = 0.0;
m_cacheOriginalBoundingBox = std::vector<double>();
m_cacheExpandedBoundingBox = std::vector<double>();
m_cacheMapSize = std::vector<int>();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimStatisticsContourMap::getCacheDirectoryPath()
{
return RimTools::getCacheRootDirectoryPathFromProject() + "_cache";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
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<RimStatisticsContourMapView*> 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 );
}