mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
RimEclipseStatisticsCaseEvaluator::addNamedResult() sized the destination statistics containers (MEAN/P10/P50/P90/etc.) using the time step count of an arbitrary first result found in the source case (existingResults()[0]). Results computed by ResInsight, such as riPORV*SOIL and riPORV*SGAS, do not have their own time step info populated until they are actually computed, so existingResults()[0] can end up being an unrelated result, including a static one (e.g. PORV) with only a single time step. Using its size to size the destination containers collapsed the statistics for all dynamic results to a single time step, silently dropping the results for every other time step, i.e. no results were shown for riPORV*SOIL / riPORV*SGAS. Fix by using RigCaseCellResultsData::maxTimeStepCount() to find the source result with the most time steps, and use its time step info both for the destination time step metadata and for sizing the destination containers, instead of depending on an arbitrary source result. Added a unit test reproducing the ensemble statistics computation for riPORV*SOIL across two source cases.
221 lines
9.8 KiB
C++
221 lines
9.8 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2026- Equinor ASA
|
|
//
|
|
// ResInsight is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE.
|
|
//
|
|
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
|
// for more details.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "RiaDefines.h"
|
|
#include "RiaResultNames.h"
|
|
|
|
#include "RifReaderMockModel.h"
|
|
|
|
#include "RigActiveCellInfo.h"
|
|
#include "RigCaseCellResultsData.h"
|
|
#include "RigEclipseCaseData.h"
|
|
#include "RigEclipseResultAddress.h"
|
|
#include "RigEclipseResultInfo.h"
|
|
#include "RigMainGrid.h"
|
|
|
|
#include "RimEclipseResultCase.h"
|
|
#include "RimEclipseStatisticsCaseEvaluator.h"
|
|
|
|
#include <QDateTime>
|
|
|
|
#include <memory>
|
|
#include <set>
|
|
|
|
namespace
|
|
{
|
|
struct MockCase
|
|
{
|
|
std::unique_ptr<RimEclipseResultCase> resultCase;
|
|
cvf::ref<RigEclipseCaseData> caseData;
|
|
};
|
|
|
|
MockCase createMockCase( const std::set<RiaDefines::PhaseType>& phases )
|
|
{
|
|
MockCase mockCase;
|
|
mockCase.resultCase.reset( new RimEclipseResultCase );
|
|
mockCase.caseData = new RigEclipseCaseData( mockCase.resultCase.get() );
|
|
|
|
cvf::ref<RifReaderMockModel> mockReader = new RifReaderMockModel;
|
|
mockReader->setWorldCoordinates( cvf::Vec3d( 0, 0, 0 ), cvf::Vec3d( 100, 100, 100 ) );
|
|
mockReader->setCellCounts( cvf::Vec3st( 2, 2, 2 ) );
|
|
mockReader->setResultInfo( 0, 0 );
|
|
mockReader->enableWellData( false );
|
|
mockReader->open( "", mockCase.caseData.p() );
|
|
mockCase.caseData->mainGrid()->computeCachedData();
|
|
|
|
mockCase.caseData->setAvailablePhases( phases );
|
|
mockCase.resultCase->setReservoirData( mockCase.caseData.p() );
|
|
|
|
return mockCase;
|
|
}
|
|
|
|
void addStaticResult( RigCaseCellResultsData* cellResults, const QString& resultName, const std::vector<double>& values )
|
|
{
|
|
RigEclipseResultAddress addr( RiaDefines::ResultCatType::STATIC_NATIVE, resultName );
|
|
cellResults->createResultEntry( addr, false );
|
|
cellResults->setTimeStepInfos( addr, { RigEclipseTimeStepInfo( QDateTime(), 0, 0 ) } );
|
|
|
|
auto* timeStepValues = cellResults->modifiableCellScalarResultTimesteps( addr );
|
|
timeStepValues->resize( 1 );
|
|
( *timeStepValues )[0] = values;
|
|
}
|
|
|
|
void addDynamicResult( RigCaseCellResultsData* cellResults, const QString& resultName, const std::vector<std::vector<double>>& valuesForEachTimeStep )
|
|
{
|
|
RigEclipseResultAddress addr( RiaDefines::ResultCatType::DYNAMIC_NATIVE, resultName );
|
|
cellResults->createResultEntry( addr, false );
|
|
|
|
std::vector<RigEclipseTimeStepInfo> timeStepInfos;
|
|
for ( int i = 0; i < static_cast<int>( valuesForEachTimeStep.size() ); i++ )
|
|
{
|
|
timeStepInfos.push_back( RigEclipseTimeStepInfo( QDateTime(), i, i ) );
|
|
}
|
|
cellResults->setTimeStepInfos( addr, timeStepInfos );
|
|
|
|
auto* timeStepValues = cellResults->modifiableCellScalarResultTimesteps( addr );
|
|
timeStepValues->resize( valuesForEachTimeStep.size() );
|
|
for ( size_t i = 0; i < valuesForEachTimeStep.size(); i++ )
|
|
{
|
|
( *timeStepValues )[i] = valuesForEachTimeStep[i];
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Reproduces the per-time-step loading pattern used by RimEclipseStatisticsCaseEvaluator when computing
|
|
/// ensemble grid statistics (P10/P50/Mean/P90). Each time step is requested individually via
|
|
/// findOrLoadKnownScalarResultForTimeStep(), instead of loading all time steps at once.
|
|
//--------------------------------------------------------------------------------------------------
|
|
TEST( RigPorvSoilSgasResultCalculatorTest, ComputeRiPorvSoilPerTimeStep )
|
|
{
|
|
MockCase mockCase =
|
|
createMockCase( { RiaDefines::PhaseType::OIL_PHASE, RiaDefines::PhaseType::GAS_PHASE, RiaDefines::PhaseType::WATER_PHASE } );
|
|
|
|
RigCaseCellResultsData* cellResults = mockCase.caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
ASSERT_NE( cellResults, nullptr );
|
|
|
|
const size_t cellCount = cellResults->activeCellInfo()->reservoirActiveCellCount();
|
|
ASSERT_GT( cellCount, 0u );
|
|
|
|
addStaticResult( cellResults, RiaResultNames::porv(), std::vector<double>( cellCount, 10.0 ) );
|
|
|
|
const std::vector<std::vector<double>> soilValues = { std::vector<double>( cellCount, 0.2 ), std::vector<double>( cellCount, 0.4 ) };
|
|
addDynamicResult( cellResults, RiaResultNames::soil(), soilValues );
|
|
|
|
cellResults->createPlaceholderResultEntries();
|
|
|
|
const RigEclipseResultAddress riPorvSoilAddr( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSoil() );
|
|
ASSERT_TRUE( cellResults->hasResultEntry( riPorvSoilAddr ) );
|
|
|
|
// Mimic RimEclipseStatisticsCaseEvaluator::evaluateForResults(), which requests one time step at a time.
|
|
for ( size_t timeStepIndex = 0; timeStepIndex < soilValues.size(); timeStepIndex++ )
|
|
{
|
|
size_t scalarResultIndex = cellResults->findOrLoadKnownScalarResultForTimeStep( riPorvSoilAddr, timeStepIndex );
|
|
EXPECT_NE( scalarResultIndex, cvf::UNDEFINED_SIZE_T );
|
|
|
|
const auto& riPorvSoilValues = cellResults->cellScalarResults( riPorvSoilAddr, timeStepIndex );
|
|
ASSERT_EQ( riPorvSoilValues.size(), cellCount ) << "No values computed for time step " << timeStepIndex;
|
|
|
|
for ( size_t i = 0; i < cellCount; i++ )
|
|
{
|
|
EXPECT_DOUBLE_EQ( riPorvSoilValues[i], 10.0 * soilValues[timeStepIndex][i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Reproduces issue #14703: computing ensemble grid statistics (P10/P50/Mean/P90 per cell across
|
|
/// realizations) for riPORV*SOIL produces no results, even though PORV and SOIL separately work.
|
|
//--------------------------------------------------------------------------------------------------
|
|
TEST( RigPorvSoilSgasResultCalculatorTest, EnsembleStatisticsForRiPorvSoil )
|
|
{
|
|
std::vector<MockCase> sourceMockCases;
|
|
for ( int caseIdx = 0; caseIdx < 2; caseIdx++ )
|
|
{
|
|
MockCase mockCase =
|
|
createMockCase( { RiaDefines::PhaseType::OIL_PHASE, RiaDefines::PhaseType::GAS_PHASE, RiaDefines::PhaseType::WATER_PHASE } );
|
|
|
|
RigCaseCellResultsData* cellResults = mockCase.caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
const size_t cellCount = cellResults->activeCellInfo()->reservoirActiveCellCount();
|
|
ASSERT_GT( cellCount, 0u );
|
|
|
|
double porvValue = 10.0 + caseIdx;
|
|
addStaticResult( cellResults, RiaResultNames::porv(), std::vector<double>( cellCount, porvValue ) );
|
|
|
|
double soilValueT0 = 0.2 + 0.1 * caseIdx;
|
|
double soilValueT1 = 0.4 + 0.1 * caseIdx;
|
|
addDynamicResult( cellResults,
|
|
RiaResultNames::soil(),
|
|
{ std::vector<double>( cellCount, soilValueT0 ), std::vector<double>( cellCount, soilValueT1 ) } );
|
|
|
|
cellResults->createPlaceholderResultEntries();
|
|
|
|
sourceMockCases.push_back( std::move( mockCase ) );
|
|
}
|
|
|
|
std::vector<RimEclipseCase*> sourceCases;
|
|
for ( auto& mockCase : sourceMockCases )
|
|
{
|
|
sourceCases.push_back( mockCase.resultCase.get() );
|
|
}
|
|
|
|
MockCase destinationMockCase = createMockCase( { RiaDefines::PhaseType::OIL_PHASE } );
|
|
|
|
RimStatisticsConfig statisticsConfig;
|
|
|
|
RigActiveCellInfo* unionOfMatrixActiveCells = destinationMockCase.caseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
RigActiveCellInfo* unionOfFractureActiveCells =
|
|
destinationMockCase.caseData->activeCellInfo( RiaDefines::PorosityModelType::FRACTURE_MODEL );
|
|
|
|
RimEclipseStatisticsCaseEvaluator evaluator( sourceCases,
|
|
{ 0, 1 },
|
|
statisticsConfig,
|
|
destinationMockCase.caseData.p(),
|
|
unionOfMatrixActiveCells,
|
|
unionOfFractureActiveCells,
|
|
false );
|
|
|
|
QList<RimEclipseStatisticsCaseEvaluator::ResSpec> resultSpecification;
|
|
resultSpecification.push_back( RimEclipseStatisticsCaseEvaluator::ResSpec( RiaDefines::PorosityModelType::MATRIX_MODEL,
|
|
RiaDefines::ResultCatType::DYNAMIC_NATIVE,
|
|
RiaResultNames::riPorvSoil() ) );
|
|
|
|
evaluator.evaluateForResults( resultSpecification, nullptr );
|
|
|
|
RigCaseCellResultsData* destinationCellResults = destinationMockCase.caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
|
|
const RigEclipseResultAddress meanAddr( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::riPorvSoil() + "_MEAN" );
|
|
ASSERT_TRUE( destinationCellResults->hasResultEntry( meanAddr ) );
|
|
|
|
const size_t cellCount = destinationCellResults->activeCellInfo()->reservoirActiveCellCount();
|
|
ASSERT_GT( cellCount, 0u );
|
|
|
|
for ( size_t timeStepIndex = 0; timeStepIndex < 2; timeStepIndex++ )
|
|
{
|
|
const auto& meanValues = destinationCellResults->cellScalarResults( meanAddr, timeStepIndex );
|
|
ASSERT_EQ( meanValues.size(), cellCount ) << "No statistics computed for time step " << timeStepIndex;
|
|
|
|
for ( size_t i = 0; i < cellCount; i++ )
|
|
{
|
|
EXPECT_NE( meanValues[i], HUGE_VAL ) << "Undefined riPORV*SOIL statistics value at cell " << i << ", time step " << timeStepIndex;
|
|
}
|
|
}
|
|
}
|