mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-30 17:10:19 -05:00
Fix ensemble contour map GENERATED result statistics and caching bugs (#14813)
Fixes a set of related bugs affecting ensemble contour maps that use a GENERATED (grid calculation) result, plus a related bug where switching a view's case lost generated results.
This commit is contained in:
1 parent
1cfe49262a
commit
c5c0e05012
8 files changed
+526
-8
No files matched your search
@@ -50,6 +50,8 @@
|
||||
#include "RimEclipseContourMapProjection.h"
|
||||
#include "RimEclipseResultCase.h"
|
||||
#include "RimEclipseResultDefinition.h"
|
||||
#include "RimGridCalculation.h"
|
||||
#include "RimGridCalculationCollection.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimReservoirGridEnsemble.h"
|
||||
#include "RimSimWellInViewCollection.h"
|
||||
@@ -90,6 +92,23 @@ void applyDataFilterVisibility( RigEclipseContourMapProjection& projection, RimC
|
||||
projection.setCellVisibility( RimCellFilterTools::computeReservoirCellVisibility( dataFilter, eCase, timeStepIndex ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// A GENERATED result only exists in memory, so it must be recomputed for each realization while it
|
||||
/// is open here, as it is otherwise lost when the case is closed again.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void ensureGeneratedResultIsComputed( const RimEclipseResultDefinition* resultDefinition, RimEclipseCase* eCase )
|
||||
{
|
||||
if ( !resultDefinition || resultDefinition->resultType() != RiaDefines::ResultCatType::GENERATED ) return;
|
||||
|
||||
auto project = RimProject::current();
|
||||
if ( !project ) return;
|
||||
|
||||
RimGridCalculation* calculation = project->gridCalculationCollection()->findCalculation( resultDefinition->resultVariable() );
|
||||
if ( !calculation ) return;
|
||||
|
||||
calculation->calculateForCase( eCase );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Generate one result per selected time step if the mapped property is dynamic or the active data
|
||||
/// filter is dynamic (its visible cells then differ per time step, even for a static property).
|
||||
@@ -775,6 +794,13 @@ void RimStatisticsContourMap::computeStatisticsForMaps( const std::vector<RimSta
|
||||
auto activeCellInfo = eclipseCaseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||
auto resultData = eclipseCaseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||
|
||||
// GENERATED results are not stored on disk, so recompute them for this realization while it is open.
|
||||
for ( auto& ctx : contexts )
|
||||
{
|
||||
if ( !ctx.active ) continue;
|
||||
ensureGeneratedResultIsComputed( ctx.map->m_resultDefinition(), eCase );
|
||||
}
|
||||
|
||||
// Make sure at least one dynamic result this case needs is loaded before asking for its time step
|
||||
// dates: allTimeStepDatesFromEclipseReader() and the loaded-result based fallback below both only
|
||||
// report the full time step count once such a result is known.
|
||||
@@ -1062,6 +1088,14 @@ void RimStatisticsContourMap::ensureResultsComputed()
|
||||
// Use the Compute button to force a recomputation after changing settings.
|
||||
if ( m_contourMapGrid ) return;
|
||||
|
||||
// A GENERATED result only reports as dynamic once computed, and the disk cache key depends on that
|
||||
// via selectedTimeSteps(). Ensure the result is computed for the primary case before checking the
|
||||
// cache, so the key is stable between save and load.
|
||||
if ( RimEclipseCase* primaryCase = eclipseCase() )
|
||||
{
|
||||
if ( primaryCase->ensureReservoirCaseIsOpen() ) ensureGeneratedResultIsComputed( m_resultDefinition(), primaryCase );
|
||||
}
|
||||
|
||||
if ( loadCachedResults() ) return;
|
||||
|
||||
// Compute all pending sibling contour maps in the same sweep over the ensemble realizations, so
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
#include "RimFracture.h"
|
||||
#include "RimFractureTemplateCollection.h"
|
||||
#include "RimGeoMechResultDefinition.h"
|
||||
#include "RimGridCalculationCollection.h"
|
||||
#include "RimGridCollection.h"
|
||||
#include "RimGridCrossPlotDataSet.h"
|
||||
#include "RimGridView.h"
|
||||
@@ -533,6 +534,13 @@ void RimEclipseView::fieldChangedByUi( const caf::PdmFieldHandle* changedField,
|
||||
previousSumoCase->cancelPendingTransfers();
|
||||
}
|
||||
|
||||
// Grid calculation results are not stored with the case, so compute them before the result
|
||||
// definitions are updated, otherwise generated results are missing and the view is cleared.
|
||||
if ( auto project = RimProject::current(); project && project->gridCalculationCollection() )
|
||||
{
|
||||
project->gridCalculationCollection()->ensureCalculationsAreComputed( m_eclipseCase );
|
||||
}
|
||||
|
||||
propagateEclipseCaseToChildObjects();
|
||||
|
||||
// Sync with RimReloadCaseTools::updateAll3dViews
|
||||
|
||||
@@ -227,7 +227,9 @@ RimGridCalculationVariable* RimGridCalculation::createVariable()
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RimGridCalculation::calculate()
|
||||
{
|
||||
CloseCasesOpenedForCalculation closeCasesWhenFinished( sourceAndDestinationCases( *this, outputEclipseCases() ) );
|
||||
const std::vector<RimEclipseCase*> calculationCases = casesToCalculate();
|
||||
|
||||
CloseCasesOpenedForCalculation closeCasesWhenFinished( sourceAndDestinationCases( *this, calculationCases ) );
|
||||
|
||||
const bool useCellFilterView = ( m_filterType() == FilterType::CELL_FILTER_VIEW ) && m_cellFilterView() != nullptr;
|
||||
const bool useDataFilter = ( m_filterType() == FilterType::DATA_FILTER );
|
||||
@@ -250,7 +252,7 @@ bool RimGridCalculation::calculate()
|
||||
if ( inputCase && !inputCase->eclipseCaseData() ) inputCase->ensureReservoirCaseIsOpen();
|
||||
}
|
||||
|
||||
for ( auto calculationCase : outputEclipseCases() )
|
||||
for ( auto calculationCase : calculationCases )
|
||||
{
|
||||
if ( !calculationCase ) continue;
|
||||
|
||||
@@ -336,8 +338,64 @@ bool RimGridCalculation::calculate()
|
||||
timeSteps = tmp;
|
||||
}
|
||||
|
||||
bool evaluateDependentCalculations = true;
|
||||
const bool ok = calculateForCases( calculationCases, inputValueVisibilityFilter.p(), timeSteps, evaluateDependentCalculations );
|
||||
|
||||
// When only the ensemble's main case was (re)computed above, other already open realizations keep their
|
||||
// previously computed (now potentially stale) result. Clear it, so it is lazily recomputed with the
|
||||
// current expression/filter next time it is needed, instead of being mistaken for up to date.
|
||||
if ( ok && calculateForEnsemble && !isAggregationExpression() )
|
||||
{
|
||||
for ( RimEclipseCase* otherCase : outputEclipseCases() )
|
||||
{
|
||||
if ( !otherCase || !otherCase->eclipseCaseData() ) continue;
|
||||
if ( std::find( calculationCases.begin(), calculationCases.end(), otherCase ) != calculationCases.end() ) continue;
|
||||
|
||||
otherCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->clearScalarResult( outputAddress() );
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Compute the result for a single case, e.g. when a view is stepped to another case. The case does not
|
||||
/// have to be an output case, as variables referring to the destination case are evaluated on the case.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RimGridCalculation::calculateForCase( RimEclipseCase* eclipseCase )
|
||||
{
|
||||
// Source cases are not necessarily open (e.g. after reopening a project), and validateVariables()
|
||||
// requires an open case for every variable. Open them here, as calculate() does.
|
||||
for ( auto inputCase : inputCases() )
|
||||
{
|
||||
if ( !inputCase ) continue;
|
||||
if ( !inputCase->eclipseCaseData() ) inputCase->ensureReservoirCaseIsOpen();
|
||||
if ( !inputCase->eclipseCaseData() ) return false;
|
||||
}
|
||||
|
||||
// Values from other cases and the cell filter view are mapped cell by cell, requiring equal grid sizes
|
||||
const bool calculateForEnsemble = m_destinationEnsemble() || m_additionalCasesType == AdditionalCasesType::ENSEMBLE;
|
||||
for ( auto inputCase : inputCases() )
|
||||
{
|
||||
if ( calculateForEnsemble || inputCase == destinationCase() ) continue;
|
||||
if ( !eclipseCase->isGridSizeEqualTo( inputCase ) ) return false;
|
||||
}
|
||||
|
||||
cvf::ref<cvf::UByteArray> inputValueVisibilityFilter;
|
||||
if ( m_filterType() == FilterType::CELL_FILTER_VIEW && m_cellFilterView() )
|
||||
{
|
||||
if ( !eclipseCase->isGridSizeEqualTo( dynamic_cast<RimEclipseCase*>( m_cellFilterView()->ownerCase() ) ) ) return false;
|
||||
inputValueVisibilityFilter = m_cellFilterView()->currentTotalCellVisibility();
|
||||
}
|
||||
|
||||
std::optional<std::vector<size_t>> timeSteps;
|
||||
if ( !m_selectedTimeSteps().empty() )
|
||||
{
|
||||
timeSteps = std::vector<size_t>( m_selectedTimeSteps().begin(), m_selectedTimeSteps().end() );
|
||||
}
|
||||
|
||||
bool evaluateDependentCalculations = true;
|
||||
return calculateForCases( outputEclipseCases(), inputValueVisibilityFilter.p(), timeSteps, evaluateDependentCalculations );
|
||||
return calculateForCases( { eclipseCase }, inputValueVisibilityFilter.p(), timeSteps, evaluateDependentCalculations );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -724,6 +782,30 @@ bool RimGridCalculation::allSourceCasesAreEqualToDestinationCase() const
|
||||
return true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// An aggregation expression (sum/avg/min/max/count) reduces a case's cell values to a single scalar
|
||||
/// value per case. Unlike per-cell expressions, its result cannot be recomputed later for a single
|
||||
/// realization on demand, so it must be computed for every realization up front.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RimGridCalculation::isAggregationExpression() const
|
||||
{
|
||||
return m_expression().contains( "sum" ) || m_expression().contains( "avg" ) || m_expression().contains( "min" ) ||
|
||||
m_expression().contains( "max" ) || m_expression().contains( "count" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// For an ensemble destination with a plain per-cell expression, each realization is computed lazily
|
||||
/// when its result is actually needed, so only the main case needs to be computed here. Aggregation
|
||||
/// expressions produce the per-realization summary itself and must be computed for every realization.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<RimEclipseCase*> RimGridCalculation::casesToCalculate() const
|
||||
{
|
||||
const bool calculateForEnsemble = m_destinationEnsemble() || m_additionalCasesType == AdditionalCasesType::ENSEMBLE;
|
||||
if ( calculateForEnsemble && !isAggregationExpression() ) return { destinationCase() };
|
||||
|
||||
return outputEclipseCases();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -1025,9 +1107,7 @@ bool RimGridCalculation::calculateForCases( const std::vector<RimEclipseCase*>&
|
||||
}
|
||||
|
||||
const bool isMultipleCasesPresent = calculationCases.size() > 1;
|
||||
const bool hasAggregationExpression = m_expression().contains( "sum" ) || m_expression().contains( "avg" ) ||
|
||||
m_expression().contains( "min" ) || m_expression().contains( "max" ) ||
|
||||
m_expression().contains( "count" );
|
||||
const bool hasAggregationExpression = isAggregationExpression();
|
||||
|
||||
// If multiple cases are present, release memory after data is extracted to avoid memory issues.
|
||||
m_releaseMemoryAfterDataIsExtracted = isMultipleCasesPresent;
|
||||
|
||||
@@ -73,6 +73,7 @@ public:
|
||||
|
||||
bool preCalculate() const override;
|
||||
bool calculate() override;
|
||||
bool calculateForCase( RimEclipseCase* eclipseCase );
|
||||
|
||||
void updateDependentObjects() override;
|
||||
void removeDependentObjects() override;
|
||||
@@ -149,6 +150,9 @@ private:
|
||||
bool allSourceCasesAreEqualToDestinationCase() const;
|
||||
void onEditNonVisibleResultAddressButtonPressed();
|
||||
|
||||
bool isAggregationExpression() const;
|
||||
std::vector<RimEclipseCase*> casesToCalculate() const;
|
||||
|
||||
static std::pair<bool, QStringList> createStatisticsText( const std::vector<std::vector<double>>& values );
|
||||
|
||||
private:
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
#include "RimGridCalculationCollection.h"
|
||||
|
||||
#include "RiaLogging.h"
|
||||
#include "RigCaseCellResultsData.h"
|
||||
#include "RigEclipseResultAddress.h"
|
||||
#include "RimEclipseCase.h"
|
||||
#include "RimGridCalculation.h"
|
||||
|
||||
#include "cafPdmUiGroup.h"
|
||||
@@ -132,6 +134,27 @@ RimGridCalculation* RimGridCalculationCollection::findCalculation( const QString
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Grid calculation results are not stored with the case. Compute the calculations missing or
|
||||
/// invalidated (cleared, but still registered) for the case, e.g. when a view is stepped to another
|
||||
/// case.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimGridCalculationCollection::ensureCalculationsAreComputed( RimEclipseCase* eclipseCase ) const
|
||||
{
|
||||
if ( !eclipseCase || !eclipseCase->ensureReservoirCaseIsOpen() ) return;
|
||||
|
||||
auto results = eclipseCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||
if ( !results ) return;
|
||||
|
||||
for ( auto calculation : sortedGridCalculations() )
|
||||
{
|
||||
const RigEclipseResultAddress address = calculation->outputAddress();
|
||||
if ( results->hasResultEntry( address ) && results->isResultLoaded( address ) ) continue;
|
||||
|
||||
calculation->calculateForCase( eclipseCase );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -37,9 +37,10 @@ public:
|
||||
RimGridCalculation* createCalculation() const override;
|
||||
std::vector<RimGridCalculation*> sortedGridCalculations() const;
|
||||
std::vector<RimGridCalculation*> dependentCalculations( RimGridCalculation* sourceCalculation ) const;
|
||||
RimGridCalculation* findCalculation( const QString& calculationName ) const;
|
||||
void ensureCalculationsAreComputed( RimEclipseCase* eclipseCase ) const;
|
||||
|
||||
private:
|
||||
RimGridCalculation* findCalculation( const QString& calculationName ) const;
|
||||
bool dependentCalculationsRecursively( RimGridCalculation* sourceCalculation, std::vector<RimGridCalculation*>& calculations ) const;
|
||||
|
||||
void initAfterRead() override;
|
||||
|
||||
Reference in new issue
Block a user