2025-10-01 09:59:45 +02:00
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
//
|
|
|
|
|
// Copyright (C) 2025- 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 "RiaTestDataDirectory.h"
|
|
|
|
|
#include "RifEclipseInputFileTools.h"
|
|
|
|
|
#include "RifOpmFlowDeckFile.h"
|
|
|
|
|
#include "RifReaderEclipseOutput.h"
|
|
|
|
|
#include "RigEclipseResultTools.h"
|
|
|
|
|
|
2025-10-24 14:48:10 +02:00
|
|
|
#include "ProjectDataModel/Jobs/RimKeywordFactory.h"
|
|
|
|
|
|
2025-10-01 09:59:45 +02:00
|
|
|
#include "RigActiveCellInfo.h"
|
|
|
|
|
#include "RigCaseCellResultsData.h"
|
|
|
|
|
#include "RigEclipseCaseData.h"
|
|
|
|
|
#include "RigEclipseResultAddress.h"
|
|
|
|
|
#include "RigMainGrid.h"
|
|
|
|
|
#include "RimEclipseResultCase.h"
|
|
|
|
|
|
2025-10-24 14:48:10 +02:00
|
|
|
#include "opm/input/eclipse/Deck/DeckKeyword.hpp"
|
|
|
|
|
|
2025-10-01 09:59:45 +02:00
|
|
|
#include <QDir>
|
|
|
|
|
#include <QFile>
|
|
|
|
|
#include <QTemporaryDir>
|
|
|
|
|
#include <QTextStream>
|
|
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
|
/// Test BCCON generation from border cells
|
|
|
|
|
///
|
|
|
|
|
/// This test verifies that:
|
|
|
|
|
/// 1. We can generate BORDNUM result identifying border and interior cells
|
|
|
|
|
/// 2. We can extract border cell faces that connect to interior cells
|
|
|
|
|
/// 3. We can write BCCON keyword to an OPM deck file
|
|
|
|
|
///
|
|
|
|
|
/// Test process:
|
|
|
|
|
/// 1. Load reek test model
|
|
|
|
|
/// 2. Create custom visibility for a subset of cells (a box region)
|
|
|
|
|
/// 3. Generate BORDNUM result
|
|
|
|
|
/// 4. Generate border cell faces
|
|
|
|
|
/// 5. Verify border cells and faces are correctly identified
|
|
|
|
|
/// 6. Create OPM deck and add BCCON keyword
|
|
|
|
|
/// 7. Save and verify deck file format
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
|
TEST( RigEclipseResultToolsTest, BorderCellBcconGeneration )
|
|
|
|
|
{
|
|
|
|
|
// Setup test data directory - use BRUGGE model like other tests
|
|
|
|
|
QDir baseFolder( TEST_MODEL_DIR );
|
|
|
|
|
bool subFolderExists = baseFolder.cd( "Case_with_10_timesteps/Real0" );
|
|
|
|
|
ASSERT_TRUE( subFolderExists ) << "Test model directory not found";
|
|
|
|
|
|
|
|
|
|
QString inputFilename( "BRUGGE_0000.EGRID" );
|
|
|
|
|
QString inputFilePath = baseFolder.absoluteFilePath( inputFilename );
|
|
|
|
|
ASSERT_TRUE( QFile::exists( inputFilePath ) ) << "Test model file not found: " << inputFilePath.toStdString();
|
|
|
|
|
|
|
|
|
|
// Step 1: Load original grid using RifReaderEclipseOutput (properly initializes everything)
|
|
|
|
|
std::unique_ptr<RimEclipseResultCase> testCase( new RimEclipseResultCase );
|
|
|
|
|
cvf::ref<RigEclipseCaseData> caseData = new RigEclipseCaseData( testCase.get() );
|
|
|
|
|
|
|
|
|
|
cvf::ref<RifReaderEclipseOutput> readerInterfaceEcl = new RifReaderEclipseOutput;
|
|
|
|
|
bool success = readerInterfaceEcl->open( inputFilePath, caseData.p() );
|
|
|
|
|
ASSERT_TRUE( success ) << "Failed to load grid";
|
|
|
|
|
|
|
|
|
|
testCase->setReservoirData( caseData.p() );
|
|
|
|
|
|
|
|
|
|
const RigMainGrid* grid = caseData->mainGrid();
|
|
|
|
|
ASSERT_NE( grid, nullptr ) << "Grid is null";
|
|
|
|
|
|
|
|
|
|
// Step 2: Create custom visibility for a subset of cells (e.g., middle 50% of grid)
|
|
|
|
|
size_t cellCount = grid->cellCount();
|
|
|
|
|
|
|
|
|
|
cvf::ref<cvf::UByteArray> customVisibility = new cvf::UByteArray( cellCount );
|
|
|
|
|
customVisibility->setAll( 0 ); // Start with all invisible
|
|
|
|
|
|
|
|
|
|
std::vector<int> bcconValues( cellCount, 0 );
|
|
|
|
|
|
|
|
|
|
// Make a box in the middle visible
|
|
|
|
|
size_t startI = grid->cellCountI() / 4;
|
|
|
|
|
size_t endI = 3 * grid->cellCountI() / 4;
|
|
|
|
|
size_t startJ = grid->cellCountJ() / 4;
|
|
|
|
|
size_t endJ = 3 * grid->cellCountJ() / 4;
|
|
|
|
|
size_t startK = grid->cellCountK() / 4;
|
|
|
|
|
size_t endK = 3 * grid->cellCountK() / 4;
|
|
|
|
|
|
|
|
|
|
for ( size_t i = startI; i < endI; ++i )
|
|
|
|
|
{
|
|
|
|
|
for ( size_t j = startJ; j < endJ; ++j )
|
|
|
|
|
{
|
|
|
|
|
for ( size_t k = startK; k < endK; ++k )
|
|
|
|
|
{
|
|
|
|
|
size_t cellIndex = grid->cellIndexFromIJK( i, j, k );
|
|
|
|
|
( *customVisibility )[cellIndex] = 1;
|
|
|
|
|
|
|
|
|
|
// Set bccon==1 for half of the cells
|
|
|
|
|
if ( i > grid->cellCountI() / 2 )
|
|
|
|
|
{
|
|
|
|
|
bcconValues[cellIndex] = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 3: Generate BORDNUM result
|
|
|
|
|
RigEclipseResultTools::generateBorderResult( testCase.get(), customVisibility, "BORDNUM" );
|
|
|
|
|
|
|
|
|
|
// Verify BORDNUM was created
|
|
|
|
|
auto resultsData = testCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
|
|
|
ASSERT_NE( resultsData, nullptr );
|
|
|
|
|
|
|
|
|
|
RigEclipseResultAddress bordNumAddr( RiaDefines::ResultCatType::GENERATED, RiaDefines::ResultDataType::INTEGER, "BORDNUM" );
|
|
|
|
|
ASSERT_TRUE( resultsData->hasResultEntry( bordNumAddr ) ) << "BORDNUM result not created";
|
|
|
|
|
|
|
|
|
|
RigEclipseResultTools::createResultVector( *testCase, "BCCON", bcconValues );
|
|
|
|
|
|
|
|
|
|
// Step 4: Generate border cell faces
|
|
|
|
|
auto borderCellFaces = RigEclipseResultTools::generateBorderCellFaces( testCase.get() );
|
|
|
|
|
|
|
|
|
|
// Step 5: Verify results
|
|
|
|
|
EXPECT_GT( borderCellFaces.size(), 0 ) << "No border cell faces generated";
|
|
|
|
|
|
|
|
|
|
// Verify that all faces are within the visible region boundaries
|
|
|
|
|
for ( const auto& face : borderCellFaces )
|
|
|
|
|
{
|
|
|
|
|
EXPECT_GE( face.ijk[0], startI );
|
|
|
|
|
EXPECT_LT( face.ijk[0], endI );
|
|
|
|
|
EXPECT_GE( face.ijk[1], startJ );
|
|
|
|
|
EXPECT_LT( face.ijk[1], endJ );
|
|
|
|
|
EXPECT_GE( face.ijk[2], startK );
|
|
|
|
|
EXPECT_LT( face.ijk[2], endK );
|
|
|
|
|
|
|
|
|
|
// Verify face type is valid
|
|
|
|
|
EXPECT_GE( static_cast<int>( face.faceType ), static_cast<int>( cvf::StructGridInterface::POS_I ) );
|
|
|
|
|
EXPECT_LT( static_cast<int>( face.faceType ), static_cast<int>( cvf::StructGridInterface::NO_FACE ) );
|
|
|
|
|
|
|
|
|
|
// Verify boundary condition (should be 1 if no BCCON grid property exists)
|
|
|
|
|
EXPECT_EQ( face.boundaryCondition, 1 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 6: Create OPM deck file and add BCCON keyword
|
|
|
|
|
QTemporaryDir tempDir;
|
|
|
|
|
ASSERT_TRUE( tempDir.isValid() ) << "Failed to create temporary directory";
|
|
|
|
|
|
|
|
|
|
// Create a minimal deck file with GRID section
|
|
|
|
|
QString deckFilePath = tempDir.filePath( "test_bccon.DATA" );
|
|
|
|
|
{
|
|
|
|
|
QFile deckFile( deckFilePath );
|
|
|
|
|
ASSERT_TRUE( deckFile.open( QIODevice::WriteOnly | QIODevice::Text ) );
|
|
|
|
|
QTextStream out( &deckFile );
|
|
|
|
|
out << "RUNSPEC\n\n";
|
|
|
|
|
out << "DIMENS\n";
|
|
|
|
|
out << grid->cellCountI() << " " << grid->cellCountJ() << " " << grid->cellCountK() << " /\n\n";
|
|
|
|
|
out << "GRID\n\n";
|
|
|
|
|
out << "-- BCCON will be added here\n\n";
|
|
|
|
|
out << "PROPS\n\n";
|
|
|
|
|
out << "SCHEDULE\n\n";
|
|
|
|
|
deckFile.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load the deck file
|
|
|
|
|
RifOpmFlowDeckFile deckFile;
|
|
|
|
|
bool deckLoaded = deckFile.loadDeck( deckFilePath.toStdString() );
|
|
|
|
|
ASSERT_TRUE( deckLoaded ) << "Failed to load deck file";
|
|
|
|
|
|
2025-10-24 14:48:10 +02:00
|
|
|
// Create BCCON keyword using factory and replace in deck
|
|
|
|
|
Opm::DeckKeyword bcconKw = RimKeywordFactory::bcconKeyword( borderCellFaces );
|
|
|
|
|
bool bcconAdded = deckFile.replaceKeyword( "GRID", bcconKw );
|
|
|
|
|
ASSERT_TRUE( bcconAdded ) << "Failed to replace BCCON keyword";
|
2025-10-01 09:59:45 +02:00
|
|
|
|
|
|
|
|
// Step 7: Save deck and verify format
|
|
|
|
|
QString outputDeckPath = tempDir.filePath( "output_bccon.DATA" );
|
|
|
|
|
bool deckSaved = deckFile.saveDeck( tempDir.path().toStdString(), "output_bccon.DATA" );
|
|
|
|
|
ASSERT_TRUE( deckSaved ) << "Failed to save deck file";
|
|
|
|
|
ASSERT_TRUE( QFile::exists( outputDeckPath ) ) << "Output deck file not created";
|
|
|
|
|
|
|
|
|
|
// Read and verify BCCON content
|
|
|
|
|
QFile outputFile( outputDeckPath );
|
|
|
|
|
ASSERT_TRUE( outputFile.open( QIODevice::ReadOnly | QIODevice::Text ) );
|
|
|
|
|
QString content = QTextStream( &outputFile ).readAll();
|
|
|
|
|
outputFile.close();
|
|
|
|
|
|
|
|
|
|
// Verify BCCON keyword is present
|
|
|
|
|
EXPECT_TRUE( content.contains( "BCCON" ) ) << "BCCON keyword not found in output";
|
|
|
|
|
|
|
|
|
|
// Verify at least one entry exists
|
|
|
|
|
// BCCON format: counter i1 i2 j1 j2 k1 k2 face
|
|
|
|
|
// Should have face directions like X, X-, Y, Y-, Z, Z-
|
|
|
|
|
bool hasFaceDirection = content.contains( "X-" ) || content.contains( "Y-" ) || content.contains( "Z-" ) || content.contains( " X " ) ||
|
|
|
|
|
content.contains( " Y " ) || content.contains( " Z " );
|
|
|
|
|
EXPECT_TRUE( hasFaceDirection ) << "BCCON entries don't contain face directions";
|
|
|
|
|
}
|