mirror of
https://github.com/OPM/ResInsight.git
synced 2026-11-02 20:30:46 -06:00
#14710 Compute per-time-step visible cells for dynamic data filters
A static property with an active dynamic filter now produces a contour map per time step. Adds a synthetic ensemble fixture with unified restart files, as the OPM reader used for statistics does not support per-step restart files.
This commit is contained in:
1 parent
cf75dbf58e
commit
bb8af7bbbd
19 files changed
+248
-26
No files matched your search
@@ -1057,6 +1057,10 @@ void RifReaderEclipseOutput::sourSimRlResult( const QString& result, size_t step
|
|||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
std::vector<QDateTime> RifReaderEclipseOutput::allTimeSteps() const
|
std::vector<QDateTime> RifReaderEclipseOutput::allTimeSteps() const
|
||||||
{
|
{
|
||||||
|
// allTimeSteps() is also used to discover the time steps a case has before any dynamic result has actually
|
||||||
|
// been requested, so the underlying restart file access must be created here rather than assumed present.
|
||||||
|
const_cast<RifReaderEclipseOutput*>( this )->ensureDynamicResultAccessIsPresent();
|
||||||
|
|
||||||
std::vector<QDateTime> steps;
|
std::vector<QDateTime> steps;
|
||||||
if ( m_dynamicResultsAccess.notNull() )
|
if ( m_dynamicResultsAccess.notNull() )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,12 +19,16 @@
|
|||||||
#include "RimCellFilterTools.h"
|
#include "RimCellFilterTools.h"
|
||||||
|
|
||||||
#include "RigEclipseCaseData.h"
|
#include "RigEclipseCaseData.h"
|
||||||
|
#include "RigEclipseResultAddress.h"
|
||||||
#include "RigGridBase.h"
|
#include "RigGridBase.h"
|
||||||
#include "RigLocalGrid.h"
|
#include "RigLocalGrid.h"
|
||||||
#include "RigMainGrid.h"
|
#include "RigMainGrid.h"
|
||||||
|
|
||||||
#include "RimCellFilter.h"
|
#include "RimCellFilter.h"
|
||||||
|
#include "RimCombinedFilter.h"
|
||||||
#include "RimEclipseCase.h"
|
#include "RimEclipseCase.h"
|
||||||
|
#include "RimEclipsePropertyFilter.h"
|
||||||
|
#include "RimEclipseResultDefinition.h"
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
/// Evaluate the filter against the given case for all grids, and return the visibility indexed by
|
/// Evaluate the filter against the given case for all grids, and return the visibility indexed by
|
||||||
@@ -105,3 +109,56 @@ cvf::ref<cvf::UByteArray>
|
|||||||
|
|
||||||
return reservoirVisibility;
|
return reservoirVisibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/// A property filter on a dynamic result (e.g. SOIL, SWAT) accepts different cells at each time step.
|
||||||
|
/// A combined filter is dynamic if any of its descendants is. Range, index, polygon and formation
|
||||||
|
/// name filters are static: they select the same cells regardless of time step.
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
bool RimCellFilterTools::isDynamicFilter( const RimCellFilter* filter )
|
||||||
|
{
|
||||||
|
if ( auto* propertyFilter = dynamic_cast<const RimEclipsePropertyFilter*>( filter ) )
|
||||||
|
{
|
||||||
|
if ( !propertyFilter->resultDefinition() ) return false;
|
||||||
|
|
||||||
|
// Classify by result category rather than hasDynamicResult(), which only answers correctly once
|
||||||
|
// the result has been loaded. Ensemble data filters may not have loaded their result yet.
|
||||||
|
switch ( propertyFilter->resultDefinition()->resultType() )
|
||||||
|
{
|
||||||
|
case RiaDefines::ResultCatType::DYNAMIC_NATIVE:
|
||||||
|
case RiaDefines::ResultCatType::SOURSIMRL:
|
||||||
|
case RiaDefines::ResultCatType::FLOW_DIAGNOSTICS:
|
||||||
|
case RiaDefines::ResultCatType::INJECTION_FLOODING:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return propertyFilter->resultDefinition()->hasDynamicResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( auto* combinedFilter = dynamic_cast<const RimCombinedFilter*>( filter ) )
|
||||||
|
{
|
||||||
|
return combinedFilter->hasActiveDynamicPropertyDescendant();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/// The result address a dynamic property filter (or one of a combined filter's descendants) is based
|
||||||
|
/// on, so callers can make sure it is loaded before asking the case how many time steps it has.
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::optional<RigEclipseResultAddress> RimCellFilterTools::dynamicResultAddress( const RimCellFilter* filter )
|
||||||
|
{
|
||||||
|
if ( auto* propertyFilter = dynamic_cast<const RimEclipsePropertyFilter*>( filter ) )
|
||||||
|
{
|
||||||
|
if ( isDynamicFilter( propertyFilter ) ) return propertyFilter->resultDefinition()->eclipseResultAddress();
|
||||||
|
}
|
||||||
|
else if ( auto* combinedFilter = dynamic_cast<const RimCombinedFilter*>( filter ) )
|
||||||
|
{
|
||||||
|
for ( RimCellFilter* child : combinedFilter->filters() )
|
||||||
|
{
|
||||||
|
if ( auto address = dynamicResultAddress( child ) ) return address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
@@ -21,6 +21,9 @@
|
|||||||
#include "cvfArray.h"
|
#include "cvfArray.h"
|
||||||
#include "cvfObject.h"
|
#include "cvfObject.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
class RigEclipseResultAddress;
|
||||||
class RimCellFilter;
|
class RimCellFilter;
|
||||||
class RimEclipseCase;
|
class RimEclipseCase;
|
||||||
|
|
||||||
@@ -31,4 +34,12 @@ class RimCellFilterTools
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static cvf::ref<cvf::UByteArray> computeReservoirCellVisibility( RimCellFilter* filter, RimEclipseCase* eclipseCase, size_t timeStepIndex );
|
static cvf::ref<cvf::UByteArray> computeReservoirCellVisibility( RimCellFilter* filter, RimEclipseCase* eclipseCase, size_t timeStepIndex );
|
||||||
|
|
||||||
|
// True if the filter's visible cells can change from one time step to the next, i.e. it is (or
|
||||||
|
// contains) a property filter on a dynamic (time-varying) result such as SOIL or SWAT.
|
||||||
|
static bool isDynamicFilter( const RimCellFilter* filter );
|
||||||
|
|
||||||
|
// The dynamic result address the filter (or one of its descendants) is based on, if any. Used to make sure
|
||||||
|
// that result is loaded before its time step count is queried.
|
||||||
|
static std::optional<RigEclipseResultAddress> dynamicResultAddress( const RimCellFilter* filter );
|
||||||
};
|
};
|
||||||
@@ -81,29 +81,44 @@ namespace
|
|||||||
{
|
{
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
/// Restrict the grid mapping to the cells accepted by the data filter, evaluated against the given
|
/// Restrict the grid mapping to the cells accepted by the data filter, evaluated against the given
|
||||||
/// realization. The filter is evaluated at the first time step, as the grid mapping is shared by all
|
/// realization at the given (local) time step.
|
||||||
/// time steps.
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
void applyDataFilterVisibility( RigEclipseContourMapProjection& projection, RimCellFilter* dataFilter, RimEclipseCase* eCase )
|
void applyDataFilterVisibility( RigEclipseContourMapProjection& projection, RimCellFilter* dataFilter, RimEclipseCase* eCase, size_t timeStepIndex )
|
||||||
{
|
{
|
||||||
if ( !dataFilter ) return;
|
if ( !dataFilter ) return;
|
||||||
|
|
||||||
projection.setCellVisibility( RimCellFilterTools::computeReservoirCellVisibility( dataFilter, eCase, 0 ) );
|
projection.setCellVisibility( RimCellFilterTools::computeReservoirCellVisibility( dataFilter, eCase, timeStepIndex ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/// 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).
|
||||||
|
/// Otherwise generate a single, time-independent result.
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
void extractCaseResults( RigEclipseContourMapProjection& projection,
|
void extractCaseResults( RigEclipseContourMapProjection& projection,
|
||||||
|
RimCellFilter* dataFilter,
|
||||||
|
RimEclipseCase* eCase,
|
||||||
const RigEclipseResultAddress& resultAddress,
|
const RigEclipseResultAddress& resultAddress,
|
||||||
bool hasDynamicResult,
|
bool hasDynamicResult,
|
||||||
|
bool hasDynamicFilter,
|
||||||
RigContourMapCalculator::ResultAggregationType resultAggregation,
|
RigContourMapCalculator::ResultAggregationType resultAggregation,
|
||||||
RigFloodingSettings& floodSettings,
|
RigFloodingSettings& floodSettings,
|
||||||
|
const std::vector<std::vector<cvf::Vec3d>>& selectedPolygons,
|
||||||
const std::vector<std::pair<int, int>>& localToGlobalTimeSteps,
|
const std::vector<std::pair<int, int>>& localToGlobalTimeSteps,
|
||||||
std::map<size_t, std::vector<std::vector<double>>>& timestepResults )
|
std::map<size_t, std::vector<std::vector<double>>>& timestepResults )
|
||||||
{
|
{
|
||||||
if ( hasDynamicResult )
|
if ( hasDynamicResult || hasDynamicFilter )
|
||||||
{
|
{
|
||||||
for ( auto [localTs, globalTs] : localToGlobalTimeSteps )
|
for ( auto [localTs, globalTs] : localToGlobalTimeSteps )
|
||||||
{
|
{
|
||||||
timestepResults[globalTs].push_back( projection.generateResults( resultAddress, resultAggregation, localTs, floodSettings ) );
|
if ( hasDynamicFilter )
|
||||||
|
{
|
||||||
|
applyDataFilterVisibility( projection, dataFilter, eCase, static_cast<size_t>( localTs ) );
|
||||||
|
projection.generateGridMapping( resultAggregation, {}, selectedPolygons );
|
||||||
|
}
|
||||||
|
|
||||||
|
const int resultTimeStep = hasDynamicResult ? localTs : 0;
|
||||||
|
timestepResults[globalTs].push_back( projection.generateResults( resultAddress, resultAggregation, resultTimeStep, floodSettings ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -751,6 +766,24 @@ void RimStatisticsContourMap::computeStatisticsForMaps( const std::vector<RimSta
|
|||||||
auto activeCellInfo = eclipseCaseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
auto activeCellInfo = eclipseCaseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||||
auto resultData = eclipseCaseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
auto resultData = eclipseCaseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
for ( auto& ctx : contexts )
|
||||||
|
{
|
||||||
|
if ( !ctx.active ) continue;
|
||||||
|
if ( ctx.map->m_resultDefinition()->hasDynamicResult() )
|
||||||
|
resultData->ensureKnownResultLoaded( ctx.map->m_resultDefinition()->eclipseResultAddress() );
|
||||||
|
if ( auto address = RimCellFilterTools::dynamicResultAddress( ctx.map->m_dataFilter() ) )
|
||||||
|
resultData->ensureKnownResultLoaded( *address );
|
||||||
|
}
|
||||||
|
|
||||||
|
// The case's own time step dates, read directly from the reader rather than from already-loaded
|
||||||
|
// results: a realization case may not have any dynamic result loaded yet, in which case
|
||||||
|
// eCase->timeStepDates() would incorrectly report only a single (or zero) time step.
|
||||||
|
auto caseTimeStepDates = resultData->allTimeStepDatesFromEclipseReader( eCase->gridFileName() );
|
||||||
|
if ( caseTimeStepDates.empty() ) caseTimeStepDates = eCase->timeStepDates();
|
||||||
|
|
||||||
// Prefetch every dynamic result time step this case needs before reading any of it for real.
|
// Prefetch every dynamic result time step this case needs before reading any of it for real.
|
||||||
// This case has no view of its own, so it is the only chance a cloud-backed reader gets to
|
// This case has no view of its own, so it is the only chance a cloud-backed reader gets to
|
||||||
// fetch exactly the time steps needed before the case is closed again - see
|
// fetch exactly the time steps needed before the case is closed again - see
|
||||||
@@ -762,12 +795,15 @@ void RimStatisticsContourMap::computeStatisticsForMaps( const std::vector<RimSta
|
|||||||
std::map<QString, std::vector<size_t>> stepsToPrefetchByResult;
|
std::map<QString, std::vector<size_t>> stepsToPrefetchByResult;
|
||||||
for ( auto& ctx : contexts )
|
for ( auto& ctx : contexts )
|
||||||
{
|
{
|
||||||
if ( !ctx.active || !ctx.map->m_resultDefinition()->hasDynamicResult() ) continue;
|
if ( !ctx.active ) continue;
|
||||||
|
|
||||||
auto& steps = stepsToPrefetchByResult[ctx.map->m_resultDefinition()->eclipseResultAddress().resultName()];
|
auto localToGlobalTimeSteps = ctx.map->mapLocalToGlobalTimeSteps( caseTimeStepDates );
|
||||||
for ( auto [localTs, globalTs] : ctx.map->mapLocalToGlobalTimeSteps( eCase->timeStepDates() ) )
|
|
||||||
|
if ( ctx.map->m_resultDefinition()->hasDynamicResult() )
|
||||||
{
|
{
|
||||||
steps.push_back( static_cast<size_t>( localTs ) );
|
auto& steps = stepsToPrefetchByResult[ctx.map->m_resultDefinition()->eclipseResultAddress().resultName()];
|
||||||
|
for ( auto [localTs, globalTs] : localToGlobalTimeSteps )
|
||||||
|
steps.push_back( static_cast<size_t>( localTs ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,38 +820,52 @@ void RimStatisticsContourMap::computeStatisticsForMaps( const std::vector<RimSta
|
|||||||
if ( !ctx.active ) continue;
|
if ( !ctx.active ) continue;
|
||||||
|
|
||||||
RimStatisticsContourMap* map = ctx.map;
|
RimStatisticsContourMap* map = ctx.map;
|
||||||
auto localToGlobalTimeSteps = map->mapLocalToGlobalTimeSteps( eCase->timeStepDates() );
|
auto localToGlobalTimeSteps = map->mapLocalToGlobalTimeSteps( caseTimeStepDates );
|
||||||
|
|
||||||
if ( ctx.useSharedGrid )
|
if ( ctx.useSharedGrid )
|
||||||
{
|
{
|
||||||
ctx.sharedProjection->updateRealizationData( activeCellInfo, resultData );
|
ctx.sharedProjection->updateRealizationData( activeCellInfo, resultData );
|
||||||
|
|
||||||
// A data filter can accept different cells in each realization, e.g. a property filter or
|
// A data filter can accept different cells in each realization, e.g. a property filter or
|
||||||
// formation names defined per realization
|
// formation names defined per realization. A dynamic filter's visible cells also change per
|
||||||
if ( map->m_dataFilter() )
|
// time step, so its mapping is (re)generated per time step inside extractCaseResults() instead.
|
||||||
|
const bool filterIsDynamic = RimCellFilterTools::isDynamicFilter( map->m_dataFilter() );
|
||||||
|
if ( map->m_dataFilter() && !filterIsDynamic )
|
||||||
{
|
{
|
||||||
applyDataFilterVisibility( *ctx.sharedProjection, map->m_dataFilter(), eCase );
|
applyDataFilterVisibility( *ctx.sharedProjection, map->m_dataFilter(), eCase, 0 );
|
||||||
ctx.sharedProjection->generateGridMapping( ctx.resultAggregation, {}, map->selectedPolygons() );
|
ctx.sharedProjection->generateGridMapping( ctx.resultAggregation, {}, map->selectedPolygons() );
|
||||||
}
|
}
|
||||||
|
|
||||||
extractCaseResults( *ctx.sharedProjection,
|
extractCaseResults( *ctx.sharedProjection,
|
||||||
|
map->m_dataFilter(),
|
||||||
|
eCase,
|
||||||
map->m_resultDefinition()->eclipseResultAddress(),
|
map->m_resultDefinition()->eclipseResultAddress(),
|
||||||
map->m_resultDefinition()->hasDynamicResult(),
|
map->m_resultDefinition()->hasDynamicResult(),
|
||||||
|
filterIsDynamic,
|
||||||
ctx.resultAggregation,
|
ctx.resultAggregation,
|
||||||
ctx.floodSettings,
|
ctx.floodSettings,
|
||||||
|
map->selectedPolygons(),
|
||||||
localToGlobalTimeSteps,
|
localToGlobalTimeSteps,
|
||||||
ctx.timestepResults );
|
ctx.timestepResults );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RigEclipseContourMapProjection contourMapProjection( ctx.contourMapGrid.get(), eclipseCaseData, resultData );
|
RigEclipseContourMapProjection contourMapProjection( ctx.contourMapGrid.get(), eclipseCaseData, resultData );
|
||||||
applyDataFilterVisibility( contourMapProjection, map->m_dataFilter(), eCase );
|
const bool filterIsDynamic = RimCellFilterTools::isDynamicFilter( map->m_dataFilter() );
|
||||||
contourMapProjection.generateGridMapping( ctx.resultAggregation, {}, map->selectedPolygons() );
|
if ( !filterIsDynamic )
|
||||||
|
{
|
||||||
|
applyDataFilterVisibility( contourMapProjection, map->m_dataFilter(), eCase, 0 );
|
||||||
|
contourMapProjection.generateGridMapping( ctx.resultAggregation, {}, map->selectedPolygons() );
|
||||||
|
}
|
||||||
extractCaseResults( contourMapProjection,
|
extractCaseResults( contourMapProjection,
|
||||||
|
map->m_dataFilter(),
|
||||||
|
eCase,
|
||||||
map->m_resultDefinition()->eclipseResultAddress(),
|
map->m_resultDefinition()->eclipseResultAddress(),
|
||||||
map->m_resultDefinition()->hasDynamicResult(),
|
map->m_resultDefinition()->hasDynamicResult(),
|
||||||
|
filterIsDynamic,
|
||||||
ctx.resultAggregation,
|
ctx.resultAggregation,
|
||||||
ctx.floodSettings,
|
ctx.floodSettings,
|
||||||
|
map->selectedPolygons(),
|
||||||
localToGlobalTimeSteps,
|
localToGlobalTimeSteps,
|
||||||
ctx.timestepResults );
|
ctx.timestepResults );
|
||||||
}
|
}
|
||||||
@@ -889,11 +939,13 @@ std::vector<double> RimStatisticsContourMap::result( size_t timeStep, Statistics
|
|||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
///
|
/// A single, time-independent result is enough when both the mapped property and the active data
|
||||||
|
/// filter are static. Otherwise the result (or the filter's visible cells) can differ per time step,
|
||||||
|
/// so fall back to the user-selected time steps.
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
std::vector<int> RimStatisticsContourMap::selectedTimeSteps() const
|
std::vector<int> RimStatisticsContourMap::selectedTimeSteps() const
|
||||||
{
|
{
|
||||||
if ( !m_resultDefinition->hasDynamicResult() )
|
if ( !m_resultDefinition->hasDynamicResult() && !RimCellFilterTools::isDynamicFilter( m_dataFilter() ) )
|
||||||
{
|
{
|
||||||
return { 0 };
|
return { 0 };
|
||||||
}
|
}
|
||||||
@@ -913,7 +965,12 @@ std::vector<QDateTime> RimStatisticsContourMap::selectedTimeStepDates() const
|
|||||||
auto eCase = eclipseCase();
|
auto eCase = eclipseCase();
|
||||||
if ( eCase != nullptr )
|
if ( eCase != nullptr )
|
||||||
{
|
{
|
||||||
auto allDates = eCase->timeStepDates();
|
// Read time steps directly from the reader rather than from already-loaded results: the primary case may
|
||||||
|
// not have any dynamic result loaded yet, in which case eCase->timeStepDates() would under-report them.
|
||||||
|
auto* resultData = eCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||||
|
auto allDates = resultData ? resultData->allTimeStepDatesFromEclipseReader( eCase->gridFileName() ) : std::vector<QDateTime>();
|
||||||
|
if ( allDates.empty() ) allDates = eCase->timeStepDates();
|
||||||
|
|
||||||
for ( auto i : selectedTimeSteps() )
|
for ( auto i : selectedTimeSteps() )
|
||||||
{
|
{
|
||||||
if ( i < (int)allDates.size() ) retDates.push_back( allDates[i] );
|
if ( i < (int)allDates.size() ) retDates.push_back( allDates[i] );
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "RiaResultNames.h"
|
#include "RiaResultNames.h"
|
||||||
|
|
||||||
#include "RifReaderEclipseOutput.h"
|
#include "RifReaderEclipseOutput.h"
|
||||||
|
#include "RifReaderOpmCommon.h"
|
||||||
|
|
||||||
#include "RigAllanDiagramData.h"
|
#include "RigAllanDiagramData.h"
|
||||||
#include "RigAllanUtil.h"
|
#include "RigAllanUtil.h"
|
||||||
@@ -569,19 +570,23 @@ bool RigCaseCellResultsData::hasFlowDiagUsableFluxes() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
///
|
/// Time steps reported by the file reader itself, independent of which results (if any) have been
|
||||||
|
/// loaded so far. gridFileName is only needed for the opm-common reader, which requires it to locate
|
||||||
|
/// the matching restart file.
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
std::vector<QDateTime> RigCaseCellResultsData::allTimeStepDatesFromEclipseReader() const
|
std::vector<QDateTime> RigCaseCellResultsData::allTimeStepDatesFromEclipseReader( const QString& gridFileName ) const
|
||||||
{
|
{
|
||||||
const RifReaderEclipseOutput* rifReaderOutput = dynamic_cast<const RifReaderEclipseOutput*>( m_readerInterface.p() );
|
if ( const RifReaderEclipseOutput* rifReaderOutput = dynamic_cast<const RifReaderEclipseOutput*>( m_readerInterface.p() ) )
|
||||||
if ( rifReaderOutput )
|
|
||||||
{
|
{
|
||||||
return rifReaderOutput->allTimeSteps();
|
return rifReaderOutput->allTimeSteps();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if ( auto* rifReaderOpmCommon = dynamic_cast<const RifReaderOpmCommon*>( m_readerInterface.p() ) )
|
||||||
{
|
{
|
||||||
return std::vector<QDateTime>();
|
if ( !gridFileName.isEmpty() ) return const_cast<RifReaderOpmCommon*>( rifReaderOpmCommon )->timeStepsOnFile( gridFileName );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return std::vector<QDateTime>();
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public:
|
|||||||
size_t timeStepCount( const RigEclipseResultAddress& resVarAddr ) const;
|
size_t timeStepCount( const RigEclipseResultAddress& resVarAddr ) const;
|
||||||
size_t maxTimeStepCount( RigEclipseResultAddress* resultAddressWithMostTimeSteps = nullptr ) const;
|
size_t maxTimeStepCount( RigEclipseResultAddress* resultAddressWithMostTimeSteps = nullptr ) const;
|
||||||
|
|
||||||
std::vector<QDateTime> allTimeStepDatesFromEclipseReader() const;
|
std::vector<QDateTime> allTimeStepDatesFromEclipseReader( const QString& gridFileName = QString() ) const;
|
||||||
std::vector<QDateTime> timeStepDates() const;
|
std::vector<QDateTime> timeStepDates() const;
|
||||||
std::vector<QDateTime> timeStepDates( const RigEclipseResultAddress& resVarAddr ) const;
|
std::vector<QDateTime> timeStepDates( const RigEclipseResultAddress& resVarAddr ) const;
|
||||||
std::vector<double> daysSinceSimulationStart() const;
|
std::vector<double> daysSinceSimulationStart() const;
|
||||||
|
|||||||
@@ -24,12 +24,16 @@
|
|||||||
|
|
||||||
#include "ContourMap/RigContourMapCalculator.h"
|
#include "ContourMap/RigContourMapCalculator.h"
|
||||||
#include "ContourMap/RigContourMapGrid.h"
|
#include "ContourMap/RigContourMapGrid.h"
|
||||||
|
#include "RigCaseCellResultsData.h"
|
||||||
#include "RigEclipseCaseData.h"
|
#include "RigEclipseCaseData.h"
|
||||||
|
#include "RigEclipseResultAddress.h"
|
||||||
#include "RigMainGrid.h"
|
#include "RigMainGrid.h"
|
||||||
|
|
||||||
#include "ContourMap/RimStatisticsContourMap.h"
|
#include "ContourMap/RimStatisticsContourMap.h"
|
||||||
|
#include "RiaResultNames.h"
|
||||||
#include "RimCellRangeFilter.h"
|
#include "RimCellRangeFilter.h"
|
||||||
#include "RimDataFilterCollection.h"
|
#include "RimDataFilterCollection.h"
|
||||||
|
#include "RimEclipsePropertyFilter.h"
|
||||||
#include "RimEclipseResultCase.h"
|
#include "RimEclipseResultCase.h"
|
||||||
#include "RimEclipseResultDefinition.h"
|
#include "RimEclipseResultDefinition.h"
|
||||||
#include "RimProject.h"
|
#include "RimProject.h"
|
||||||
@@ -91,6 +95,43 @@ std::unique_ptr<RimReservoirGridEnsemble> createBruggeEnsemble( GridModeType gri
|
|||||||
return ensemble;
|
return ensemble;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "synthetic_ensemble_with_dynamic_filter" has unified .UNRST restart files (unlike Case_with_10_timesteps,
|
||||||
|
// which uses the legacy per-step .X#### format). Computing statistics forces the OPM_COMMON grid reader,
|
||||||
|
// which only supports unified restart files, so a dynamic (e.g. SWAT) filter needs this fixture instead.
|
||||||
|
// Real1-3 have their PORO and SWAT values scaled by a distinct per-realization factor relative to Real0,
|
||||||
|
// so the ensemble also exercises real inter-realization variability, not just identical copies.
|
||||||
|
std::unique_ptr<RimReservoirGridEnsemble> createSyntheticDynamicFilterEnsemble()
|
||||||
|
{
|
||||||
|
auto ensemble = std::make_unique<RimReservoirGridEnsemble>();
|
||||||
|
|
||||||
|
QDir baseFolder( TEST_MODEL_DIR );
|
||||||
|
if ( !baseFolder.cd( "synthetic_ensemble_with_dynamic_filter" ) ) return nullptr;
|
||||||
|
|
||||||
|
for ( const auto& folder : { "Real0", "Real1", "Real2", "Real3" } )
|
||||||
|
{
|
||||||
|
QDir realizationFolder( baseFolder );
|
||||||
|
if ( !realizationFolder.cd( folder ) ) return nullptr;
|
||||||
|
|
||||||
|
QString filePath = realizationFolder.absoluteFilePath( "SYNTH.EGRID" );
|
||||||
|
if ( !QFile::exists( filePath ) ) return nullptr;
|
||||||
|
|
||||||
|
auto eclipseCase = std::make_unique<RimEclipseResultCase>();
|
||||||
|
eclipseCase->setCaseInfo( folder, filePath );
|
||||||
|
if ( !eclipseCase->openEclipseGridFile() ) return nullptr;
|
||||||
|
|
||||||
|
ensemble->addCase( eclipseCase.release() );
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* autoDetectField = dynamic_cast<caf::PdmField<bool>*>( ensemble->findField( "AutoDetectGridType" ) );
|
||||||
|
auto* gridModeField = dynamic_cast<caf::PdmField<caf::AppEnum<GridModeType>>*>( ensemble->findField( "GridMode" ) );
|
||||||
|
if ( !autoDetectField || !gridModeField ) return nullptr;
|
||||||
|
|
||||||
|
autoDetectField->setValue( false );
|
||||||
|
gridModeField->setValue( GridModeType::SHARED_GRID );
|
||||||
|
|
||||||
|
return ensemble;
|
||||||
|
}
|
||||||
|
|
||||||
RimStatisticsContourMap* addPoroContourMap( RimReservoirGridEnsemble* ensemble, GridImportMode importMode )
|
RimStatisticsContourMap* addPoroContourMap( RimReservoirGridEnsemble* ensemble, GridImportMode importMode )
|
||||||
{
|
{
|
||||||
auto* map = new RimStatisticsContourMap;
|
auto* map = new RimStatisticsContourMap;
|
||||||
@@ -229,6 +270,53 @@ TEST( RimStatisticsContourMapTest, DataFilterSharedAndIndividualGridsAgree )
|
|||||||
EXPECT_EQ( 0u, countDifferentValues( shared, individual ) );
|
EXPECT_EQ( 0u, countDifferentValues( shared, individual ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/// A static mapped property (PORO) with an active dynamic filter (SWAT) must still produce a result
|
||||||
|
/// per selected time step, since the filter's visible cells change over time even if PORO does not.
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RimStatisticsContourMapTest, StaticResultWithDynamicFilterProducesResultsPerTimeStep )
|
||||||
|
{
|
||||||
|
auto ensemble = createSyntheticDynamicFilterEnsemble();
|
||||||
|
ASSERT_TRUE( ensemble != nullptr );
|
||||||
|
|
||||||
|
auto* map = addPoroContourMap( ensemble.get(), GridImportMode::SHARED_GRID );
|
||||||
|
ASSERT_TRUE( map != nullptr );
|
||||||
|
|
||||||
|
// The synthetic fixture's unified restart file has 13 time steps (indices 0..12)
|
||||||
|
const int lastTimeStep = 12;
|
||||||
|
|
||||||
|
auto* swatFilter = ensemble->dataFilterCollection()->addNewPropertyFilter();
|
||||||
|
swatFilter->resultDefinition()->setResultType( RiaDefines::ResultCatType::DYNAMIC_NATIVE );
|
||||||
|
swatFilter->resultDefinition()->setResultVariable( RiaResultNames::swat() );
|
||||||
|
|
||||||
|
auto* lowerField = dynamic_cast<caf::PdmField<double>*>( swatFilter->findField( "LowerBound" ) );
|
||||||
|
auto* upperField = dynamic_cast<caf::PdmField<double>*>( swatFilter->findField( "UpperBound" ) );
|
||||||
|
ASSERT_TRUE( lowerField && upperField );
|
||||||
|
lowerField->setValue( 0.25 );
|
||||||
|
upperField->setValue( 0.255 );
|
||||||
|
|
||||||
|
setDataFilter( map, swatFilter );
|
||||||
|
|
||||||
|
auto* selectedTimeStepsField = dynamic_cast<caf::PdmField<std::vector<int>>*>( map->findField( "SelectedTimeSteps" ) );
|
||||||
|
ASSERT_TRUE( selectedTimeStepsField != nullptr );
|
||||||
|
selectedTimeStepsField->setValue( { 0, lastTimeStep } );
|
||||||
|
|
||||||
|
// A dynamic filter must make selectedTimeSteps() return the full user selection, even for static PORO
|
||||||
|
EXPECT_EQ( ( std::vector<int>{ 0, lastTimeStep } ), map->selectedTimeSteps() );
|
||||||
|
|
||||||
|
RimStatisticsContourMap::computeStatisticsForMaps( { map } );
|
||||||
|
|
||||||
|
const auto resultAtFirstStep = map->result( 0, RimStatisticsContourMap::StatisticsType::MEAN );
|
||||||
|
const auto resultAtLastStep = map->result( 1, RimStatisticsContourMap::StatisticsType::MEAN );
|
||||||
|
|
||||||
|
EXPECT_GT( countValidValues( resultAtFirstStep ), 0u );
|
||||||
|
EXPECT_GT( countValidValues( resultAtLastStep ), 0u );
|
||||||
|
ASSERT_EQ( resultAtFirstStep.size(), resultAtLastStep.size() );
|
||||||
|
|
||||||
|
// The filter's visible cells differ per time step (SWAT changes), so the aggregated PORO results differ too
|
||||||
|
EXPECT_GT( countDifferentValues( resultAtFirstStep, resultAtLastStep ), 0u );
|
||||||
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user