Files
ResInsight/ApplicationLibCode/UnitTests/RimCellFilterTools-Test.cpp
T

326 lines
13 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 "RiaTestDataDirectory.h"
#include "RigActiveCellInfo.h"
#include "RigCaseCellResultsData.h"
#include "RigEclipseCaseData.h"
#include "RigEclipseResultAddress.h"
#include "RigMainGrid.h"
#include "RimCellFilterTools.h"
#include "RimCellRangeFilter.h"
#include "RimDataFilterCollection.h"
#include "RimDataFilterInView.h"
#include "RimDataFilterInViewCollection.h"
#include "RimEclipsePropertyFilter.h"
#include "RimEclipseResultCase.h"
#include "RimEclipseResultDefinition.h"
#include "RimEclipseView.h"
#include "RimReservoirGridEnsemble.h"
#include "cafPdmField.h"
#include "cafPdmUiDoubleSliderEditor.h"
#include "cafPdmUiLineEditor.h"
#include "cafPdmUiObjectHandle.h"
#include "cafPdmUiOrdering.h"
#include <QDir>
#include <QFile>
#include <cmath>
#include <memory>
static std::unique_ptr<RimEclipseResultCase> openBruggeCase( const QString& realizationFolder, const QString& fileName )
{
QDir baseFolder( TEST_MODEL_DIR );
if ( !baseFolder.cd( QString( "Case_with_10_timesteps/%1" ).arg( realizationFolder ) ) ) return nullptr;
QString filePath = baseFolder.absoluteFilePath( fileName );
if ( !QFile::exists( filePath ) ) return nullptr;
auto eclipseCase = std::make_unique<RimEclipseResultCase>();
eclipseCase->setCaseInfo( realizationFolder, filePath );
if ( !eclipseCase->openEclipseGridFile() ) return nullptr;
return eclipseCase;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RimCellFilterToolsTest, RangeFilterVisibility )
{
auto eclipseCase = openBruggeCase( "Real0", "BRUGGE_0000.EGRID" );
ASSERT_TRUE( eclipseCase != nullptr );
RimCellRangeFilter rangeFilter;
rangeFilter.setCase( eclipseCase.get() );
rangeFilter.startIndexI = 10;
rangeFilter.startIndexJ = 20;
rangeFilter.startIndexK = 1;
rangeFilter.cellCountI = 5;
rangeFilter.cellCountJ = 4;
rangeFilter.cellCountK = 3;
auto visibility = RimCellFilterTools::computeReservoirCellVisibility( &rangeFilter, eclipseCase.get(), 0 );
ASSERT_TRUE( visibility.notNull() );
const RigMainGrid* mainGrid = eclipseCase->eclipseCaseData()->mainGrid();
ASSERT_EQ( mainGrid->totalCellCount(), visibility->size() );
size_t visibleCount = 0;
for ( size_t i = 0; i < visibility->size(); i++ )
{
if ( visibility->val( i ) ) visibleCount++;
}
// The Brugge grid has no LGRs, so the geometric mask is exactly the IJK box
EXPECT_EQ( size_t( 5 * 4 * 3 ), visibleCount );
// An EXCLUDE filter must select the complement
rangeFilter.setFilterMode( RimCellFilter::EXCLUDE );
auto excludeVisibility = RimCellFilterTools::computeReservoirCellVisibility( &rangeFilter, eclipseCase.get(), 0 );
ASSERT_TRUE( excludeVisibility.notNull() );
size_t excludeVisibleCount = 0;
for ( size_t i = 0; i < excludeVisibility->size(); i++ )
{
if ( excludeVisibility->val( i ) ) excludeVisibleCount++;
}
EXPECT_EQ( visibility->size() - visibleCount, excludeVisibleCount );
}
//--------------------------------------------------------------------------------------------------
/// Data filters in a grid ensemble collection are bound to the main case of the ensemble, and can
/// be evaluated per case without any view
//--------------------------------------------------------------------------------------------------
TEST( RimCellFilterToolsTest, GridEnsembleDataFilterCollection )
{
auto eclipseCase = openBruggeCase( "Real0", "BRUGGE_0000.EGRID" );
ASSERT_TRUE( eclipseCase != nullptr );
auto ensemble = std::make_unique<RimReservoirGridEnsemble>();
// The ensemble takes ownership of the case
RimEclipseResultCase* mainCase = eclipseCase.release();
ensemble->addCase( mainCase );
ASSERT_TRUE( ensemble->dataFilterCollection() != nullptr );
// New filters in the ensemble collection are bound to the main case of the ensemble
auto* propertyFilter = ensemble->dataFilterCollection()->addNewPropertyFilter();
ASSERT_TRUE( propertyFilter != nullptr );
EXPECT_EQ( mainCase, propertyFilter->resultDefinition()->eclipseCase() );
auto* lowerField = dynamic_cast<caf::PdmField<double>*>( propertyFilter->findField( "LowerBound" ) );
auto* upperField = dynamic_cast<caf::PdmField<double>*>( propertyFilter->findField( "UpperBound" ) );
ASSERT_TRUE( lowerField && upperField );
caf::PdmUiOrdering uiOrdering;
propertyFilter->uiCapability()->uiOrdering( "", uiOrdering );
EXPECT_DOUBLE_EQ( 0.0, lowerField->value() );
EXPECT_DOUBLE_EQ( 1.0, upperField->value() );
EXPECT_EQ( caf::PdmUiLineEditor::uiEditorTypeName(), lowerField->uiCapability()->uiEditorTypeName( "" ) );
EXPECT_EQ( caf::PdmUiLineEditor::uiEditorTypeName(), upperField->uiCapability()->uiEditorTypeName( "" ) );
lowerField->setValue( -100.0 );
upperField->setValue( 100.0 );
propertyFilter->resultDefinition()->setResultType( RiaDefines::ResultCatType::DYNAMIC_NATIVE );
propertyFilter->resultDefinition()->setResultVariable( RiaResultNames::swat() );
propertyFilter->computeResultValueRange();
EXPECT_DOUBLE_EQ( -100.0, lowerField->value() );
EXPECT_DOUBLE_EQ( 100.0, upperField->value() );
propertyFilter->resultDefinition()->setResultVariable( "DX" );
propertyFilter->setToDefaultValues();
EXPECT_DOUBLE_EQ( 0.0, lowerField->value() );
EXPECT_DOUBLE_EQ( 1.0, upperField->value() );
auto* rangeFilter = ensemble->dataFilterCollection()->addNewRangeFilter();
ASSERT_TRUE( rangeFilter != nullptr );
rangeFilter->startIndexI = 1;
rangeFilter->startIndexJ = 1;
rangeFilter->startIndexK = 1;
rangeFilter->cellCountI = 10;
rangeFilter->cellCountJ = 10;
rangeFilter->cellCountK = 1;
auto visibility = RimCellFilterTools::computeReservoirCellVisibility( rangeFilter, mainCase, 0 );
ASSERT_TRUE( visibility.notNull() );
size_t visibleCount = 0;
for ( size_t i = 0; i < visibility->size(); i++ )
{
if ( visibility->val( i ) ) visibleCount++;
}
EXPECT_EQ( size_t( 10 * 10 * 1 ), visibleCount );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RimCellFilterToolsTest, EnsembleDataFilterInViewUsesViewCase )
{
auto caseA = openBruggeCase( "Real0", "BRUGGE_0000.EGRID" );
auto caseB = openBruggeCase( "Real40", "BRUGGE_0040.EGRID" );
ASSERT_TRUE( caseA != nullptr );
ASSERT_TRUE( caseB != nullptr );
auto ensemble = std::make_unique<RimReservoirGridEnsemble>();
RimEclipseResultCase* mainCase = caseA.release();
RimEclipseResultCase* viewCase = caseB.release();
ensemble->addCase( mainCase );
ensemble->addCase( viewCase );
auto* view = new RimEclipseView();
view->setEclipseCase( viewCase );
ensemble->addView( view );
auto* propertyFilter = ensemble->dataFilterCollection()->addNewPropertyFilter();
ASSERT_TRUE( propertyFilter != nullptr );
EXPECT_EQ( mainCase, propertyFilter->resultDefinition()->eclipseCase() );
propertyFilter->resultDefinition()->setResultType( RiaDefines::ResultCatType::DYNAMIC_NATIVE );
propertyFilter->resultDefinition()->setResultVariable( RiaResultNames::swat() );
auto* lowerField = dynamic_cast<caf::PdmField<double>*>( propertyFilter->findField( "LowerBound" ) );
auto* upperField = dynamic_cast<caf::PdmField<double>*>( propertyFilter->findField( "UpperBound" ) );
ASSERT_TRUE( lowerField && upperField );
lowerField->setValue( 0.3 );
upperField->setValue( 0.6 );
auto wrappers = view->dataFiltersInView()->wrappers();
ASSERT_EQ( 1u, wrappers.size() );
wrappers.front()->setCheckState( true );
const size_t timeStepIndex = 5;
auto expectedViewCaseVisibility = RimCellFilterTools::computeReservoirCellVisibility( propertyFilter, viewCase, timeStepIndex );
auto mainCaseVisibility = RimCellFilterTools::computeReservoirCellVisibility( propertyFilter, mainCase, timeStepIndex );
ASSERT_TRUE( expectedViewCaseVisibility.notNull() );
ASSERT_TRUE( mainCaseVisibility.notNull() );
const RigMainGrid* viewGrid = viewCase->eclipseCaseData()->mainGrid();
cvf::UByteArray viewVisibility( viewGrid->cellCount() );
viewVisibility.setAll( true );
wrappers.front()->applyToCellVisibility( &viewVisibility, viewGrid, timeStepIndex );
ASSERT_EQ( expectedViewCaseVisibility->size(), viewVisibility.size() );
ASSERT_EQ( mainCaseVisibility->size(), viewVisibility.size() );
size_t differingFromMainCase = 0;
for ( size_t i = 0; i < viewVisibility.size(); i++ )
{
EXPECT_EQ( expectedViewCaseVisibility->val( i ), viewVisibility.val( i ) );
if ( mainCaseVisibility->val( i ) != viewVisibility.val( i ) ) differingFromMainCase++;
}
EXPECT_GT( differingFromMainCase, 0u );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RimCellFilterToolsTest, PropertyFilterVisibilityPerCase )
{
auto caseA = openBruggeCase( "Real0", "BRUGGE_0000.EGRID" );
auto caseB = openBruggeCase( "Real40", "BRUGGE_0040.EGRID" );
ASSERT_TRUE( caseA != nullptr );
ASSERT_TRUE( caseB != nullptr );
const double lowerBound = 0.3;
const double upperBound = 0.6;
// Property filter bound to case A
RimEclipsePropertyFilter propertyFilter;
propertyFilter.setCase( caseA.get() );
propertyFilter.resultDefinition()->setEclipseCase( caseA.get() );
propertyFilter.resultDefinition()->setResultType( RiaDefines::ResultCatType::DYNAMIC_NATIVE );
propertyFilter.resultDefinition()->setResultVariable( RiaResultNames::swat() );
auto* lowerField = dynamic_cast<caf::PdmField<double>*>( propertyFilter.findField( "LowerBound" ) );
auto* upperField = dynamic_cast<caf::PdmField<double>*>( propertyFilter.findField( "UpperBound" ) );
ASSERT_TRUE( lowerField && upperField );
propertyFilter.setToDefaultValues();
caf::PdmUiOrdering uiOrdering;
propertyFilter.uiCapability()->uiOrdering( "", uiOrdering );
EXPECT_LE( lowerField->value(), upperField->value() );
EXPECT_EQ( caf::PdmUiDoubleSliderEditor::uiEditorTypeName(), lowerField->uiCapability()->uiEditorTypeName( "" ) );
EXPECT_EQ( caf::PdmUiDoubleSliderEditor::uiEditorTypeName(), upperField->uiCapability()->uiEditorTypeName( "" ) );
lowerField->setValue( lowerBound );
upperField->setValue( upperBound );
const size_t timeStepIndex = 5;
auto countVisible = []( const cvf::UByteArray* visibility )
{
size_t count = 0;
for ( size_t i = 0; i < visibility->size(); i++ )
{
if ( visibility->val( i ) ) count++;
}
return count;
};
// Reference: count active cells with SWAT inside the bounds, using the case data directly
auto expectedVisibleCount = [&]( RimEclipseCase* eclipseCase )
{
auto porosityModel = RiaDefines::PorosityModelType::MATRIX_MODEL;
auto results = eclipseCase->results( porosityModel );
RigEclipseResultAddress swatAddress( RiaDefines::ResultCatType::DYNAMIC_NATIVE, RiaResultNames::swat() );
results->ensureKnownResultLoaded( swatAddress );
const auto& values = results->cellScalarResults( swatAddress, timeStepIndex );
size_t count = 0;
for ( double value : values )
{
if ( lowerBound <= value && value <= upperBound ) count++;
}
return count;
};
auto visibilityA = RimCellFilterTools::computeReservoirCellVisibility( &propertyFilter, caseA.get(), timeStepIndex );
auto visibilityB = RimCellFilterTools::computeReservoirCellVisibility( &propertyFilter, caseB.get(), timeStepIndex );
ASSERT_TRUE( visibilityA.notNull() );
ASSERT_TRUE( visibilityB.notNull() );
// The same filter definition must be evaluated against each case's own result values
EXPECT_EQ( expectedVisibleCount( caseA.get() ), countVisible( visibilityA.p() ) );
EXPECT_EQ( expectedVisibleCount( caseB.get() ), countVisible( visibilityB.p() ) );
// The realizations have different SWAT fields, so the masks must differ
ASSERT_EQ( visibilityA->size(), visibilityB->size() );
size_t differingCells = 0;
for ( size_t i = 0; i < visibilityA->size(); i++ )
{
if ( visibilityA->val( i ) != visibilityB->val( i ) ) differingCells++;
}
EXPECT_GT( differingCells, 0u );
}