mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-26 07:00:17 -05:00
#13411 Sector Model Export: crop data keywords inside BOX/ENDBOX to sector intersection
Data keywords (PORO, EQLNUM, etc.) inside BOX/ENDBOX contain values for the sub-box only. When BOX coordinates are transformed to sector coordinates, the data size must match the transformed box dimensions. Crop boxed data keywords to the intersection of the BOX region with the sector, preserving the BOX/ENDBOX structure in the exported deck. If the box does not intersect the sector, the data keyword is removed.
This commit is contained in:
1 parent
ff1018a65f
commit
fce37b8fdb
4 files changed
+1147
-6
No files matched your search
@@ -101,6 +101,8 @@ std::expected<void, QString> RigSimulationInputTool::exportSimulationInput( RimE
|
||||
return result;
|
||||
}
|
||||
|
||||
cropDataKeywordsInsideBoxContext( deckFile, settings.min(), settings.max(), settings.refinement() );
|
||||
|
||||
auto croppedKeywords = cropDataKeywordsInDeckFile( &eclipseCase, settings, deckFile );
|
||||
|
||||
if ( auto result = replaceKeywordValuesInDeckFile( &eclipseCase, settings, deckFile, croppedKeywords ); !result )
|
||||
@@ -108,6 +110,10 @@ std::expected<void, QString> RigSimulationInputTool::exportSimulationInput( RimE
|
||||
return result;
|
||||
}
|
||||
|
||||
// Pre-process BOX/ENDBOX context: inject BOX indices into EQUALS/COPY/ADD/MULTIPLY records
|
||||
// that don't have explicit box indices, so they get properly transformed by subsequent steps.
|
||||
expandBoxContextInDeckFile( deckFile );
|
||||
|
||||
if ( auto result = replaceEqualsKeywordIndices( &eclipseCase, settings, deckFile ); !result )
|
||||
{
|
||||
return result;
|
||||
@@ -555,6 +561,312 @@ std::vector<int> RigSimulationInputTool::createRefinedVisibility( const RigGridE
|
||||
return refinedVisibility;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Check if a record (EQUALS/COPY/ADD/MULTIPLY) has explicit box indices at items 2-7
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static bool recordHasExplicitBoxIndices( const Opm::DeckRecord& record )
|
||||
{
|
||||
return record.size() >= 8 && record.getItem( 2 ).hasValue( 0 ) && record.getItem( 3 ).hasValue( 0 ) && record.getItem( 4 ).hasValue( 0 ) &&
|
||||
record.getItem( 5 ).hasValue( 0 ) && record.getItem( 6 ).hasValue( 0 ) && record.getItem( 7 ).hasValue( 0 );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Create a new record with the original items 0-1 plus injected box indices
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static Opm::DeckRecord injectBoxIndicesIntoRecord( const Opm::DeckRecord& record, const std::array<int, 6>& boxIndices )
|
||||
{
|
||||
std::vector<Opm::DeckItem> items;
|
||||
items.push_back( record.getItem( 0 ) );
|
||||
items.push_back( record.getItem( 1 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "I1", boxIndices[0] ) );
|
||||
items.push_back( RifOpmDeckTools::item( "I2", boxIndices[1] ) );
|
||||
items.push_back( RifOpmDeckTools::item( "J1", boxIndices[2] ) );
|
||||
items.push_back( RifOpmDeckTools::item( "J2", boxIndices[3] ) );
|
||||
items.push_back( RifOpmDeckTools::item( "K1", boxIndices[4] ) );
|
||||
items.push_back( RifOpmDeckTools::item( "K2", boxIndices[5] ) );
|
||||
|
||||
return Opm::DeckRecord{ std::move( items ) };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Extract box indices (I1,I2,J1,J2,K1,K2) from items at positions 2-7 of a record
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static std::array<int, 6> extractBoxIndicesFromRecord( const Opm::DeckRecord& record )
|
||||
{
|
||||
return { record.getItem( 2 ).get<int>( 0 ),
|
||||
record.getItem( 3 ).get<int>( 0 ),
|
||||
record.getItem( 4 ).get<int>( 0 ),
|
||||
record.getItem( 5 ).get<int>( 0 ),
|
||||
record.getItem( 6 ).get<int>( 0 ),
|
||||
record.getItem( 7 ).get<int>( 0 ) };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Pre-processing step: expand BOX context into EQUALS/COPY/ADD/MULTIPLY records.
|
||||
/// For keywords inside a BOX/ENDBOX block, records without explicit box indices
|
||||
/// get the active BOX indices injected. This ensures all records have explicit
|
||||
/// indices before the individual keyword processing transforms them.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigSimulationInputTool::expandBoxContextInDeckFile( RifOpmFlowDeckFile& deckFile )
|
||||
{
|
||||
Opm::FileDeck* fd = deckFile.fileDeck();
|
||||
if ( !fd ) return {};
|
||||
|
||||
bool insideBox = false;
|
||||
std::array<int, 6> activeBoxIndices{};
|
||||
const std::set<std::string> affectedKeywords = { "EQUALS", "COPY", "ADD", "MULTIPLY" };
|
||||
|
||||
for ( auto it = fd->start(); it != fd->stop(); it++ )
|
||||
{
|
||||
const auto& kw = ( *fd )[it];
|
||||
const std::string& name = kw.name();
|
||||
|
||||
if ( name == "BOX" )
|
||||
{
|
||||
if ( insideBox )
|
||||
{
|
||||
RiaLogging::warning( "BOX keyword found while already inside a BOX context. Previous BOX will be overridden." );
|
||||
}
|
||||
|
||||
if ( kw.size() > 0 )
|
||||
{
|
||||
const auto& rec = kw.getRecord( 0 );
|
||||
if ( rec.size() >= 6 )
|
||||
{
|
||||
activeBoxIndices = { rec.getItem( 0 ).get<int>( 0 ),
|
||||
rec.getItem( 1 ).get<int>( 0 ),
|
||||
rec.getItem( 2 ).get<int>( 0 ),
|
||||
rec.getItem( 3 ).get<int>( 0 ),
|
||||
rec.getItem( 4 ).get<int>( 0 ),
|
||||
rec.getItem( 5 ).get<int>( 0 ) };
|
||||
insideBox = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( name == "ENDBOX" )
|
||||
{
|
||||
if ( !insideBox )
|
||||
{
|
||||
RiaLogging::warning( "ENDBOX keyword found without a matching BOX." );
|
||||
}
|
||||
insideBox = false;
|
||||
}
|
||||
else if ( insideBox && affectedKeywords.count( name ) )
|
||||
{
|
||||
// Check if any record in this keyword needs expansion
|
||||
bool needsExpansion = false;
|
||||
for ( size_t r = 0; r < kw.size(); ++r )
|
||||
{
|
||||
if ( !recordHasExplicitBoxIndices( kw.getRecord( r ) ) )
|
||||
{
|
||||
needsExpansion = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( needsExpansion )
|
||||
{
|
||||
Opm::DeckKeyword expandedKw( kw.location(), kw.name() );
|
||||
|
||||
// Track "current operation box" - starts with active BOX indices
|
||||
std::array<int, 6> currentOpBox = activeBoxIndices;
|
||||
|
||||
for ( size_t r = 0; r < kw.size(); ++r )
|
||||
{
|
||||
const auto& rec = kw.getRecord( r );
|
||||
if ( recordHasExplicitBoxIndices( rec ) )
|
||||
{
|
||||
// Update current operation box from this record's explicit indices
|
||||
currentOpBox = extractBoxIndicesFromRecord( rec );
|
||||
Opm::DeckRecord copy( rec );
|
||||
expandedKw.addRecord( std::move( copy ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inject current operation box indices into this record
|
||||
expandedKw.addRecord( injectBoxIndicesIntoRecord( rec, currentOpBox ) );
|
||||
}
|
||||
}
|
||||
|
||||
deckFile.replaceKeywordAtIndex( it, expandedKw );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( insideBox )
|
||||
{
|
||||
RiaLogging::warning( "BOX keyword found without a matching ENDBOX at end of deck." );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Crop data keywords (PORO, EQLNUM, PERMX, etc.) inside BOX/ENDBOX blocks to the intersection
|
||||
/// of the BOX region with the sector. If the box does not intersect the sector, the data keyword
|
||||
/// is removed. This preserves the BOX/ENDBOX structure in the output while ensuring data sizes
|
||||
/// match the transformed box dimensions.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigSimulationInputTool::cropDataKeywordsInsideBoxContext( RifOpmFlowDeckFile& deckFile,
|
||||
const caf::VecIjk0& sectorMin,
|
||||
const caf::VecIjk0& sectorMax,
|
||||
const cvf::Vec3st& refinement )
|
||||
{
|
||||
Opm::FileDeck* fd = deckFile.fileDeck();
|
||||
if ( !fd ) return;
|
||||
|
||||
struct Modification
|
||||
{
|
||||
Opm::FileDeck::Index index;
|
||||
bool remove = false;
|
||||
Opm::DeckKeyword replacement;
|
||||
};
|
||||
|
||||
bool insideBox = false;
|
||||
caf::VecIjk0 boxMin( 0, 0, 0 );
|
||||
caf::VecIjk0 boxMax( 0, 0, 0 );
|
||||
std::vector<Modification> modifications;
|
||||
|
||||
for ( auto it = fd->start(); it != fd->stop(); it++ )
|
||||
{
|
||||
const auto& kw = ( *fd )[it];
|
||||
const std::string& name = kw.name();
|
||||
|
||||
if ( name == "BOX" )
|
||||
{
|
||||
insideBox = true;
|
||||
if ( kw.size() > 0 )
|
||||
{
|
||||
const auto& rec = kw.getRecord( 0 );
|
||||
if ( rec.size() >= 6 )
|
||||
{
|
||||
// BOX items: I1, I2, J1, J2, K1, K2 (1-based)
|
||||
boxMin = caf::VecIjk0( rec.getItem( 0 ).get<int>( 0 ) - 1,
|
||||
rec.getItem( 2 ).get<int>( 0 ) - 1,
|
||||
rec.getItem( 4 ).get<int>( 0 ) - 1 );
|
||||
boxMax = caf::VecIjk0( rec.getItem( 1 ).get<int>( 0 ) - 1,
|
||||
rec.getItem( 3 ).get<int>( 0 ) - 1,
|
||||
rec.getItem( 5 ).get<int>( 0 ) - 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( name == "ENDBOX" )
|
||||
{
|
||||
insideBox = false;
|
||||
}
|
||||
else if ( insideBox && kw.isDataKeyword() )
|
||||
{
|
||||
// Compute intersection of box with sector (0-based, inclusive)
|
||||
caf::VecIjk0 intMin( std::max( boxMin.x(), sectorMin.x() ),
|
||||
std::max( boxMin.y(), sectorMin.y() ),
|
||||
std::max( boxMin.z(), sectorMin.z() ) );
|
||||
caf::VecIjk0 intMax( std::min( boxMax.x(), sectorMax.x() ),
|
||||
std::min( boxMax.y(), sectorMax.y() ),
|
||||
std::min( boxMax.z(), sectorMax.z() ) );
|
||||
|
||||
if ( intMin.x() > intMax.x() || intMin.y() > intMax.y() || intMin.z() > intMax.z() )
|
||||
{
|
||||
// No intersection - remove the data keyword
|
||||
modifications.push_back( { it, true, Opm::DeckKeyword{} } );
|
||||
RiaLogging::info( QString( "Removing data keyword '%1' inside BOX/ENDBOX: box does not intersect sector" )
|
||||
.arg( QString::fromStdString( name ) ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( kw.size() != 1 || kw.getRecord( 0 ).size() != 1 ) continue;
|
||||
|
||||
const auto& item = kw.getRecord( 0 ).getItem( 0 );
|
||||
|
||||
// Box dimensions (source grid for the data)
|
||||
const size_t boxNi = boxMax.x() - boxMin.x() + 1;
|
||||
const size_t boxNj = boxMax.y() - boxMin.y() + 1;
|
||||
|
||||
auto cropBoxData = [&]<typename T>( const std::vector<T>& sourceData )
|
||||
{
|
||||
std::vector<T> croppedData;
|
||||
|
||||
// Iterate over refined intersection cells
|
||||
for ( size_t k = intMin.z(); k <= static_cast<size_t>( intMax.z() ); ++k )
|
||||
{
|
||||
for ( size_t rk = 0; rk < refinement.z(); ++rk )
|
||||
{
|
||||
for ( size_t j = intMin.y(); j <= static_cast<size_t>( intMax.y() ); ++j )
|
||||
{
|
||||
for ( size_t rj = 0; rj < refinement.y(); ++rj )
|
||||
{
|
||||
for ( size_t i = intMin.x(); i <= static_cast<size_t>( intMax.x() ); ++i )
|
||||
{
|
||||
// Box-relative indices
|
||||
size_t boxRelI = i - boxMin.x();
|
||||
size_t boxRelJ = j - boxMin.y();
|
||||
size_t boxRelK = k - boxMin.z();
|
||||
size_t srcIdx = boxRelI + boxRelJ * boxNi + boxRelK * boxNi * boxNj;
|
||||
|
||||
for ( size_t ri = 0; ri < refinement.x(); ++ri )
|
||||
{
|
||||
croppedData.push_back( sourceData[srcIdx] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build replacement keyword
|
||||
Opm::DeckKeyword newKw = kw.emptyStructuralCopy();
|
||||
Opm::DeckRecord record;
|
||||
|
||||
if constexpr ( std::is_same_v<T, int> )
|
||||
{
|
||||
Opm::DeckItem newItem( name, int() );
|
||||
for ( const auto& v : croppedData )
|
||||
newItem.push_back( v );
|
||||
record.addItem( std::move( newItem ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<Opm::Dimension> activeDim;
|
||||
std::vector<Opm::Dimension> defaultDim;
|
||||
Opm::DeckItem newItem( name, double(), activeDim, defaultDim );
|
||||
for ( const auto& v : croppedData )
|
||||
newItem.push_back( v );
|
||||
record.addItem( std::move( newItem ) );
|
||||
}
|
||||
|
||||
newKw.addRecord( std::move( record ) );
|
||||
modifications.push_back( { it, false, std::move( newKw ) } );
|
||||
|
||||
RiaLogging::info( QString( "Cropped data keyword '%1' inside BOX/ENDBOX (%2 -> %3 values)" )
|
||||
.arg( QString::fromStdString( name ) )
|
||||
.arg( sourceData.size() )
|
||||
.arg( croppedData.size() ) );
|
||||
};
|
||||
|
||||
if ( item.getType() == Opm::type_tag::integer )
|
||||
{
|
||||
cropBoxData( kw.getIntData() );
|
||||
}
|
||||
else if ( item.getType() == Opm::type_tag::fdouble )
|
||||
{
|
||||
cropBoxData( kw.getRawDoubleData() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply modifications in reverse order to maintain valid indices
|
||||
for ( auto it = modifications.rbegin(); it != modifications.rend(); ++it )
|
||||
{
|
||||
if ( it->remove )
|
||||
{
|
||||
deckFile.removeKeywordAtIndex( it->index );
|
||||
}
|
||||
else
|
||||
{
|
||||
deckFile.replaceKeywordAtIndex( it->index, it->replacement );
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Generic helper for processing keywords with box indices (EQUALS, COPY, MULTIPLY, ADD)
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -1125,9 +1437,9 @@ std::expected<Opm::DeckRecord, QString> RigSimulationInputTool::processMultiplyR
|
||||
{
|
||||
// MULTIPLY format: FIELD FACTOR I1 I2 J1 J2 K1 K2
|
||||
// Items: 0=FIELD, 1=FACTOR, 2=I1, 3=I2, 4=J1, 5=J2, 6=K1, 7=K2
|
||||
if ( record.size() < 8 )
|
||||
if ( record.size() < 2 )
|
||||
{
|
||||
return std::unexpected( QString( "MULTIPLY record has insufficient items (expected at least 8, got %1)" ).arg( record.size() ) );
|
||||
return std::unexpected( QString( "MULTIPLY record has insufficient items (expected at least 2, got %1)" ).arg( record.size() ) );
|
||||
}
|
||||
|
||||
std::vector<Opm::DeckItem> items;
|
||||
@@ -1136,7 +1448,7 @@ std::expected<Opm::DeckRecord, QString> RigSimulationInputTool::processMultiplyR
|
||||
items.push_back( record.getItem( 0 ) );
|
||||
items.push_back( record.getItem( 1 ) );
|
||||
|
||||
if ( record.getItem( 2 ).hasValue( 0 ) && record.getItem( 3 ).hasValue( 0 ) && record.getItem( 4 ).hasValue( 0 ) &&
|
||||
if ( record.size() >= 8 && record.getItem( 2 ).hasValue( 0 ) && record.getItem( 3 ).hasValue( 0 ) && record.getItem( 4 ).hasValue( 0 ) &&
|
||||
record.getItem( 5 ).hasValue( 0 ) && record.getItem( 6 ).hasValue( 0 ) && record.getItem( 7 ).hasValue( 0 ) )
|
||||
{
|
||||
// Transform IJK box coordinates (items 2-7: I1, I2, J1, J2, K1, K2)
|
||||
@@ -1180,9 +1492,9 @@ std::expected<Opm::DeckRecord, QString> RigSimulationInputTool::processAddRecord
|
||||
{
|
||||
// ADD format: FIELD SHIFT I1 I2 J1 J2 K1 K2
|
||||
// Items: 0=FIELD, 1=SHIFT, 2=I1, 3=I2, 4=J1, 5=J2, 6=K1, 7=K2
|
||||
if ( record.size() < 8 )
|
||||
if ( record.size() < 2 )
|
||||
{
|
||||
return std::unexpected( QString( "ADD record has insufficient items (expected at least 8, got %1)" ).arg( record.size() ) );
|
||||
return std::unexpected( QString( "ADD record has insufficient items (expected at least 2, got %1)" ).arg( record.size() ) );
|
||||
}
|
||||
|
||||
std::vector<Opm::DeckItem> items;
|
||||
@@ -1191,7 +1503,7 @@ std::expected<Opm::DeckRecord, QString> RigSimulationInputTool::processAddRecord
|
||||
items.push_back( record.getItem( 0 ) );
|
||||
items.push_back( record.getItem( 1 ) );
|
||||
|
||||
if ( record.getItem( 2 ).hasValue( 0 ) && record.getItem( 3 ).hasValue( 0 ) && record.getItem( 4 ).hasValue( 0 ) &&
|
||||
if ( record.size() >= 8 && record.getItem( 2 ).hasValue( 0 ) && record.getItem( 3 ).hasValue( 0 ) && record.getItem( 4 ).hasValue( 0 ) &&
|
||||
record.getItem( 5 ).hasValue( 0 ) && record.getItem( 6 ).hasValue( 0 ) && record.getItem( 7 ).hasValue( 0 ) )
|
||||
{
|
||||
// Transform IJK box coordinates (items 2-7: I1, I2, J1, J2, K1, K2)
|
||||
|
||||
@@ -102,6 +102,12 @@ public:
|
||||
|
||||
static std::vector<int> createRefinedVisibility( const RigGridExportAdapter& gridAdapter );
|
||||
|
||||
static void expandBoxContextInDeckFile( RifOpmFlowDeckFile& deckFile );
|
||||
static void cropDataKeywordsInsideBoxContext( RifOpmFlowDeckFile& deckFile,
|
||||
const caf::VecIjk0& sectorMin,
|
||||
const caf::VecIjk0& sectorMax,
|
||||
const cvf::Vec3st& refinement );
|
||||
|
||||
// Public EDITNNC methods for testing
|
||||
static std::vector<NNCConnection> extractDeckEditNncConnections( RifOpmFlowDeckFile& deckFile, const RigMainGrid& mainGrid );
|
||||
static std::vector<NNCConnection> filterInternalSectorConnections( const std::vector<NNCConnection>& allConnections,
|
||||
|
||||
@@ -1662,3 +1662,503 @@ TEST( RigSimulationInputTool, ExportModel5WithPadding )
|
||||
ASSERT_TRUE( actnumKw.has_value() );
|
||||
EXPECT_EQ( 4500u, actnumKw->getRecord( 0 ).getItem( 0 ).data_size() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Helper to create an ADD DeckRecord with box indices
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static Opm::DeckRecord createAddRecord( const std::string& fieldName, double shift, int i1, int i2, int j1, int j2, int k1, int k2 )
|
||||
{
|
||||
std::vector<Opm::DeckItem> items;
|
||||
items.push_back( RifOpmDeckTools::item( "FIELD", fieldName ) );
|
||||
items.push_back( RifOpmDeckTools::item( "SHIFT", shift ) );
|
||||
items.push_back( RifOpmDeckTools::item( "I1", i1 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "I2", i2 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "J1", j1 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "J2", j2 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "K1", k1 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "K2", k2 ) );
|
||||
|
||||
return Opm::DeckRecord{ std::move( items ) };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Helper to create an ADD DeckRecord without box indices
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static Opm::DeckRecord createAddRecordNoBox( const std::string& fieldName, double shift )
|
||||
{
|
||||
std::vector<Opm::DeckItem> items;
|
||||
items.push_back( RifOpmDeckTools::item( "FIELD", fieldName ) );
|
||||
items.push_back( RifOpmDeckTools::item( "SHIFT", shift ) );
|
||||
|
||||
return Opm::DeckRecord{ std::move( items ) };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Helper to create a MULTIPLY DeckRecord with box indices
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static Opm::DeckRecord createMultiplyRecord( const std::string& fieldName, double factor, int i1, int i2, int j1, int j2, int k1, int k2 )
|
||||
{
|
||||
std::vector<Opm::DeckItem> items;
|
||||
items.push_back( RifOpmDeckTools::item( "FIELD", fieldName ) );
|
||||
items.push_back( RifOpmDeckTools::item( "FACTOR", factor ) );
|
||||
items.push_back( RifOpmDeckTools::item( "I1", i1 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "I2", i2 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "J1", j1 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "J2", j2 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "K1", k1 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "K2", k2 ) );
|
||||
|
||||
return Opm::DeckRecord{ std::move( items ) };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Helper to create a MULTIPLY DeckRecord without box indices
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static Opm::DeckRecord createMultiplyRecordNoBox( const std::string& fieldName, double factor )
|
||||
{
|
||||
std::vector<Opm::DeckItem> items;
|
||||
items.push_back( RifOpmDeckTools::item( "FIELD", fieldName ) );
|
||||
items.push_back( RifOpmDeckTools::item( "FACTOR", factor ) );
|
||||
|
||||
return Opm::DeckRecord{ std::move( items ) };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test ADD record without explicit box indices - should pass through with just field+shift
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessAddRecord_NoBoxIndices )
|
||||
{
|
||||
caf::VecIjk0 sectorMin( 0, 0, 0 );
|
||||
caf::VecIjk0 sectorMax( 19, 29, 9 );
|
||||
cvf::Vec3st refinement( 1, 1, 1 );
|
||||
|
||||
auto record = createAddRecordNoBox( "PORO", 0.1 );
|
||||
|
||||
auto result = RigSimulationInputTool::processAddRecord( record, sectorMin, sectorMax, refinement );
|
||||
ASSERT_TRUE( result.has_value() );
|
||||
|
||||
// Should have only 2 items (field name and shift value, no box indices)
|
||||
EXPECT_EQ( 2u, result->size() );
|
||||
EXPECT_EQ( "PORO", result->getItem( 0 ).get<std::string>( 0 ) );
|
||||
EXPECT_DOUBLE_EQ( 0.1, result->getItem( 1 ).get<double>( 0 ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test ADD record with explicit box indices - should transform coordinates
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessAddRecord_WithBoxIndices )
|
||||
{
|
||||
// Sector: sectorMin=(0, 15, 0), sectorMax=(19, 29, 9)
|
||||
caf::VecIjk0 sectorMin( 0, 15, 0 );
|
||||
caf::VecIjk0 sectorMax( 19, 29, 9 );
|
||||
cvf::Vec3st refinement( 1, 1, 1 );
|
||||
|
||||
// ADD record with box fully inside sector (1-based Eclipse coords)
|
||||
auto record = createAddRecord( "PORO", 0.05, 1, 20, 16, 30, 1, 10 );
|
||||
|
||||
auto result = RigSimulationInputTool::processAddRecord( record, sectorMin, sectorMax, refinement );
|
||||
ASSERT_TRUE( result.has_value() );
|
||||
|
||||
// Should have 8 items with transformed coordinates
|
||||
EXPECT_EQ( 8u, result->size() );
|
||||
EXPECT_EQ( "PORO", result->getItem( 0 ).get<std::string>( 0 ) );
|
||||
|
||||
// Transformed: I stays same (1-20), J transforms from [16-30] to [1-15], K stays (1-10)
|
||||
EXPECT_EQ( 1, result->getItem( 2 ).get<int>( 0 ) ); // I1
|
||||
EXPECT_EQ( 20, result->getItem( 3 ).get<int>( 0 ) ); // I2
|
||||
EXPECT_EQ( 1, result->getItem( 4 ).get<int>( 0 ) ); // J1
|
||||
EXPECT_EQ( 15, result->getItem( 5 ).get<int>( 0 ) ); // J2
|
||||
EXPECT_EQ( 1, result->getItem( 6 ).get<int>( 0 ) ); // K1
|
||||
EXPECT_EQ( 10, result->getItem( 7 ).get<int>( 0 ) ); // K2
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test MULTIPLY record without explicit box indices - should pass through with just field+factor
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessMultiplyRecord_NoBoxIndices )
|
||||
{
|
||||
caf::VecIjk0 sectorMin( 0, 0, 0 );
|
||||
caf::VecIjk0 sectorMax( 19, 29, 9 );
|
||||
cvf::Vec3st refinement( 1, 1, 1 );
|
||||
|
||||
auto record = createMultiplyRecordNoBox( "PERMX", 2.0 );
|
||||
|
||||
auto result = RigSimulationInputTool::processMultiplyRecord( record, sectorMin, sectorMax, refinement );
|
||||
ASSERT_TRUE( result.has_value() );
|
||||
|
||||
// Should have only 2 items (field name and factor, no box indices)
|
||||
EXPECT_EQ( 2u, result->size() );
|
||||
EXPECT_EQ( "PERMX", result->getItem( 0 ).get<std::string>( 0 ) );
|
||||
EXPECT_DOUBLE_EQ( 2.0, result->getItem( 1 ).get<double>( 0 ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test MULTIPLY record with explicit box indices - should transform coordinates
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessMultiplyRecord_WithBoxIndices )
|
||||
{
|
||||
// Sector: sectorMin=(0, 15, 0), sectorMax=(19, 29, 9)
|
||||
caf::VecIjk0 sectorMin( 0, 15, 0 );
|
||||
caf::VecIjk0 sectorMax( 19, 29, 9 );
|
||||
cvf::Vec3st refinement( 1, 1, 1 );
|
||||
|
||||
// MULTIPLY record with box fully inside sector
|
||||
auto record = createMultiplyRecord( "PERMX", 3.0, 1, 20, 16, 30, 1, 10 );
|
||||
|
||||
auto result = RigSimulationInputTool::processMultiplyRecord( record, sectorMin, sectorMax, refinement );
|
||||
ASSERT_TRUE( result.has_value() );
|
||||
|
||||
// Should have 8 items with transformed coordinates
|
||||
EXPECT_EQ( 8u, result->size() );
|
||||
EXPECT_EQ( "PERMX", result->getItem( 0 ).get<std::string>( 0 ) );
|
||||
|
||||
// Transformed coordinates
|
||||
EXPECT_EQ( 1, result->getItem( 2 ).get<int>( 0 ) ); // I1
|
||||
EXPECT_EQ( 20, result->getItem( 3 ).get<int>( 0 ) ); // I2
|
||||
EXPECT_EQ( 1, result->getItem( 4 ).get<int>( 0 ) ); // J1
|
||||
EXPECT_EQ( 15, result->getItem( 5 ).get<int>( 0 ) ); // J2
|
||||
EXPECT_EQ( 1, result->getItem( 6 ).get<int>( 0 ) ); // K1
|
||||
EXPECT_EQ( 10, result->getItem( 7 ).get<int>( 0 ) ); // K2
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test expandBoxContextInDeckFile: EQUALS inside BOX/ENDBOX with no explicit indices
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ExpandBoxContext_EqualsInheritsBoxIndices )
|
||||
{
|
||||
// Create a minimal DATA file with BOX/ENDBOX containing EQUALS without indices
|
||||
QTemporaryDir tempDir;
|
||||
ASSERT_TRUE( tempDir.isValid() );
|
||||
|
||||
QString dataFilePath = tempDir.path() + "/test_box.DATA";
|
||||
{
|
||||
QFile file( dataFilePath );
|
||||
ASSERT_TRUE( file.open( QIODevice::WriteOnly | QIODevice::Text ) );
|
||||
file.write( "RUNSPEC\n\n" );
|
||||
file.write( "DIMENS\n 10 10 5 /\n\n" );
|
||||
file.write( "GRID\n\n" );
|
||||
file.write( "BOX\n 2 8 3 7 1 5 /\n\n" );
|
||||
file.write( "EQUALS\n 'PORO' 0.25 /\n/\n\n" );
|
||||
file.write( "ENDBOX\n\n" );
|
||||
file.close();
|
||||
}
|
||||
|
||||
RifOpmFlowDeckFile deckFile;
|
||||
auto loadResult = deckFile.loadDeck( dataFilePath.toStdString() );
|
||||
ASSERT_TRUE( loadResult.has_value() ) << loadResult.error();
|
||||
|
||||
// Verify EQUALS has no explicit box indices before expansion
|
||||
auto equalsBeforeVec = deckFile.findAllKeywordsWithIndices( "EQUALS" );
|
||||
ASSERT_EQ( 1u, equalsBeforeVec.size() );
|
||||
ASSERT_EQ( 1u, equalsBeforeVec[0].second.size() );
|
||||
// The record should have fewer than 8 items or items without values
|
||||
const auto& recBefore = equalsBeforeVec[0].second.getRecord( 0 );
|
||||
bool hadExplicitBefore =
|
||||
recBefore.size() >= 8 && recBefore.getItem( 2 ).hasValue( 0 ) && recBefore.getItem( 3 ).hasValue( 0 ) &&
|
||||
recBefore.getItem( 4 ).hasValue( 0 ) && recBefore.getItem( 5 ).hasValue( 0 ) && recBefore.getItem( 6 ).hasValue( 0 ) &&
|
||||
recBefore.getItem( 7 ).hasValue( 0 );
|
||||
EXPECT_FALSE( hadExplicitBefore ) << "Record should not have explicit box indices before expansion";
|
||||
|
||||
// Run expansion
|
||||
RigSimulationInputTool::expandBoxContextInDeckFile( deckFile );
|
||||
|
||||
// Verify EQUALS now has explicit box indices matching the BOX keyword
|
||||
auto equalsAfterVec = deckFile.findAllKeywordsWithIndices( "EQUALS" );
|
||||
ASSERT_EQ( 1u, equalsAfterVec.size() );
|
||||
ASSERT_EQ( 1u, equalsAfterVec[0].second.size() );
|
||||
|
||||
const auto& recAfter = equalsAfterVec[0].second.getRecord( 0 );
|
||||
ASSERT_GE( recAfter.size(), 8u );
|
||||
EXPECT_TRUE( recAfter.getItem( 2 ).hasValue( 0 ) );
|
||||
EXPECT_EQ( 2, recAfter.getItem( 2 ).get<int>( 0 ) ); // I1 from BOX
|
||||
EXPECT_EQ( 8, recAfter.getItem( 3 ).get<int>( 0 ) ); // I2 from BOX
|
||||
EXPECT_EQ( 3, recAfter.getItem( 4 ).get<int>( 0 ) ); // J1 from BOX
|
||||
EXPECT_EQ( 7, recAfter.getItem( 5 ).get<int>( 0 ) ); // J2 from BOX
|
||||
EXPECT_EQ( 1, recAfter.getItem( 6 ).get<int>( 0 ) ); // K1 from BOX
|
||||
EXPECT_EQ( 5, recAfter.getItem( 7 ).get<int>( 0 ) ); // K2 from BOX
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test expandBoxContextInDeckFile: records with explicit indices keep their own indices
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ExpandBoxContext_ExplicitIndicesPreserved )
|
||||
{
|
||||
QTemporaryDir tempDir;
|
||||
ASSERT_TRUE( tempDir.isValid() );
|
||||
|
||||
QString dataFilePath = tempDir.path() + "/test_box_explicit.DATA";
|
||||
{
|
||||
QFile file( dataFilePath );
|
||||
ASSERT_TRUE( file.open( QIODevice::WriteOnly | QIODevice::Text ) );
|
||||
file.write( "RUNSPEC\n\n" );
|
||||
file.write( "DIMENS\n 10 10 5 /\n\n" );
|
||||
file.write( "GRID\n\n" );
|
||||
file.write( "BOX\n 2 8 3 7 1 5 /\n\n" );
|
||||
file.write( "EQUALS\n 'PORO' 0.25 1 5 1 5 1 3 /\n/\n\n" );
|
||||
file.write( "ENDBOX\n\n" );
|
||||
file.close();
|
||||
}
|
||||
|
||||
RifOpmFlowDeckFile deckFile;
|
||||
auto loadResult = deckFile.loadDeck( dataFilePath.toStdString() );
|
||||
ASSERT_TRUE( loadResult.has_value() ) << loadResult.error();
|
||||
|
||||
RigSimulationInputTool::expandBoxContextInDeckFile( deckFile );
|
||||
|
||||
auto equalsVec = deckFile.findAllKeywordsWithIndices( "EQUALS" );
|
||||
ASSERT_EQ( 1u, equalsVec.size() );
|
||||
ASSERT_EQ( 1u, equalsVec[0].second.size() );
|
||||
|
||||
const auto& rec = equalsVec[0].second.getRecord( 0 );
|
||||
ASSERT_GE( rec.size(), 8u );
|
||||
// Should keep original explicit indices, not BOX indices
|
||||
EXPECT_EQ( 1, rec.getItem( 2 ).get<int>( 0 ) ); // I1
|
||||
EXPECT_EQ( 5, rec.getItem( 3 ).get<int>( 0 ) ); // I2
|
||||
EXPECT_EQ( 1, rec.getItem( 4 ).get<int>( 0 ) ); // J1
|
||||
EXPECT_EQ( 5, rec.getItem( 5 ).get<int>( 0 ) ); // J2
|
||||
EXPECT_EQ( 1, rec.getItem( 6 ).get<int>( 0 ) ); // K1
|
||||
EXPECT_EQ( 3, rec.getItem( 7 ).get<int>( 0 ) ); // K2
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test expandBoxContextInDeckFile: keywords outside BOX context are not modified
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ExpandBoxContext_OutsideBoxNotModified )
|
||||
{
|
||||
QTemporaryDir tempDir;
|
||||
ASSERT_TRUE( tempDir.isValid() );
|
||||
|
||||
QString dataFilePath = tempDir.path() + "/test_no_box.DATA";
|
||||
{
|
||||
QFile file( dataFilePath );
|
||||
ASSERT_TRUE( file.open( QIODevice::WriteOnly | QIODevice::Text ) );
|
||||
file.write( "RUNSPEC\n\n" );
|
||||
file.write( "DIMENS\n 10 10 5 /\n\n" );
|
||||
file.write( "GRID\n\n" );
|
||||
file.write( "EQUALS\n 'PORO' 0.25 /\n/\n\n" );
|
||||
file.close();
|
||||
}
|
||||
|
||||
RifOpmFlowDeckFile deckFile;
|
||||
auto loadResult = deckFile.loadDeck( dataFilePath.toStdString() );
|
||||
ASSERT_TRUE( loadResult.has_value() ) << loadResult.error();
|
||||
|
||||
RigSimulationInputTool::expandBoxContextInDeckFile( deckFile );
|
||||
|
||||
auto equalsVec = deckFile.findAllKeywordsWithIndices( "EQUALS" );
|
||||
ASSERT_EQ( 1u, equalsVec.size() );
|
||||
ASSERT_EQ( 1u, equalsVec[0].second.size() );
|
||||
|
||||
const auto& rec = equalsVec[0].second.getRecord( 0 );
|
||||
// Should still not have explicit box indices
|
||||
bool hasExplicit = rec.size() >= 8 && rec.getItem( 2 ).hasValue( 0 ) && rec.getItem( 3 ).hasValue( 0 ) && rec.getItem( 4 ).hasValue( 0 ) &&
|
||||
rec.getItem( 5 ).hasValue( 0 ) && rec.getItem( 6 ).hasValue( 0 ) && rec.getItem( 7 ).hasValue( 0 );
|
||||
EXPECT_FALSE( hasExplicit );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test cropDataKeywordsInsideBoxContext: data keywords inside BOX/ENDBOX are cropped
|
||||
/// to the intersection of the box with the sector
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, CropDataKeywordsInsideBoxContext )
|
||||
{
|
||||
// Grid: 10x10x5. BOX covers K=5 (1-based), i.e. K-index 4 (0-based).
|
||||
// Sector: (2,2,1)-(7,7,4) => covers part of the box region.
|
||||
// BOX: I[0-9], J[0-9], K[4-4] => 100 values
|
||||
// Intersection: I[2-7], J[2-7], K[4-4] => 6x6x1 = 36 values
|
||||
QTemporaryDir tempDir;
|
||||
ASSERT_TRUE( tempDir.isValid() );
|
||||
|
||||
QString dataFilePath = tempDir.path() + "/test_box_crop.DATA";
|
||||
{
|
||||
QFile file( dataFilePath );
|
||||
ASSERT_TRUE( file.open( QIODevice::WriteOnly | QIODevice::Text ) );
|
||||
file.write( "RUNSPEC\n\n" );
|
||||
file.write( "DIMENS\n 10 10 5 /\n\n" );
|
||||
file.write( "GRID\n\n" );
|
||||
file.write( "REGIONS\n\n" );
|
||||
file.write( "EQLNUM\n 500*1 /\n\n" );
|
||||
file.write( "BOX\n 1 10 1 10 5 5 /\n\n" );
|
||||
file.write( "EQLNUM\n 100*2 /\n\n" );
|
||||
file.write( "ENDBOX\n\n" );
|
||||
file.write( "SATNUM\n 500*1 /\n\n" );
|
||||
file.close();
|
||||
}
|
||||
|
||||
RifOpmFlowDeckFile deckFile;
|
||||
auto loadResult = deckFile.loadDeck( dataFilePath.toStdString() );
|
||||
ASSERT_TRUE( loadResult.has_value() ) << loadResult.error();
|
||||
|
||||
// Before cropping: should find two EQLNUM keywords
|
||||
auto eqlnumBefore = deckFile.findAllKeywordsWithIndices( "EQLNUM" );
|
||||
EXPECT_EQ( 2u, eqlnumBefore.size() );
|
||||
|
||||
// Run cropping with sector (2,2,1)-(7,7,4), no refinement
|
||||
caf::VecIjk0 sectorMin( 2, 2, 1 );
|
||||
caf::VecIjk0 sectorMax( 7, 7, 4 );
|
||||
cvf::Vec3st refinement( 1, 1, 1 );
|
||||
RigSimulationInputTool::cropDataKeywordsInsideBoxContext( deckFile, sectorMin, sectorMax, refinement );
|
||||
|
||||
// After cropping: should still find two EQLNUM keywords
|
||||
auto eqlnumAfter = deckFile.findAllKeywordsWithIndices( "EQLNUM" );
|
||||
ASSERT_EQ( 2u, eqlnumAfter.size() );
|
||||
|
||||
// First EQLNUM (full grid) should be unchanged (500 values)
|
||||
EXPECT_EQ( 500u, eqlnumAfter[0].second.getRecord( 0 ).getItem( 0 ).data_size() );
|
||||
|
||||
// Second EQLNUM (inside BOX) should be cropped to 6x6x1 = 36 values
|
||||
const auto& croppedKw = eqlnumAfter[1].second;
|
||||
EXPECT_EQ( 36u, croppedKw.getRecord( 0 ).getItem( 0 ).data_size() );
|
||||
|
||||
// All cropped values should still be 2
|
||||
const auto& croppedData = croppedKw.getIntData();
|
||||
for ( size_t i = 0; i < croppedData.size(); ++i )
|
||||
{
|
||||
EXPECT_EQ( 2, croppedData[i] ) << "Cropped EQLNUM value at index " << i << " should be 2";
|
||||
}
|
||||
|
||||
// SATNUM outside BOX should still be present and unchanged
|
||||
auto satnumAfter = deckFile.findAllKeywordsWithIndices( "SATNUM" );
|
||||
EXPECT_EQ( 1u, satnumAfter.size() );
|
||||
EXPECT_EQ( 500u, satnumAfter[0].second.getRecord( 0 ).getItem( 0 ).data_size() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test cropDataKeywordsInsideBoxContext: box that doesn't intersect sector removes keyword
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, CropDataKeywordsInsideBoxContext_NoIntersection )
|
||||
{
|
||||
// Grid: 10x10x5. BOX covers K=5 (1-based), i.e. K-index 4 (0-based).
|
||||
// Sector: (2,2,0)-(7,7,2) => does NOT include K=4, so no intersection.
|
||||
QTemporaryDir tempDir;
|
||||
ASSERT_TRUE( tempDir.isValid() );
|
||||
|
||||
QString dataFilePath = tempDir.path() + "/test_box_nointersect.DATA";
|
||||
{
|
||||
QFile file( dataFilePath );
|
||||
ASSERT_TRUE( file.open( QIODevice::WriteOnly | QIODevice::Text ) );
|
||||
file.write( "RUNSPEC\n\n" );
|
||||
file.write( "DIMENS\n 10 10 5 /\n\n" );
|
||||
file.write( "GRID\n\n" );
|
||||
file.write( "REGIONS\n\n" );
|
||||
file.write( "EQLNUM\n 500*1 /\n\n" );
|
||||
file.write( "BOX\n 1 10 1 10 5 5 /\n\n" );
|
||||
file.write( "EQLNUM\n 100*2 /\n\n" );
|
||||
file.write( "ENDBOX\n\n" );
|
||||
file.close();
|
||||
}
|
||||
|
||||
RifOpmFlowDeckFile deckFile;
|
||||
auto loadResult = deckFile.loadDeck( dataFilePath.toStdString() );
|
||||
ASSERT_TRUE( loadResult.has_value() ) << loadResult.error();
|
||||
|
||||
// Sector does not include K=4
|
||||
caf::VecIjk0 sectorMin( 2, 2, 0 );
|
||||
caf::VecIjk0 sectorMax( 7, 7, 2 );
|
||||
cvf::Vec3st refinement( 1, 1, 1 );
|
||||
RigSimulationInputTool::cropDataKeywordsInsideBoxContext( deckFile, sectorMin, sectorMax, refinement );
|
||||
|
||||
// Boxed EQLNUM should be removed since box doesn't intersect sector
|
||||
auto eqlnumAfter = deckFile.findAllKeywordsWithIndices( "EQLNUM" );
|
||||
ASSERT_EQ( 1u, eqlnumAfter.size() );
|
||||
EXPECT_EQ( 500u, eqlnumAfter[0].second.getRecord( 0 ).getItem( 0 ).data_size() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test exportSimulationInput with model5 BOX data: EQLNUM inside BOX/ENDBOX
|
||||
/// Verifies that the boxed EQLNUM is cropped to the sector/box intersection and the
|
||||
/// BOX/ENDBOX structure is preserved in the exported deck.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ExportModel5_DataKeywordInsideBox )
|
||||
{
|
||||
// Load model5 test data
|
||||
QDir baseFolder( TEST_DATA_DIR );
|
||||
bool subFolderExists = baseFolder.cd( "RigSimulationInputTool/model5" );
|
||||
ASSERT_TRUE( subFolderExists );
|
||||
|
||||
QString egridFilename( "0_BASE_MODEL5.EGRID" );
|
||||
QString egridFilePath = baseFolder.absoluteFilePath( egridFilename );
|
||||
ASSERT_TRUE( QFile::exists( egridFilePath ) );
|
||||
|
||||
QString dataFilename( "2_BOX_MODEL5.DATA" );
|
||||
QString dataFilePath = baseFolder.absoluteFilePath( dataFilename );
|
||||
ASSERT_TRUE( QFile::exists( dataFilePath ) );
|
||||
|
||||
// Create Eclipse case and load grid
|
||||
std::unique_ptr<RimEclipseResultCase> resultCase( new RimEclipseResultCase );
|
||||
cvf::ref<RigEclipseCaseData> caseData = new RigEclipseCaseData( resultCase.get() );
|
||||
|
||||
cvf::ref<RifReaderEclipseOutput> reader = new RifReaderEclipseOutput;
|
||||
bool loadResult = reader->open( egridFilePath, caseData.p() );
|
||||
ASSERT_TRUE( loadResult );
|
||||
|
||||
// Verify grid dimensions (20x30x10)
|
||||
ASSERT_TRUE( caseData->mainGrid() != nullptr );
|
||||
EXPECT_EQ( 20u, caseData->mainGrid()->cellCountI() );
|
||||
EXPECT_EQ( 30u, caseData->mainGrid()->cellCountJ() );
|
||||
EXPECT_EQ( 10u, caseData->mainGrid()->cellCountK() );
|
||||
|
||||
// Create temporary directory for export
|
||||
QTemporaryDir tempDir;
|
||||
ASSERT_TRUE( tempDir.isValid() );
|
||||
|
||||
QString exportFilePath = tempDir.path() + "/exported_box_model.DATA";
|
||||
|
||||
// Set up export settings: sector includes K=10 to verify BOX override
|
||||
// Sector: min=(5,5,2), max=(14,14,9) => 10x10x8 = 800 cells (includes K=10 which is K-index 9)
|
||||
// BOX in input is: I[1-20], J[1-30], K[10-10] (1-based) = I[0-19], J[0-29], K[9-9] (0-based)
|
||||
// Intersection with sector: I[5-14], J[5-14], K[9-9] => 10x10x1 = 100 cells
|
||||
RigSimulationInputSettings settings;
|
||||
settings.setMin( caf::VecIjk0( 5, 5, 2 ) );
|
||||
settings.setMax( caf::VecIjk0( 14, 14, 9 ) );
|
||||
settings.setRefinement( cvf::Vec3st( 1, 1, 1 ) );
|
||||
settings.setInputDeckFileName( dataFilePath );
|
||||
settings.setOutputDeckFileName( exportFilePath );
|
||||
|
||||
// Create visibility from IJK bounds
|
||||
cvf::ref<cvf::UByteArray> visibility =
|
||||
RigEclipseCaseDataTools::createVisibilityFromIjkBounds( caseData.p(), settings.min(), settings.max() );
|
||||
|
||||
// Export simulation input
|
||||
resultCase->setReservoirData( caseData.p() );
|
||||
auto exportResult = RigSimulationInputTool::exportSimulationInput( *resultCase, settings, visibility.p() );
|
||||
ASSERT_TRUE( exportResult.has_value() ) << "Export failed: " << exportResult.error().toStdString();
|
||||
|
||||
// Verify exported file exists
|
||||
ASSERT_TRUE( QFile::exists( exportFilePath ) );
|
||||
|
||||
// Load the exported deck file
|
||||
RifOpmFlowDeckFile deckFile;
|
||||
bool deckLoadResult = deckFile.loadDeck( exportFilePath.toStdString() ).has_value();
|
||||
ASSERT_TRUE( deckLoadResult ) << "Failed to load exported deck file";
|
||||
|
||||
const size_t sectorNx = 10;
|
||||
const size_t sectorNy = 10;
|
||||
const size_t sectorNz = 8;
|
||||
const size_t expectedCellCount = sectorNx * sectorNy * sectorNz; // 800 cells
|
||||
|
||||
// Both EQLNUM keywords should exist: the full-grid one (cropped to sector) and the boxed one (cropped to intersection)
|
||||
auto eqlnumAll = deckFile.findAllKeywordsWithIndices( "EQLNUM" );
|
||||
EXPECT_EQ( 2u, eqlnumAll.size() ) << "Should have two EQLNUM keywords (full-grid + cropped boxed)";
|
||||
|
||||
// First EQLNUM (full grid, cropped to sector): 10x10x8 = 800 values
|
||||
{
|
||||
const auto& firstKw = eqlnumAll[0].second;
|
||||
const auto& firstData = firstKw.getIntData();
|
||||
EXPECT_EQ( expectedCellCount, firstData.size() ) << "First EQLNUM should have " << expectedCellCount << " values";
|
||||
}
|
||||
|
||||
// Second EQLNUM (boxed, cropped to sector/box intersection): 10x10x1 = 100 values
|
||||
{
|
||||
const size_t expectedBoxedCount = sectorNx * sectorNy * 1; // 100 cells
|
||||
const auto& secondKw = eqlnumAll[1].second;
|
||||
const auto& secondData = secondKw.getIntData();
|
||||
EXPECT_EQ( expectedBoxedCount, secondData.size() ) << "Boxed EQLNUM should have " << expectedBoxedCount << " values";
|
||||
|
||||
// All boxed values should be 2 (the BOX override value)
|
||||
for ( size_t i = 0; i < secondData.size(); ++i )
|
||||
{
|
||||
EXPECT_EQ( 2, secondData[i] ) << "Boxed EQLNUM value at index " << i << " should be 2";
|
||||
}
|
||||
}
|
||||
}
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
-- This reservoir simulation deck is made available under the Open Database
|
||||
-- License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in
|
||||
-- individual contents of the database are licensed under the Database Contents
|
||||
-- License: http://opendatacommons.org/licenses/dbcl/1.0/
|
||||
|
||||
-- Copyright (C) 2020 Equinor
|
||||
|
||||
-- This is model5 with EQLNUM set partially via BOX/ENDBOX.
|
||||
-- Full grid EQLNUM=1 everywhere, then BOX overrides K=10 (last layer) to EQLNUM=2.
|
||||
--
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------
|
||||
RUNSPEC
|
||||
------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
DIMENS
|
||||
20 30 10 /
|
||||
|
||||
|
||||
OIL
|
||||
WATER
|
||||
GAS
|
||||
DISGAS
|
||||
--VAPOIL
|
||||
|
||||
METRIC
|
||||
|
||||
START
|
||||
01 'JAN' 2020 /
|
||||
|
||||
--
|
||||
GRIDOPTS
|
||||
'YES' 0 /
|
||||
|
||||
EQLDIMS
|
||||
2 1* 25 /
|
||||
|
||||
|
||||
REGDIMS
|
||||
-- max. ntfip nmfipr max. nrfreg max. ntfreg
|
||||
3 2 1* 2 /
|
||||
|
||||
--
|
||||
TABDIMS
|
||||
--ntsfun ntpvt max.nssfun max.nppvt max.ntfip max.nrpvt
|
||||
1 1 150 60 3 60 /
|
||||
|
||||
--
|
||||
WELLDIMS
|
||||
--max.well max.con/well max.grup max.w/grup
|
||||
10 15 9 10 /
|
||||
|
||||
--FLOW THP WCT GCT ALQ VFP
|
||||
VFPPDIMS
|
||||
22 13 10 13 13 50 /
|
||||
|
||||
|
||||
|
||||
UNIFIN
|
||||
UNIFOUT
|
||||
|
||||
------------------------------------------------------------------------------------------------
|
||||
GRID
|
||||
------------------------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
NEWTRAN
|
||||
|
||||
--
|
||||
GRIDFILE
|
||||
0 1 /
|
||||
|
||||
--
|
||||
GRIDUNIT
|
||||
METRES /
|
||||
|
||||
--
|
||||
INIT
|
||||
|
||||
|
||||
INCLUDE
|
||||
'include/test1_20x30x10.grdecl' /
|
||||
|
||||
INCLUDE
|
||||
'include/permx_model5.grdecl' /
|
||||
|
||||
|
||||
PORO
|
||||
6000*0.28 /
|
||||
|
||||
COPY
|
||||
PERMX PERMY /
|
||||
PERMX PERMZ /
|
||||
/
|
||||
|
||||
MULTIPLY
|
||||
PERMZ 0.1 /
|
||||
/
|
||||
|
||||
RPTGRID
|
||||
'ALLNNC' /
|
||||
|
||||
EQUALS
|
||||
'MULTY' 0.01 1 20 14 14 1 10 /
|
||||
/
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------
|
||||
EDIT
|
||||
------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------
|
||||
PROPS
|
||||
------------------------------------------------------------------------------------------------
|
||||
|
||||
NOECHO
|
||||
|
||||
INCLUDE
|
||||
'include/pvt_live_oil_dgas.ecl' /
|
||||
|
||||
|
||||
INCLUDE
|
||||
'include/rock.inc' /
|
||||
|
||||
INCLUDE
|
||||
'include/relperm.inc' /
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------
|
||||
REGIONS
|
||||
------------------------------------------------------------------------------------------------
|
||||
|
||||
-- Default EQLNUM=1 for all 6000 cells
|
||||
EQLNUM
|
||||
6000*1 /
|
||||
|
||||
-- Override last K-layer (K=10, 1-based) to EQLNUM=2 via BOX
|
||||
BOX
|
||||
1 20 1 30 10 10 /
|
||||
EQLNUM
|
||||
600*2 /
|
||||
ENDBOX
|
||||
|
||||
EQUALS
|
||||
FIPNUM 1 1 20 1 14 1 10 /
|
||||
FIPNUM 2 1 20 15 30 1 10 /
|
||||
/
|
||||
|
||||
SATNUM
|
||||
6000*1 /
|
||||
|
||||
-- custom region
|
||||
FIPABC
|
||||
2000*1 2000*2 2000*3 /
|
||||
|
||||
------------------------------------------------------------------------------------------------
|
||||
SOLUTION
|
||||
------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
RPTRST
|
||||
'BASIC = 2' 'PBPD' /
|
||||
|
||||
EQUIL
|
||||
-- Datum P woc Pc goc Pc Rsvd Rvvd
|
||||
2000.00 195.0 2070 0.0 500.00 0.0 1 0 0 /
|
||||
2000.00 195.0 2070 0.0 500.00 0.0 1 0 0 /
|
||||
|
||||
PBVD
|
||||
2000.00 75.00
|
||||
2150.00 75.00 /
|
||||
2000.00 75.00
|
||||
2150.00 75.00 /
|
||||
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------
|
||||
SUMMARY
|
||||
------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
INCLUDE
|
||||
'include/summary.inc' /
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------
|
||||
SCHEDULE
|
||||
------------------------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- FIELD
|
||||
-- |
|
||||
-- PLAT-A
|
||||
-- ---------------+---------------------
|
||||
-- | |
|
||||
-- M5S M5N
|
||||
-- ---------+---------- -----+-------
|
||||
-- | | | |
|
||||
-- B1 G1 C1 F1
|
||||
-- ----+------ ---+--- ---+--- ---+---
|
||||
-- | | | | | | | | |
|
||||
-- B-1H B-2H B-3H G-3H G-4H C-1H C-2H F-1H F-2H
|
||||
--
|
||||
|
||||
TUNING
|
||||
0.5 1 /
|
||||
/
|
||||
2* 50 1* 20 /
|
||||
|
||||
NUPCOL
|
||||
4 /
|
||||
|
||||
|
||||
GRUPTREE
|
||||
'PROD' 'FIELD' /
|
||||
|
||||
'M5S' 'PLAT-A' /
|
||||
'M5N' 'PLAT-A' /
|
||||
|
||||
'C1' 'M5N' /
|
||||
'F1' 'M5N' /
|
||||
'B1' 'M5S' /
|
||||
'G1' 'M5S' /
|
||||
/
|
||||
|
||||
RPTRST
|
||||
'BASIC=2' /
|
||||
|
||||
|
||||
INCLUDE
|
||||
'include/well_vfp.ecl' /
|
||||
|
||||
INCLUDE
|
||||
'include/flowl_b_vfp.ecl' /
|
||||
|
||||
INCLUDE
|
||||
'include/flowl_c_vfp.ecl' /
|
||||
|
||||
|
||||
WELSPECS
|
||||
--WELL GROUP IHEEL JHEEL DREF PHASE DRAD INFEQ SIINS XFLOW PRTAB DENS
|
||||
'B-1H' 'B1' 11 3 1* OIL 1* 1* SHUT 1* 1* 1* /
|
||||
'B-2H' 'B1' 4 7 1* OIL 1* 1* SHUT 1* 1* 1* /
|
||||
'B-3H' 'B1' 11 12 1* OIL 1* 1* SHUT 1* 1* 1* /
|
||||
'C-1H' 'C1' 13 20 1* OIL 1* 1* SHUT 1* 1* 1* /
|
||||
'C-2H' 'C1' 12 27 1* OIL 1* 1* SHUT 1* 1* 1* /
|
||||
/
|
||||
|
||||
WELSPECS
|
||||
'F-1H' 'F1' 19 4 1* WATER 1* 1* SHUT 1* 1* 1* /
|
||||
'F-2H' 'F1' 19 12 1* WATER 1* 1* SHUT 1* 1* 1* /
|
||||
'G-3H' 'G1' 19 21 1* WATER 1* 1* SHUT 1* 1* 1* /
|
||||
'G-4H' 'G1' 19 25 1* WATER 1* 1* SHUT 1* 1* 1* /
|
||||
/
|
||||
|
||||
COMPDAT
|
||||
--WELL I J K1 K2 OP/SH SATN TRAN WBDIA KH SKIN DFACT DIR PEQVR
|
||||
'B-1H' 11 3 1 5 OPEN 1* 1* 0.216 1* 0 1* Z 1* /
|
||||
'B-2H' 4 7 1 6 OPEN 1* 1* 0.216 1* 0 1* Z 1* /
|
||||
'B-3H' 11 12 1 4 OPEN 1* 1* 0.216 1* 0 1* Z 1* /
|
||||
'C-1H' 13 20 1 4 OPEN 1* 1* 0.216 1* 0 1* Z 1* /
|
||||
'C-2H' 12 27 1 5 OPEN 1* 1* 0.216 1* 0 1* Z 1* /
|
||||
/
|
||||
|
||||
COMPDAT
|
||||
'F-1H' 19 4 6 10 OPEN 1* 1* 0.216 1* 0 1* Z 1* /
|
||||
'F-2H' 19 12 6 10 OPEN 1* 1* 0.216 1* 0 1* Z 1* /
|
||||
'G-3H' 19 21 6 10 OPEN 1* 1* 0.216 1* 0 1* Z 1* /
|
||||
'G-4H' 19 25 6 10 OPEN 1* 1* 0.216 1* 0 1* Z 1* /
|
||||
/
|
||||
|
||||
WCONPROD
|
||||
-- Well_name Status Ctrl Orate Wrate Grate Lrate RFV FBHP WHP VFP Glift
|
||||
'B-1H' OPEN ORAT 1500.0 1* 1* 3000.0 1* 100.0 30 1 1* /
|
||||
'B-2H' SHUT ORAT 1500.0 1* 1* 3000.0 1* 100.0 30 1 1* /
|
||||
'B-3H' OPEN ORAT 1500.0 1* 1* 3000.0 1* 100.0 30 1 1* /
|
||||
'C-1H' OPEN ORAT 1500.0 1* 1* 3000.0 1* 100.0 30 1 1* /
|
||||
'C-2H' SHUT ORAT 1500.0 1* 1* 3000.0 1* 100.0 30 1 1* /
|
||||
/
|
||||
|
||||
GCONINJE
|
||||
'FIELD' 'WATER' 'VREP' 3* 1.020 'NO' 5* /
|
||||
/
|
||||
|
||||
|
||||
WCONINJE
|
||||
-- Well_name Type Status Ctrl SRate1 Rrate BHP THP VFP
|
||||
'F-1H' WATER OPEN GRUP 4000 1* 225.0 1* 1* /
|
||||
'F-2H' WATER OPEN GRUP 4000 1* 225.0 1* 1* /
|
||||
'G-3H' WATER OPEN GRUP 4000 1* 225.0 1* 1* /
|
||||
'G-4H' WATER OPEN GRUP 4000 1* 225.0 1* 1* /
|
||||
/
|
||||
|
||||
TSTEP
|
||||
0.5 /
|
||||
|
||||
|
||||
DATES
|
||||
1 FEB 2020 /
|
||||
/
|
||||
|
||||
|
||||
WELOPEN
|
||||
'B-2H' OPEN /
|
||||
/
|
||||
|
||||
DATES
|
||||
1 MAR 2020 /
|
||||
/
|
||||
|
||||
WELOPEN
|
||||
'C-2H' OPEN /
|
||||
/
|
||||
|
||||
DATES
|
||||
1 APR 2020 /
|
||||
1 MAY 2020 /
|
||||
/
|
||||
|
||||
END
|
||||
Reference in new issue
Block a user