Simulation Input: Handle MULTIPLY keyword.

This commit is contained in:
Kristian Bendiksen committed 2025-12-08 15:27:51 +01:00
1 parent c16a5de656
commit d053d4b0a6
2 files changed
+181

No files matched your search

@@ -45,6 +45,7 @@
#include "opm/input/eclipse/Parser/ParserKeyword.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/C.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/E.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/M.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/O.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/S.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/W.hpp"
@@ -111,6 +112,11 @@ std::expected<void, QString> RigSimulationInputTool::exportSimulationInput( RimE
return result;
}
if ( auto result = replaceMultiplyKeywordIndices( &eclipseCase, settings, deckFile ); !result )
{
return result;
}
if ( auto result = updateWelldimsKeyword( &eclipseCase, settings, deckFile ); !result )
{
return result;
@@ -407,6 +413,66 @@ std::expected<void, QString> RigSimulationInputTool::replaceEqualsKeywordIndices
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<void, QString> RigSimulationInputTool::replaceMultiplyKeywordIndices( RimEclipseCase* eclipseCase,
const RigSimulationInputSettings& settings,
RifOpmFlowDeckFile& deckFile )
{
auto multiplyKeywordsWithIndices = deckFile.findAllKeywordsWithIndices( "MULTIPLY" );
if ( !multiplyKeywordsWithIndices.empty() )
{
RiaLogging::info( QString( "Processing %1 occurrence(s) of MULTIPLY keyword" ).arg( multiplyKeywordsWithIndices.size() ) );
// Process in reverse order so indices remain valid after modifications
for ( auto it = multiplyKeywordsWithIndices.rbegin(); it != multiplyKeywordsWithIndices.rend(); ++it )
{
const Opm::FileDeck::Index& index = it->first;
const Opm::DeckKeyword& kw = it->second;
// Create new keyword with transformed coordinates
Opm::DeckKeyword transformedKeyword( kw.location(), kw.name() );
try
{
for ( size_t recordIdx = 0; recordIdx < kw.size(); ++recordIdx )
{
const auto& record = kw.getRecord( recordIdx );
auto result = processMultiplyRecord( record, settings.min(), settings.max(), settings.refinement() );
if ( result )
{
transformedKeyword.addRecord( std::move( result.value() ) );
}
else
{
RiaLogging::warning( QString( "Failed to process MULTIPLY record: %1" ).arg( result.error() ) );
}
}
// Replace with transformed keyword
if ( transformedKeyword.size() > 0 )
{
RiaLogging::info( QString( "Got %1 keywords." ).arg( transformedKeyword.size() ) );
deckFile.replaceKeywordAtIndex( index, transformedKeyword );
}
else
{
// If all records failed, remove the keyword
deckFile.replaceKeywordAtIndex( index, Opm::DeckKeyword( kw.location(), "SKIP" ) );
}
}
catch ( std::exception& e )
{
return std::unexpected( QString( "Exception processing MULTIPLY keyword: %1" ).arg( e.what() ) );
}
}
}
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -784,6 +850,115 @@ std::expected<Opm::DeckRecord, QString> RigSimulationInputTool::processEqualsRec
return Opm::DeckRecord{ std::move( items ) };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<Opm::DeckRecord, QString> RigSimulationInputTool::processMultiplyRecord( const Opm::DeckRecord& record,
const caf::VecIjk0& min,
const caf::VecIjk0& max,
const cvf::Vec3st& refinement )
{
// 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 )
{
return std::unexpected( QString( "MULTIPLY record has insufficient items (expected at least 8, got %1)" ).arg( record.size() ) );
}
std::vector<Opm::DeckItem> items;
// Copy field name and factor (first two items)
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 ) &&
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)
// Note: MULTIPLY uses 1-based Eclipse coordinates
int origI1 = record.getItem( 2 ).get<int>( 0 ) - 1; // Convert to 0-based
int origI2 = record.getItem( 3 ).get<int>( 0 ) - 1;
int origJ1 = record.getItem( 4 ).get<int>( 0 ) - 1;
int origJ2 = record.getItem( 5 ).get<int>( 0 ) - 1;
int origK1 = record.getItem( 6 ).get<int>( 0 ) - 1;
int origK2 = record.getItem( 7 ).get<int>( 0 ) - 1;
// Create bounding boxes (both use inclusive min/max coordinates)
RigBoundingBoxIjk multiplyBox( 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 = multiplyBox.intersection( sectorBox );
if ( !intersection )
{
// No overlap with sector - skip this record
QString fieldName = QString::fromStdString( record.getItem( 0 ).get<std::string>( 0 ) );
RiaLogging::info( QString( "MULTIPLY record for field '%1' [%2-%3, %4-%5, %6-%7] does not overlap with sector - skipping" )
.arg( fieldName )
.arg( origI1 + 1 )
.arg( origI2 + 1 )
.arg( origJ1 + 1 )
.arg( origJ2 + 1 )
.arg( origK1 + 1 )
.arg( origK2 + 1 ) );
return std::unexpected( "MULTIPLY 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() ) )
{
QString fieldName = QString::fromStdString( record.getItem( 0 ).get<std::string>( 0 ) );
RiaLogging::info(
QString( "MULTIPLY record for field '%1' partially overlaps sector, clamped from [%2-%3, %4-%5, %6-%7] to [%8-%9, %10-%11, "
"%12-%13]" )
.arg( fieldName )
.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( "MULTIPLY I1,J1,K1 coordinate is out of sector bounds: %1" ).arg( transformResult1.error() ) );
}
if ( !transformResult2 )
{
return std::unexpected( QString( "MULTIPLY I2,J2,K2 coordinate is out of sector bounds: %1" ).arg( transformResult2.error() ) );
}
using M = Opm::ParserKeywords::MULTIPLY;
items.push_back( RifOpmDeckTools::item( M::I1::itemName, static_cast<int>( transformResult1->x() ) ) );
items.push_back( RifOpmDeckTools::item( M::I2::itemName, static_cast<int>( transformResult2->x() ) ) );
items.push_back( RifOpmDeckTools::item( M::J1::itemName, static_cast<int>( transformResult1->y() ) ) );
items.push_back( RifOpmDeckTools::item( M::J2::itemName, static_cast<int>( transformResult2->y() ) ) );
items.push_back( RifOpmDeckTools::item( M::K1::itemName, static_cast<int>( transformResult1->z() ) ) );
items.push_back( RifOpmDeckTools::item( M::K2::itemName, static_cast<int>( transformResult2->z() ) ) );
}
return Opm::DeckRecord{ std::move( items ) };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -54,6 +54,9 @@ public:
static std::expected<Opm::DeckRecord, QString>
processEqualsRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const cvf::Vec3st& refinement );
static std::expected<Opm::DeckRecord, QString>
processMultiplyRecord( 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,
@@ -72,6 +75,9 @@ private:
static std::expected<void, QString>
replaceEqualsKeywordIndices( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile );
static std::expected<void, QString>
replaceMultiplyKeywordIndices( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile );
static std::expected<void, QString>
addFaultsToDeckFile( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile );