From d053d4b0a604129e788183d39b53909d5148b495 Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Wed, 3 Dec 2025 15:25:42 +0100 Subject: [PATCH] Simulation Input: Handle MULTIPLY keyword. --- .../SimulationFile/RigSimulationInputTool.cpp | 175 ++++++++++++++++++ .../SimulationFile/RigSimulationInputTool.h | 6 + 2 files changed, 181 insertions(+) diff --git a/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.cpp b/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.cpp index 4e662758a4..98fb6577fc 100644 --- a/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.cpp +++ b/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.cpp @@ -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 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 RigSimulationInputTool::replaceEqualsKeywordIndices return {}; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected 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 RigSimulationInputTool::processEqualsRec return Opm::DeckRecord{ std::move( items ) }; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::expected 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 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( 0 ) - 1; // Convert to 0-based + int origI2 = record.getItem( 3 ).get( 0 ) - 1; + int origJ1 = record.getItem( 4 ).get( 0 ) - 1; + int origJ2 = record.getItem( 5 ).get( 0 ) - 1; + int origK1 = record.getItem( 6 ).get( 0 ) - 1; + int origK2 = record.getItem( 7 ).get( 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( 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( corner1.x() ) || origI2 != static_cast( corner2.x() ) || + origJ1 != static_cast( corner1.y() ) || origJ2 != static_cast( corner2.y() ) || + origK1 != static_cast( corner1.z() ) || origK2 != static_cast( corner2.z() ) ) + { + QString fieldName = QString::fromStdString( record.getItem( 0 ).get( 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( transformResult1->x() ) ) ); + items.push_back( RifOpmDeckTools::item( M::I2::itemName, static_cast( transformResult2->x() ) ) ); + items.push_back( RifOpmDeckTools::item( M::J1::itemName, static_cast( transformResult1->y() ) ) ); + items.push_back( RifOpmDeckTools::item( M::J2::itemName, static_cast( transformResult2->y() ) ) ); + items.push_back( RifOpmDeckTools::item( M::K1::itemName, static_cast( transformResult1->z() ) ) ); + items.push_back( RifOpmDeckTools::item( M::K2::itemName, static_cast( transformResult2->z() ) ) ); + } + + return Opm::DeckRecord{ std::move( items ) }; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.h b/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.h index cb87e7a659..e5beb27644 100644 --- a/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.h +++ b/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.h @@ -54,6 +54,9 @@ public: static std::expected processEqualsRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const cvf::Vec3st& refinement ); + static std::expected + processMultiplyRecord( const Opm::DeckRecord& record, const caf::VecIjk0& min, const caf::VecIjk0& max, const cvf::Vec3st& refinement ); + private: static std::expected updateCornerPointGridInDeckFile( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, @@ -72,6 +75,9 @@ private: static std::expected replaceEqualsKeywordIndices( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); + static std::expected + replaceMultiplyKeywordIndices( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile ); + static std::expected addFaultsToDeckFile( RimEclipseCase* eclipseCase, const RigSimulationInputSettings& settings, RifOpmFlowDeckFile& deckFile );