mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
Simulation Input: Handle AQUNUM keyword.
This commit is contained in:
1 parent
5c273c0130
commit
ab708f5e0e
3 files changed
+263
No files matched your search
@@ -134,6 +134,11 @@ std::expected<void, QString> RigSimulationInputTool::exportSimulationInput( RimE
|
||||
return result;
|
||||
}
|
||||
|
||||
if ( auto result = replaceAquconKeywordIndices( &eclipseCase, settings, deckFile ); !result )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if ( auto result = updateWelldimsKeyword( &eclipseCase, settings, deckFile ); !result )
|
||||
{
|
||||
return result;
|
||||
@@ -463,6 +468,16 @@ std::expected<void, QString> RigSimulationInputTool::replaceAddKeywordIndices( R
|
||||
return replaceKeywordWithBoxIndices( "ADD", eclipseCase, settings, deckFile, processAddRecord );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::expected<void, QString> RigSimulationInputTool::replaceAquconKeywordIndices( RimEclipseCase* eclipseCase,
|
||||
const RigSimulationInputSettings& settings,
|
||||
RifOpmFlowDeckFile& deckFile )
|
||||
{
|
||||
return replaceKeywordWithBoxIndices( "AQUCON", eclipseCase, settings, deckFile, processAquconRecord );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -1072,6 +1087,128 @@ std::expected<Opm::DeckRecord, QString> RigSimulationInputTool::processAddRecord
|
||||
return Opm::DeckRecord{ std::move( items ) };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::expected<Opm::DeckRecord, QString> RigSimulationInputTool::processAquconRecord( const Opm::DeckRecord& record,
|
||||
const caf::VecIjk0& min,
|
||||
const caf::VecIjk0& max,
|
||||
const cvf::Vec3st& refinement )
|
||||
{
|
||||
// AQUCON format: ID I1 I2 J1 J2 K1 K2 CONNECT_FACE ...
|
||||
// Items: 0=ID, 1=I1, 2=I2, 3=J1, 4=J2, 5=K1, 6=K2, 7+=other parameters
|
||||
if ( record.size() < 1 )
|
||||
{
|
||||
return std::unexpected( QString( "AQUCON record has insufficient items (expected at least 1, got %1)" ).arg( record.size() ) );
|
||||
}
|
||||
|
||||
std::vector<Opm::DeckItem> items;
|
||||
|
||||
// Copy aquifer ID (first item)
|
||||
items.push_back( record.getItem( 0 ) );
|
||||
|
||||
if ( record.size() >= 7 && record.getItem( 1 ).hasValue( 0 ) && 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 ) )
|
||||
{
|
||||
// Transform IJK box coordinates (items 1-6: I1, I2, J1, J2, K1, K2)
|
||||
// Note: AQUCON uses 1-based Eclipse coordinates
|
||||
int origI1 = record.getItem( 1 ).get<int>( 0 ) - 1; // Convert to 0-based
|
||||
int origI2 = record.getItem( 2 ).get<int>( 0 ) - 1;
|
||||
int origJ1 = record.getItem( 3 ).get<int>( 0 ) - 1;
|
||||
int origJ2 = record.getItem( 4 ).get<int>( 0 ) - 1;
|
||||
int origK1 = record.getItem( 5 ).get<int>( 0 ) - 1;
|
||||
int origK2 = record.getItem( 6 ).get<int>( 0 ) - 1;
|
||||
|
||||
// Create bounding boxes (both use inclusive min/max coordinates)
|
||||
RigBoundingBoxIjk aquconBox( cvf::Vec3st( origI1, origJ1, origK1 ), cvf::Vec3st( origI2, origJ2, origK2 ) );
|
||||
RigBoundingBoxIjk sectorBox( cvf::Vec3st( min.x(), min.y(), min.z() ), cvf::Vec3st( max.x(), max.y(), max.z() ) );
|
||||
|
||||
// Check if boxes overlap and get intersection
|
||||
auto intersection = aquconBox.intersection( sectorBox );
|
||||
if ( !intersection )
|
||||
{
|
||||
// No overlap with sector - skip this record
|
||||
int aquiferId = record.getItem( 0 ).get<int>( 0 );
|
||||
RiaLogging::info( QString( "AQUCON record for aquifer %1 [%2-%3, %4-%5, %6-%7] does not overlap with sector - skipping" )
|
||||
.arg( aquiferId )
|
||||
.arg( origI1 + 1 )
|
||||
.arg( origI2 + 1 )
|
||||
.arg( origJ1 + 1 )
|
||||
.arg( origJ2 + 1 )
|
||||
.arg( origK1 + 1 )
|
||||
.arg( origK2 + 1 ) );
|
||||
return std::unexpected( "AQUCON record does not overlap with sector" );
|
||||
}
|
||||
|
||||
// Get the clamped coordinates from the intersection
|
||||
cvf::Vec3st corner1 = intersection->min();
|
||||
cvf::Vec3st corner2 = intersection->max();
|
||||
|
||||
// Log if clamping occurred (partial overlap)
|
||||
if ( origI1 != static_cast<int>( corner1.x() ) || origI2 != static_cast<int>( corner2.x() ) ||
|
||||
origJ1 != static_cast<int>( corner1.y() ) || origJ2 != static_cast<int>( corner2.y() ) ||
|
||||
origK1 != static_cast<int>( corner1.z() ) || origK2 != static_cast<int>( corner2.z() ) )
|
||||
{
|
||||
int aquiferId = record.getItem( 0 ).get<int>( 0 );
|
||||
RiaLogging::info(
|
||||
QString( "AQUCON record for aquifer %1 partially overlaps sector, clamped from [%2-%3, %4-%5, %6-%7] to [%8-%9, "
|
||||
"%10-%11, %12-%13]" )
|
||||
.arg( aquiferId )
|
||||
.arg( origI1 + 1 )
|
||||
.arg( origI2 + 1 )
|
||||
.arg( origJ1 + 1 )
|
||||
.arg( origJ2 + 1 )
|
||||
.arg( origK1 + 1 )
|
||||
.arg( origK2 + 1 )
|
||||
.arg( corner1.x() + 1 )
|
||||
.arg( corner2.x() + 1 )
|
||||
.arg( corner1.y() + 1 )
|
||||
.arg( corner2.y() + 1 )
|
||||
.arg( corner1.z() + 1 )
|
||||
.arg( corner2.z() + 1 ) );
|
||||
}
|
||||
|
||||
auto transformResult1 =
|
||||
RigGridExportAdapter::transformIjkToSectorCoordinates( caf::VecIjk0( corner1.x(), corner1.y(), corner1.z() ), min, max, refinement );
|
||||
auto transformResult2 =
|
||||
RigGridExportAdapter::transformIjkToSectorCoordinates( caf::VecIjk0( corner2.x(), corner2.y(), corner2.z() ), min, max, refinement );
|
||||
|
||||
if ( !transformResult1 )
|
||||
{
|
||||
return std::unexpected( QString( "AQUCON I1,J1,K1 coordinate is out of sector bounds: %1" ).arg( transformResult1.error() ) );
|
||||
}
|
||||
|
||||
if ( !transformResult2 )
|
||||
{
|
||||
return std::unexpected( QString( "AQUCON I2,J2,K2 coordinate is out of sector bounds: %1" ).arg( transformResult2.error() ) );
|
||||
}
|
||||
|
||||
using A = Opm::ParserKeywords::AQUCON;
|
||||
items.push_back( RifOpmDeckTools::item( A::I1::itemName, static_cast<int>( transformResult1->x() ) ) );
|
||||
items.push_back( RifOpmDeckTools::item( A::I2::itemName, static_cast<int>( transformResult2->x() ) ) );
|
||||
items.push_back( RifOpmDeckTools::item( A::J1::itemName, static_cast<int>( transformResult1->y() ) ) );
|
||||
items.push_back( RifOpmDeckTools::item( A::J2::itemName, static_cast<int>( transformResult2->y() ) ) );
|
||||
items.push_back( RifOpmDeckTools::item( A::K1::itemName, static_cast<int>( transformResult1->z() ) ) );
|
||||
items.push_back( RifOpmDeckTools::item( A::K2::itemName, static_cast<int>( transformResult2->z() ) ) );
|
||||
|
||||
// Copy remaining items (CONNECT_FACE and other parameters)
|
||||
for ( size_t i = 7; i < record.size(); ++i )
|
||||
{
|
||||
items.push_back( record.getItem( i ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No box definition or incomplete box definition - copy remaining items as-is
|
||||
for ( size_t i = 1; i < record.size(); ++i )
|
||||
{
|
||||
items.push_back( record.getItem( i ) );
|
||||
}
|
||||
}
|
||||
|
||||
return Opm::DeckRecord{ std::move( items ) };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -67,6 +67,9 @@ public:
|
||||
static std::expected<Opm::DeckRecord, QString>
|
||||
processAddRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const cvf::Vec3st& refinement );
|
||||
|
||||
static std::expected<Opm::DeckRecord, QString>
|
||||
processAquconRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const cvf::Vec3st& refinement );
|
||||
|
||||
private:
|
||||
static std::expected<void, QString> updateCornerPointGridInDeckFile( RimEclipseCase* eclipseCase,
|
||||
const RigSimulationInputSettings& settings,
|
||||
@@ -97,6 +100,9 @@ private:
|
||||
static std::expected<void, QString>
|
||||
replaceAddKeywordIndices( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile );
|
||||
|
||||
static std::expected<void, QString>
|
||||
replaceAquconKeywordIndices( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile );
|
||||
|
||||
static std::expected<void, QString>
|
||||
addFaultsToDeckFile( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile );
|
||||
|
||||
|
||||
@@ -244,6 +244,126 @@ TEST( RigSimulationInputTool, ProcessEqualsRecord_AtBoundary )
|
||||
EXPECT_EQ( 10, result->getItem( 7 ).get<int>( 0 ) ); // K2
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Helper to create an AQUCON DeckRecord
|
||||
/// AQUCON format: ID I1 I2 J1 J2 K1 K2 CONNECT_FACE ... (1-based Eclipse coordinates)
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static Opm::DeckRecord createAquconRecord( int aquiferId, int i1, int i2, int j1, int j2, int k1, int k2, const std::string& connectFace )
|
||||
{
|
||||
std::vector<Opm::DeckItem> items;
|
||||
items.push_back( RifOpmDeckTools::item( "ID", aquiferId ) );
|
||||
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 ) );
|
||||
items.push_back( RifOpmDeckTools::item( "CONNECT_FACE", connectFace ) );
|
||||
|
||||
return Opm::DeckRecord{ std::move( items ) };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test AQUCON record with partial overlap (based on user-provided data)
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessAquconRecord_PartialOverlap )
|
||||
{
|
||||
// Sector: min=(40, 27, 21), max=(100, 72, 58) (inclusive, 0-based)
|
||||
// This corresponds to a sector with cells [40-100, 27-72, 21-58]
|
||||
caf::VecIjk0 sectorMin( 40, 27, 21 );
|
||||
caf::VecIjk0 sectorMax( 100, 72, 58 );
|
||||
cvf::Vec3st refinement( 1, 1, 1 );
|
||||
|
||||
// AQUCON record from user data: 1 57 57 28 36 46 58 'I+' (1-based Eclipse)
|
||||
// Converts to 0-based: I[56-56], J[27-35], K[45-57]
|
||||
// Sector is I[40-100], J[27-72], K[21-58]
|
||||
// Overlap: I[56-56], J[27-35], K[45-57] - all inside, no clamping needed
|
||||
auto record = createAquconRecord( 1, 57, 57, 28, 36, 46, 58, "I+" );
|
||||
|
||||
auto result = RigSimulationInputTool::processAquconRecord( record, sectorMin, sectorMax, refinement );
|
||||
|
||||
ASSERT_TRUE( result.has_value() );
|
||||
|
||||
// Check transformed coordinates (sector-relative, 1-based)
|
||||
// I: (56-40)*1+1 = 17 to (56-40)*1+1 = 17
|
||||
// J: (27-27)*1+1 = 1 to (35-27)*1+1 = 9
|
||||
// K: (45-21)*1+1 = 25 to (57-21)*1+1 = 37
|
||||
EXPECT_EQ( 17, result->getItem( 1 ).get<int>( 0 ) ); // I1
|
||||
EXPECT_EQ( 17, result->getItem( 2 ).get<int>( 0 ) ); // I2
|
||||
EXPECT_EQ( 1, result->getItem( 3 ).get<int>( 0 ) ); // J1
|
||||
EXPECT_EQ( 9, result->getItem( 4 ).get<int>( 0 ) ); // J2
|
||||
EXPECT_EQ( 25, result->getItem( 5 ).get<int>( 0 ) ); // K1
|
||||
EXPECT_EQ( 37, result->getItem( 6 ).get<int>( 0 ) ); // K2
|
||||
|
||||
// Check aquifer ID and connect face are preserved
|
||||
EXPECT_EQ( 1, result->getItem( 0 ).get<int>( 0 ) );
|
||||
EXPECT_EQ( "I+", result->getItem( 7 ).get<std::string>( 0 ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test AQUCON record with partial K overlap requiring clamping
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessAquconRecord_PartialOverlapWithClamping )
|
||||
{
|
||||
// Sector: min=(40, 27, 21), max=(100, 72, 58) (inclusive, 0-based)
|
||||
caf::VecIjk0 sectorMin( 40, 27, 21 );
|
||||
caf::VecIjk0 sectorMax( 100, 72, 58 );
|
||||
cvf::Vec3st refinement( 1, 1, 1 );
|
||||
|
||||
// AQUCON record: 1 79 79 41 67 5 11 'I+' (1-based Eclipse)
|
||||
// Converts to 0-based: I[78-78], J[40-66], K[4-10]
|
||||
// Sector K is [21-58], so K[4-10] is outside and should be skipped
|
||||
auto record = createAquconRecord( 1, 79, 79, 41, 67, 5, 11, "I+" );
|
||||
|
||||
auto result = RigSimulationInputTool::processAquconRecord( record, sectorMin, sectorMax, refinement );
|
||||
|
||||
// This should fail because K range [4-10] doesn't overlap with sector K range [21-58]
|
||||
EXPECT_FALSE( result.has_value() );
|
||||
EXPECT_TRUE( result.error().contains( "does not overlap" ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test AQUCON record completely inside sector
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessAquconRecord_CompletelyInside )
|
||||
{
|
||||
// Sector: min=(40, 27, 21), max=(100, 72, 58) (inclusive, 0-based)
|
||||
caf::VecIjk0 sectorMin( 40, 27, 21 );
|
||||
caf::VecIjk0 sectorMax( 100, 72, 58 );
|
||||
cvf::Vec3st refinement( 1, 1, 1 );
|
||||
|
||||
// AQUCON record: 1 61 61 48 72 12 17 'I+' (1-based Eclipse)
|
||||
// Converts to 0-based: I[60-60], J[47-71], K[11-16]
|
||||
// However, K[11-16] is outside sector K[21-58], so this should fail
|
||||
auto record = createAquconRecord( 1, 61, 61, 48, 72, 12, 17, "I+" );
|
||||
|
||||
auto result = RigSimulationInputTool::processAquconRecord( record, sectorMin, sectorMax, refinement );
|
||||
|
||||
EXPECT_FALSE( result.has_value() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test AQUCON record without box definition (only aquifer ID)
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RigSimulationInputTool, ProcessAquconRecord_NoBoxDefinition )
|
||||
{
|
||||
caf::VecIjk0 sectorMin( 0, 0, 0 );
|
||||
caf::VecIjk0 sectorMax( 9, 9, 9 );
|
||||
cvf::Vec3st refinement( 1, 1, 1 );
|
||||
|
||||
// Create a record with only aquifer ID (no box coordinates)
|
||||
std::vector<Opm::DeckItem> items;
|
||||
items.push_back( RifOpmDeckTools::item( "ID", 1 ) );
|
||||
|
||||
Opm::DeckRecord record{ std::move( items ) };
|
||||
|
||||
auto result = RigSimulationInputTool::processAquconRecord( record, sectorMin, sectorMax, refinement );
|
||||
|
||||
EXPECT_TRUE( result.has_value() );
|
||||
EXPECT_EQ( result->size(), 1U );
|
||||
EXPECT_EQ( 1, result->getItem( 0 ).get<int>( 0 ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Test exportSimulationInput with model5 data
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user