Well planning: prepare for improved MSW handling (#13096)

* Refactor MSW data handling in opm flow job
This commit is contained in:
JJ authored and GitHub committed 2025-10-30 11:57:22 +01:00
1 parent 90e8145382
commit 948d6d82a6
6 files changed
+322 -122

No files matched your search

@@ -74,9 +74,9 @@ static std::optional<Opm::FileDeck::Index> locateKeywordAtTimeStep( std::unique_
if ( startIdx.has_value() )
{
auto idx = startIdx.value();
idx--;
idx++;
// locate keyword for the selected step, break if another date is found
for ( auto it = idx; it != fileDeck->start(); it-- )
for ( auto it = idx; it != fileDeck->stop(); it++ )
{
auto& kw = fileDeck->operator[]( it );
if ( kw.name() == Opm::ParserKeywords::DATES::keywordName )
@@ -156,8 +156,6 @@ static Opm::ParseContext defaultParseContext()
//--------------------------------------------------------------------------------------------------
static bool insertKeywordAtIndex( std::unique_ptr<Opm::FileDeck>& fileDeck, Opm::DeckKeyword& keyword, Opm::FileDeck::Index insertIdx )
{
Opm::ErrorGuard errors{};
try
{
fileDeck->insert( insertIdx, keyword );
@@ -351,7 +349,7 @@ int RifOpmFlowDeckFile::mergeKeywordAtPosition( int position, const Opm::DeckKey
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RifOpmFlowDeckFile::mergeKeywordAtTimeStep( int timeStep, const Opm::DeckKeyword& keyword )
bool RifOpmFlowDeckFile::mergeKeywordAtTimeStep( int timeStep, const Opm::DeckKeyword& keyword, std::string insertAfterKeyword )
{
if ( m_fileDeck.get() == nullptr ) return false;
@@ -378,8 +376,23 @@ bool RifOpmFlowDeckFile::mergeKeywordAtTimeStep( int timeStep, const Opm::DeckKe
}
else
{
// existing kw not found, insert a new one
m_fileDeck->insert( insertIdx, keyword );
bool insertedOk = false;
if ( !insertAfterKeyword.empty() )
{
auto insertAfterKw = internal::locateKeywordAtTimeStep( m_fileDeck, timeStep, insertAfterKeyword );
if ( insertAfterKw.has_value() )
{
auto afterIdx = insertAfterKw.value();
afterIdx++;
m_fileDeck->insert( afterIdx, keyword );
insertedOk = true;
}
}
if ( !insertedOk )
{
// insert after keyword not found, insert at date position
m_fileDeck->insert( insertIdx, keyword );
}
}
return true;
}
@@ -631,6 +644,55 @@ bool RifOpmFlowDeckFile::setWelldims( int maxWells, int maxConnections, int maxG
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<int> RifOpmFlowDeckFile::wsegdims()
{
using W = Opm::ParserKeywords::WSEGDIMS;
if ( m_fileDeck.get() == nullptr ) return {};
auto idx = m_fileDeck->find( W::keywordName );
if ( idx.has_value() )
{
std::vector<int> dims;
auto& kw = m_fileDeck->operator[]( idx.value() );
const auto& rec = kw.getRecord( 0 );
dims.push_back( rec.getItem<W::NSWLMX>().get<int>( 0 ) );
dims.push_back( rec.getItem<W::NSEGMX>().get<int>( 0 ) );
dims.push_back( rec.getItem<W::NLBRMX>().get<int>( 0 ) );
return dims;
}
return { 0, 0, 0 };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RifOpmFlowDeckFile::setWsegdims( int maxMSWells, int maxSegmentsPerWell, int maxBranchesPerWell )
{
using W = Opm::ParserKeywords::WSEGDIMS;
if ( m_fileDeck.get() == nullptr ) return false;
auto idx = m_fileDeck->find( W::keywordName );
if ( !idx.has_value() )
{
idx = m_fileDeck->find( Opm::ParserKeywords::WELLDIMS::keywordName );
if ( !idx.has_value() ) return false;
idx = idx.value() + 1;
}
Opm::DeckKeyword newKw( ( Opm::ParserKeywords::WSEGDIMS() ) );
newKw.addRecord( Opm::DeckRecord{ { RifOpmDeckTools::item( W::NSWLMX::itemName, maxMSWells ),
RifOpmDeckTools::item( W::NSEGMX::itemName, maxSegmentsPerWell ),
RifOpmDeckTools::item( W::NLBRMX::itemName, maxBranchesPerWell ) } } );
m_fileDeck->erase( idx.value() );
m_fileDeck->insert( idx.value(), newKw );
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -777,97 +839,6 @@ bool RifOpmFlowDeckFile::replaceKeyword( const std::string& section, const Opm::
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RifOpmFlowDeckFile::mergeMswData( std::vector<std::string>& mswFileData )
{
Opm::ErrorGuard errors{};
int curTimeStep = 0;
int maxSteps = (int)mswFileData.size();
std::string prevFileData;
// locate dates keyword for the selected step
for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ )
{
auto& kw = m_fileDeck->operator[]( it );
if ( kw.name() != Opm::ParserKeywords::DATES::keywordName ) continue;
Opm::FileDeck::Index insertIdx( it );
if ( mswFileData[curTimeStep] != prevFileData )
{
auto deck = Opm::Parser{}.parseString( mswFileData[curTimeStep], internal::defaultParseContext(), errors );
for ( auto kwit = deck.begin(); kwit != deck.end(); kwit++ )
{
Opm::DeckKeyword newKw( *kwit );
if ( ( newKw.name() == Opm::ParserKeywords::WELSPECS::keywordName ) && ( curTimeStep == 0 ) )
{
const auto found = m_fileDeck->find( Opm::ParserKeywords::WELSPECS::keywordName );
if ( !found.has_value() )
{
m_fileDeck->insert( insertIdx, newKw );
insertIdx++;
}
else
{
Opm::DeckRecord newRecToAdd( newKw.getRecord( 0 ) );
auto& existing_idx = found.value();
auto& welspecs_kw = m_fileDeck->operator[]( existing_idx );
Opm::DeckKeyword newWelspecsKw( welspecs_kw );
newWelspecsKw.addRecord( std::move( newRecToAdd ) );
m_fileDeck->erase( existing_idx );
m_fileDeck->insert( existing_idx, newWelspecsKw );
}
}
else if ( ( newKw.name() == Opm::ParserKeywords::COMPDAT::keywordName ) && ( curTimeStep == 0 ) )
{
const auto found = m_fileDeck->find( Opm::ParserKeywords::COMPDAT::keywordName );
if ( !found.has_value() )
{
m_fileDeck->insert( insertIdx, newKw );
insertIdx++;
}
else
{
auto& existing_idx = found.value();
auto& compdat_kw = m_fileDeck->operator[]( existing_idx );
Opm::DeckKeyword newCompdatKw( compdat_kw );
for ( size_t i = 0; i < newKw.size(); i++ )
{
Opm::DeckRecord newRecToAdd( newKw.getRecord( i ) );
newCompdatKw.addRecord( std::move( newRecToAdd ) );
}
m_fileDeck->erase( existing_idx );
m_fileDeck->insert( existing_idx, newCompdatKw );
}
}
else
{
m_fileDeck->insert( insertIdx, newKw );
insertIdx++;
}
}
prevFileData = mswFileData[curTimeStep];
}
curTimeStep++;
if ( curTimeStep >= maxSteps ) break;
}
return curTimeStep > 1;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -48,9 +48,7 @@ public:
bool saveDeck( std::string folder, std::string filename );
int mergeKeywordAtPosition( int deckPosition, const Opm::DeckKeyword& keyword );
bool mergeKeywordAtTimeStep( int timeStep, const Opm::DeckKeyword& keyword );
bool mergeMswData( std::vector<std::string>& mswFileData );
bool mergeKeywordAtTimeStep( int timeStep, const Opm::DeckKeyword& keyword, std::string insertAfterKeyword = "" );
bool openWellAtTimeStep( int timeStep, Opm::DeckKeyword& openKeyword );
bool openWellAtDeckPosition( int deckPosition, Opm::DeckKeyword& openKeyword );
@@ -71,6 +69,9 @@ public:
std::vector<int> welldims();
bool setWelldims( int maxWells, int maxConnections, int maxGroups, int maxWellsInGroup );
std::vector<int> wsegdims();
bool setWsegdims( int maxMSWells, int maxSegmentsPerWell, int maxBranchesPerWell );
std::vector<int> regdims();
bool setRegdims( int maxRegions, int maxRegionDefinitions, int maxRegionFlowConnections, int maxFIPRegions );
bool ensureRegdimsKeyword();
@@ -35,16 +35,40 @@
#include "cvfStructGrid.h"
#include "opm/input/eclipse/Deck/Deck.hpp"
#include "opm/input/eclipse/Deck/DeckItem.hpp"
#include "opm/input/eclipse/Deck/DeckKeyword.hpp"
#include "opm/input/eclipse/Deck/DeckRecord.hpp"
#include "opm/input/eclipse/Parser/ParserKeyword.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/B.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/C.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/F.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/O.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/W.hpp"
#include "opm/input/eclipse/Parser/ErrorGuard.hpp"
#include "opm/input/eclipse/Parser/InputErrorAction.hpp"
#include "opm/input/eclipse/Parser/ParseContext.hpp"
#include "opm/input/eclipse/Parser/Parser.hpp"
namespace internal
{
//--------------------------------------------------------------------------------------------------
/// Temporary methods for MSW data while waiting for MSW export rewrite
//--------------------------------------------------------------------------------------------------
static Opm::ParseContext defaultParseContext()
{
// Use the same default ParseContext as flow.
Opm::ParseContext pc( Opm::InputErrorAction::WARN );
pc.update( Opm::ParseContext::PARSE_RANDOM_SLASH, Opm::InputErrorAction::IGNORE );
pc.update( Opm::ParseContext::PARSE_MISSING_DIMS_KEYWORD, Opm::InputErrorAction::WARN );
pc.update( Opm::ParseContext::SUMMARY_UNKNOWN_WELL, Opm::InputErrorAction::WARN );
pc.update( Opm::ParseContext::SUMMARY_UNKNOWN_GROUP, Opm::InputErrorAction::WARN );
return pc;
}
} // namespace internal
//==================================================================================================
///
///
@@ -158,6 +182,139 @@ Opm::DeckKeyword compdatKeyword( RimEclipseCase* eCase, RimWellPath* wellPath )
return kw;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Opm::DeckKeyword welsegsKeyword( RimEclipseCase* eCase, RimWellPath* wellPath, const std::string completionText )
{
if ( eCase == nullptr || wellPath == nullptr || wellPath->completionSettings() == nullptr )
{
return Opm::DeckKeyword();
}
Opm::ErrorGuard errors{};
bool headerDone = false;
Opm::DeckKeyword newKw( ( Opm::ParserKeywords::WELSEGS() ) );
auto deck = Opm::Parser{}.parseString( completionText, internal::defaultParseContext(), errors );
for ( auto kwit = deck.begin(); kwit != deck.end(); kwit++ )
{
auto& existingKw = *kwit;
if ( existingKw.name() != Opm::ParserKeywords::WELSEGS::keywordName ) continue;
for ( size_t i = 0; i < existingKw.size(); i++ )
{
Opm::DeckRecord newRec( existingKw.getRecord( i ) );
if ( newRec.getItem( 0 ).is_string() && headerDone ) continue;
newKw.addRecord( std::move( newRec ) );
headerDone = true;
}
}
return newKw;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Opm::DeckKeyword compsegsKeyword( RimEclipseCase* eCase, RimWellPath* wellPath, const std::string completionText )
{
if ( eCase == nullptr || wellPath == nullptr || wellPath->completionSettings() == nullptr )
{
return Opm::DeckKeyword();
}
Opm::ErrorGuard errors{};
bool headerDone = false;
Opm::DeckKeyword newKw( ( Opm::ParserKeywords::COMPSEGS() ) );
auto deck = Opm::Parser{}.parseString( completionText, internal::defaultParseContext(), errors );
for ( auto kwit = deck.begin(); kwit != deck.end(); kwit++ )
{
auto& existingKw = *kwit;
if ( existingKw.name() != Opm::ParserKeywords::COMPSEGS::keywordName ) continue;
for ( size_t i = 0; i < existingKw.size(); i++ )
{
Opm::DeckRecord newRec( existingKw.getRecord( i ) );
if ( newRec.size() == 1 && headerDone ) continue;
newKw.addRecord( std::move( newRec ) );
headerDone = true;
}
}
return newKw;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Opm::DeckKeyword wsegvalvKeyword( RimEclipseCase* eCase, RimWellPath* wellPath, const std::string completionText )
{
if ( eCase == nullptr || wellPath == nullptr || wellPath->completionSettings() == nullptr )
{
return Opm::DeckKeyword();
}
Opm::ErrorGuard errors{};
Opm::DeckKeyword newKw( ( Opm::ParserKeywords::WSEGVALV() ) );
auto deck = Opm::Parser{}.parseString( completionText, internal::defaultParseContext(), errors );
for ( auto kwit = deck.begin(); kwit != deck.end(); kwit++ )
{
auto& existingKw = *kwit;
if ( existingKw.name() != Opm::ParserKeywords::WSEGVALV::keywordName ) continue;
for ( size_t i = 0; i < existingKw.size(); i++ )
{
Opm::DeckRecord newRec( existingKw.getRecord( i ) );
newKw.addRecord( std::move( newRec ) );
}
}
return newKw;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
Opm::DeckKeyword wsegaicdKeyword( RimEclipseCase* eCase, RimWellPath* wellPath, const std::string completionText )
{
if ( eCase == nullptr || wellPath == nullptr || wellPath->completionSettings() == nullptr )
{
return Opm::DeckKeyword();
}
Opm::ErrorGuard errors{};
Opm::DeckKeyword newKw( ( Opm::ParserKeywords::WSEGAICD() ) );
auto deck = Opm::Parser{}.parseString( completionText, internal::defaultParseContext(), errors );
for ( auto kwit = deck.begin(); kwit != deck.end(); kwit++ )
{
auto& existingKw = *kwit;
if ( existingKw.name() != Opm::ParserKeywords::WSEGAICD::keywordName ) continue;
for ( size_t i = 0; i < existingKw.size(); i++ )
{
Opm::DeckRecord newRec( existingKw.getRecord( i ) );
newKw.addRecord( std::move( newRec ) );
}
}
return newKw;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -48,6 +48,12 @@ namespace RimKeywordFactory
Opm::DeckKeyword welspecsKeyword( const std::string wellGrpName, RimEclipseCase* eCase, RimWellPath* wellPath );
Opm::DeckKeyword compdatKeyword( RimEclipseCase* eCase, RimWellPath* wellPath );
Opm::DeckKeyword welsegsKeyword( RimEclipseCase* eCase, RimWellPath* wellPath, const std::string completionText );
Opm::DeckKeyword compsegsKeyword( RimEclipseCase* eCase, RimWellPath* wellPath, const std::string completionText );
Opm::DeckKeyword wsegvalvKeyword( RimEclipseCase* eCase, RimWellPath* wellPath, const std::string completionText );
Opm::DeckKeyword wsegaicdKeyword( RimEclipseCase* eCase, RimWellPath* wellPath, const std::string completionText );
Opm::DeckKeyword faultsKeyword( const RigMainGrid* mainGrid,
const cvf::Vec3st& min = cvf::Vec3st::ZERO,
const cvf::Vec3st& max = cvf::Vec3st::UNDEFINED,
@@ -783,29 +783,19 @@ bool RimOpmFlowJob::onPrepare()
}
m_wellPath->completionSettings()->setGroupName( m_wellGroupName() );
int mergePosition = m_openWellDeckPosition();
int mergePosition = mergeBasicWellSettings();
if ( mergePosition < 0 )
{
RiaLogging::error( "Unable to merge new well data into DATA file. Please check file format." );
return false;
}
if ( ( m_includeMSWData ) && ( m_wellOpenType == WellOpenType::OPEN_AT_DATE ) )
{
std::vector<std::string> mswData;
int nDates = (int)m_eclipseCase()->timeStepDates().size();
for ( int timeStep = 0; timeStep < nDates; timeStep++ )
{
mswData.push_back( exportMswWellSettings( timeStep ) );
}
if ( !m_deckFile->mergeMswData( mswData ) )
{
RiaLogging::error( "Failed to merge MSW data into file deck." );
return false;
}
}
else
{
mergePosition = mergeBasicWellSettings();
mergePosition = mergeMswData( mergePosition );
if ( mergePosition < 0 )
{
RiaLogging::error( "Unable to merge new well data into DATA file. Please check file format." );
RiaLogging::error( "Failed to merge MSW data into file deck." );
return false;
}
}
@@ -1018,7 +1008,7 @@ int RimOpmFlowJob::mergeBasicWellSettings()
// reverse order for correct insertion order
if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), compdatKw ) ) return failure;
if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), welspecsKw ) ) return failure;
return 0;
mergePosition = 0;
}
else
{
@@ -1031,8 +1021,10 @@ int RimOpmFlowJob::mergeBasicWellSettings()
// increase wells and connections in welldims to make sure they are big enough
auto additionalConnections = (int)compdatKw.size();
auto welldims = m_deckFile->welldims();
if ( !m_deckFile->setWelldims( (int)welldims[0] + 1, (int)( welldims[1] + additionalConnections ), (int)welldims[2] + 1, (int)welldims[3] + 1 ) )
if ( ( welldims.size() < 4 ) ||
!m_deckFile->setWelldims( (int)welldims[0] + 1, (int)( welldims[1] + additionalConnections ), (int)welldims[2] + 1, (int)welldims[3] + 1 ) )
{
RiaLogging::error( "Failed to update WELLDIMS keyword in DATA file, is it missing?" );
return failure;
}
return mergePosition;
@@ -1074,6 +1066,78 @@ std::string RimOpmFlowJob::exportMswWellSettings( int timeStep )
return fileContent.toStdString();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RimOpmFlowJob::mergeMswData( int mergePosition )
{
const int failure = -1;
auto mswData = exportMswWellSettings( 0 );
auto welsegsKw = RimKeywordFactory::welsegsKeyword( m_eclipseCase(), m_wellPath(), mswData );
auto compsegsKw = RimKeywordFactory::compsegsKeyword( m_eclipseCase(), m_wellPath(), mswData );
auto wsegvalvKw = RimKeywordFactory::wsegvalvKeyword( m_eclipseCase(), m_wellPath(), mswData );
auto wsegaicdKw = RimKeywordFactory::wsegaicdKeyword( m_eclipseCase(), m_wellPath(), mswData );
if ( welsegsKw.size() < 1 || compsegsKw.size() < 1 )
{
RiaLogging::error( "Failed to create WELSEGS or COMPSEGS keyword from MSW data." );
return failure;
}
if ( m_wellOpenType == WellOpenType::OPEN_AT_DATE )
{
// make sure we insert after COMPDAT kw
if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), welsegsKw, "COMPDAT" ) ) return failure;
if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), compsegsKw, welsegsKw.name() ) ) return failure;
if ( wsegvalvKw.size() >= 1 )
{
if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), wsegvalvKw, compsegsKw.name() ) ) return failure;
}
if ( wsegaicdKw.size() >= 1 )
{
if ( !m_deckFile->mergeKeywordAtTimeStep( m_openTimeStep(), wsegaicdKw, compsegsKw.name() ) ) return failure;
}
mergePosition = 0;
}
else
{
mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, welsegsKw );
if ( mergePosition < 0 ) return failure;
mergePosition++;
mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, compsegsKw );
if ( mergePosition < 0 ) return failure;
mergePosition++;
if ( wsegvalvKw.size() >= 1 )
{
mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, wsegvalvKw );
if ( mergePosition < 0 ) return failure;
mergePosition++;
}
if ( wsegaicdKw.size() >= 1 )
{
mergePosition = m_deckFile->mergeKeywordAtPosition( mergePosition, wsegaicdKw );
if ( mergePosition < 0 ) return failure;
mergePosition++;
}
}
int branches = (int)m_wellPath->allWellPathLaterals().size();
// increase wells and connections in welldims to make sure they are big enough
auto additionalSegments = (int)welsegsKw.size() - 1;
auto wsegdims = m_deckFile->wsegdims();
auto maxBranches = std::max( branches, wsegdims[2] );
if ( ( wsegdims.size() < 3 ) || !m_deckFile->setWsegdims( wsegdims[0] + 1, wsegdims[1] + additionalSegments, maxBranches + 1 ) )
{
RiaLogging::error( "Failed to update WSEGDIMS keyword in DATA file." );
return failure;
}
return mergePosition;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -104,6 +104,7 @@ private:
int mergeBasicWellSettings();
std::string exportMswWellSettings( int timeStep );
int mergeMswData( int mergePosition );
void selectOpenWellPosition();
void resetEnsembleRunId();