Files
ResInsight/ApplicationLibCode/UnitTests/RigPorvSoilSgasResultCalculator-Test.cpp
T
Magne Sjaastad 8776a2e2bf #14703 Grid statistics: Fix missing results for computed dynamic results like riPORV*SOIL
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.
2026-09-12 04:53:16 -04:00

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;
}
}
}