#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:
Kristian Bendiksen committed 2026-03-20 14:30:34 +01:00
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";
}
}
}
@@ -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