diff --git a/ApplicationLibCode/Application/Tools/RiaWslTools.cpp b/ApplicationLibCode/Application/Tools/RiaWslTools.cpp index c04c5e052c..d596ab69a6 100644 --- a/ApplicationLibCode/Application/Tools/RiaWslTools.cpp +++ b/ApplicationLibCode/Application/Tools/RiaWslTools.cpp @@ -43,7 +43,11 @@ QStringList RiaWslTools::wslDistributionList() if ( !wslCmd.isEmpty() ) { RimProcess wslProc( false /*no logging*/ ); - wslProc.addEnvironmentVariable( "WSL_UTF8", "1" ); + + for ( auto& [key, val] : wslEnvironmentVariables() ) + { + wslProc.addEnvironmentVariable( key, val ); + } wslProc.setCommand( wslCmd ); wslProc.addParameter( "--list" ); // list distribution names wslProc.addParameter( "--quiet" ); // quiet, only show name @@ -73,3 +77,13 @@ QString RiaWslTools::convertToWslPath( QString windowsPath ) return QString( "/mnt/%1%2" ).arg( drive ).arg( path ); } + +//-------------------------------------------------------------------------------------------------- +/// Returns the input windows path as seen in the wsl world. I.e. c:\tmp -> /mnt/c/tmp +//-------------------------------------------------------------------------------------------------- +std::map RiaWslTools::wslEnvironmentVariables() +{ + std::map env; + env["WSL_UTF8"] = "1"; + return env; +} diff --git a/ApplicationLibCode/Application/Tools/RiaWslTools.h b/ApplicationLibCode/Application/Tools/RiaWslTools.h index ff44c5b876..6aa9a1757d 100644 --- a/ApplicationLibCode/Application/Tools/RiaWslTools.h +++ b/ApplicationLibCode/Application/Tools/RiaWslTools.h @@ -21,14 +21,17 @@ #include #include +#include + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- namespace RiaWslTools { -QString wslCommand(); -QStringList wslDistributionList(); -QString convertToWslPath( QString windowsPath ); +QString wslCommand(); +QStringList wslDistributionList(); +QString convertToWslPath( QString windowsPath ); +std::map wslEnvironmentVariables(); } // namespace RiaWslTools diff --git a/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.cpp b/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.cpp index 75c4894aba..816de24449 100644 --- a/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.cpp +++ b/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.cpp @@ -18,13 +18,19 @@ #include "RifOpmFlowDeckFile.h" +#include "opm/common/utility/TimeService.hpp" #include "opm/input/eclipse/Deck/Deck.hpp" #include "opm/input/eclipse/Deck/FileDeck.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" +#include "opm/input/eclipse/Parser/ParserKeywords/C.hpp" #include "opm/input/eclipse/Parser/ParserKeywords/D.hpp" +#include "opm/input/eclipse/Parser/ParserKeywords/E.hpp" +#include "opm/input/eclipse/Parser/ParserKeywords/R.hpp" +#include "opm/input/eclipse/Parser/ParserKeywords/S.hpp" +#include "opm/input/eclipse/Parser/ParserKeywords/W.hpp" #include #include @@ -37,17 +43,17 @@ namespace internal //-------------------------------------------------------------------------------------------------- static std::optional locateTimeStep( std::unique_ptr& fileDeck, int timeStep ) { - int currentStep = 1; + int currentStep = 0; // locate dates keyword for the selected step for ( auto it = fileDeck->start(); it != fileDeck->stop(); it++ ) { auto& kw = fileDeck->operator[]( it ); - if ( kw.name() != "DATES" ) continue; + if ( kw.name() != Opm::ParserKeywords::DATES::keywordName ) continue; if ( currentStep == timeStep ) { - return it; + return ++it; } currentStep++; } @@ -68,11 +74,11 @@ static std::optional locateKeywordAtTimeStep( std::unique_ for ( auto it = startIdx; it != fileDeck->start(); it-- ) { auto& kw = fileDeck->operator[]( it ); - if ( kw.name() == "DATES" ) + if ( kw.name() == Opm::ParserKeywords::DATES::keywordName ) { break; } - else if ( kw.name() == "SCHEDULE" ) + else if ( kw.name() == Opm::ParserKeywords::SCHEDULE::keywordName ) { break; } @@ -96,6 +102,71 @@ static std::optional positionToIndex( int deckPosition, st return std::nullopt; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +static std::string datesKeywordToString( const Opm::DeckKeyword& kw ) +{ + using D = Opm::ParserKeywords::DATES; + if ( kw.name() == D::keywordName ) + { + const auto& rec = kw.getRecord( 0 ); + int day = rec.getItem().get( 0 ); + auto month = rec.getItem().get( 0 ); + int year = rec.getItem().get( 0 ); + + std::string dateStr; + if ( rec.size() > 3 ) + { + auto time = rec.getItem().get( 0 ); + dateStr = std::format( "{}/{}/{} - {}", day, month, year, time ); + } + else + { + dateStr = std::format( "{}/{}/{}", day, month, year ); + } + return dateStr; + } + + return ""; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +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; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +static bool insertDataAtPosition( std::unique_ptr& fileDeck, std::string text, Opm::FileDeck::Index insertPos ) +{ + Opm::ErrorGuard errors{}; + + try + { + auto deck = Opm::Parser{}.parseString( text, defaultParseContext(), errors ); + Opm::DeckKeyword newKw( *deck.begin() ); + fileDeck->insert( insertPos, newKw ); + } + catch ( ... ) + { + return false; + } + + return true; +} + } // namespace internal //-------------------------------------------------------------------------------------------------- @@ -119,13 +190,67 @@ bool RifOpmFlowDeckFile::loadDeck( std::string filename ) { Opm::ErrorGuard errors{}; - auto deck = Opm::Parser{}.parseFile( filename, defaultParseContext(), errors ); + try + { + auto deck = Opm::Parser{}.parseFile( filename, internal::defaultParseContext(), errors ); - m_fileDeck = std::make_unique( deck ); + m_fileDeck = std::make_unique( deck ); + + splitDatesIfNecessary(); + } + catch ( ... ) + { + m_fileDeck.reset(); + return false; + } return true; } +//-------------------------------------------------------------------------------------------------- +/// Make sure all dates keywords are single record, to make it easier to insert new things +//-------------------------------------------------------------------------------------------------- +void RifOpmFlowDeckFile::splitDatesIfNecessary() +{ + using D = Opm::ParserKeywords::DATES; + if ( m_fileDeck.get() == nullptr ) return; + + bool keepSplitting = true; + while ( keepSplitting ) + { + keepSplitting = false; + for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ ) + { + auto& kw = m_fileDeck->operator[]( it ); + if ( ( kw.name() == D::keywordName ) && ( kw.size() > 1 ) ) + { + // split this keyword into multiple keywords + int records = (int)kw.size(); + std::vector newKeywords; + + for ( int i = 0; i < records; i++ ) + { + Opm::DeckKeyword newKw( kw.emptyStructuralCopy() ); + Opm::DeckRecord newRecToAdd( kw.getRecord( i ) ); + newKw.addRecord( std::move( newRecToAdd ) ); + newKeywords.push_back( newKw ); + } + + std::reverse( newKeywords.begin(), newKeywords.end() ); + // erase old kw and insert the new ones at the same position + m_fileDeck->erase( it ); + for ( auto& newKw : newKeywords ) + { + m_fileDeck->insert( it, newKw ); + } + // need to restart the search as the iterators could be invalid + keepSplitting = true; + break; + } + } + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -172,30 +297,30 @@ Opm::DeckItem RifOpmFlowDeckFile::defaultItem( std::string name, int cols ) //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -bool RifOpmFlowDeckFile::mergeWellDeck( int timeStep, std::string filename, int fallbackPosition ) +bool RifOpmFlowDeckFile::mergeWellDeckAtTimeStep( int timeStep, std::string filename ) { Opm::ErrorGuard errors{}; - auto deckToMerge = Opm::Parser{}.parseFile( filename, defaultParseContext(), errors ); + auto deckToMerge = Opm::Parser{}.parseFile( filename, internal::defaultParseContext(), errors ); - const auto welspecsIndexes = deckToMerge.index( "WELSPECS" ); + const auto welspecsIndexes = deckToMerge.index( Opm::ParserKeywords::WELSPECS::keywordName ); if ( welspecsIndexes.empty() ) return false; auto& mergeWelspecsKw = deckToMerge[welspecsIndexes[0]]; Opm::DeckRecord newWelspecRecToAdd( mergeWelspecsKw.getRecord( 0 ) ); - const auto compdatIndexes = deckToMerge.index( "COMPDAT" ); + const auto compdatIndexes = deckToMerge.index( Opm::ParserKeywords::COMPDAT::keywordName ); if ( compdatIndexes.empty() ) return false; auto& mergeCompdatKw = deckToMerge[compdatIndexes[0]]; auto additionalConnections = mergeCompdatKw.size(); auto welldims = this->welldims(); - auto datePos = internal::locateTimeStep( m_fileDeck, timeStep ); - if ( datePos.has_value() ) + auto dateIdx = internal::locateTimeStep( m_fileDeck, timeStep ); + if ( dateIdx.has_value() ) { - auto insertPos = datePos.value(); + auto insertIdx = dateIdx.value(); // Insert new well into WELSPECS in the selected date section - auto existingKwFound = internal::locateKeywordAtTimeStep( m_fileDeck, timeStep, "WELSPECS" ); + auto existingKwFound = internal::locateKeywordAtTimeStep( m_fileDeck, timeStep, Opm::ParserKeywords::WELSPECS::keywordName ); if ( existingKwFound.has_value() ) { auto& welspecs_kw = m_fileDeck->operator[]( existingKwFound.value() ); @@ -209,12 +334,12 @@ bool RifOpmFlowDeckFile::mergeWellDeck( int timeStep, std::string filename, int else { // existing kw not found, insert a new one - m_fileDeck->insert( insertPos, mergeWelspecsKw ); - insertPos++; + m_fileDeck->insert( insertIdx, mergeWelspecsKw ); + insertIdx++; } // Insert new well data into COMPDAT in the selected date section - existingKwFound = internal::locateKeywordAtTimeStep( m_fileDeck, timeStep, "COMPDAT" ); + existingKwFound = internal::locateKeywordAtTimeStep( m_fileDeck, timeStep, Opm::ParserKeywords::COMPDAT::keywordName ); if ( existingKwFound.has_value() ) { auto& compdat_kw = m_fileDeck->operator[]( existingKwFound.value() ); @@ -232,66 +357,96 @@ bool RifOpmFlowDeckFile::mergeWellDeck( int timeStep, std::string filename, int else { // existing kw not found, insert a new one - m_fileDeck->insert( insertPos, mergeCompdatKw ); + m_fileDeck->insert( insertIdx, mergeCompdatKw ); } + + // increase wells and connections in welldims to make sure they are big enough + return setWelldims( (int)welldims[0] + 1, (int)( welldims[1] + additionalConnections ), (int)welldims[2], (int)welldims[3] ); + } + + return false; +} + +//-------------------------------------------------------------------------------------------------- +/// Returns updated position due to new inserts if successful, < 0 if failure +//-------------------------------------------------------------------------------------------------- +int RifOpmFlowDeckFile::mergeWellDeckAtPosition( int position, std::string filename ) +{ + Opm::ErrorGuard errors{}; + + auto deckToMerge = Opm::Parser{}.parseFile( filename, internal::defaultParseContext(), errors ); + + const auto welspecsIndexes = deckToMerge.index( Opm::ParserKeywords::WELSPECS::keywordName ); + if ( welspecsIndexes.empty() ) return -1; + auto& mergeWelspecsKw = deckToMerge[welspecsIndexes[0]]; + Opm::DeckRecord newWelspecRecToAdd( mergeWelspecsKw.getRecord( 0 ) ); + + const auto compdatIndexes = deckToMerge.index( Opm::ParserKeywords::COMPDAT::keywordName ); + if ( compdatIndexes.empty() ) return -1; + auto& mergeCompdatKw = deckToMerge[compdatIndexes[0]]; + + auto additionalConnections = mergeCompdatKw.size(); + auto welldims = this->welldims(); + + auto insertIdx = internal::positionToIndex( position, m_fileDeck ); + if ( !insertIdx.has_value() ) + { + return -1; + } + + // Insert new well into main WELSPECS + const auto foundWelspecs = m_fileDeck->find( Opm::ParserKeywords::WELSPECS::keywordName ); + if ( foundWelspecs.has_value() ) + { + auto& existing_pos = foundWelspecs.value(); + auto& welspecs_kw = m_fileDeck->operator[]( existing_pos ); + + Opm::DeckKeyword newWelspecsKw( welspecs_kw ); + newWelspecsKw.addRecord( std::move( newWelspecRecToAdd ) ); + + m_fileDeck->erase( existing_pos ); + m_fileDeck->insert( existing_pos, newWelspecsKw ); } else { - // Insert new well into main WELSPECS + // existing kw not found, insert a new one + m_fileDeck->insert( insertIdx.value(), mergeCompdatKw ); + // update index to keep keyword order + insertIdx.value() = insertIdx.value() + 1; + position++; + } + + // Insert new well data into main COMPDAT + const auto foundCompdat = m_fileDeck->find( Opm::ParserKeywords::COMPDAT::keywordName ); + if ( foundCompdat.has_value() ) + { + auto& existing_pos = foundCompdat.value(); + auto& compdat_kw = m_fileDeck->operator[]( existing_pos ); + + Opm::DeckKeyword newCompdatKw( compdat_kw ); + + for ( size_t i = 0; i < mergeCompdatKw.size(); i++ ) { - const auto foundWelspecs = m_fileDeck->find( "WELSPECS" ); - if ( !foundWelspecs.has_value() ) return false; - auto& existing_pos = foundWelspecs.value(); - auto& welspecs_kw = m_fileDeck->operator[]( existing_pos ); - - Opm::DeckKeyword newWelspecsKw( welspecs_kw ); - newWelspecsKw.addRecord( std::move( newWelspecRecToAdd ) ); - - m_fileDeck->erase( existing_pos ); - m_fileDeck->insert( existing_pos, newWelspecsKw ); + Opm::DeckRecord newRecToAdd( mergeCompdatKw.getRecord( i ) ); + newCompdatKw.addRecord( std::move( newRecToAdd ) ); } - // Insert new well data into main COMPDAT - { - const auto foundCompdat = m_fileDeck->find( "COMPDAT" ); - if ( foundCompdat.has_value() ) - { - auto& existing_pos = foundCompdat.value(); - auto& compdat_kw = m_fileDeck->operator[]( existing_pos ); - - Opm::DeckKeyword newCompdatKw( compdat_kw ); - - for ( size_t i = 0; i < mergeCompdatKw.size(); i++ ) - { - Opm::DeckRecord newRecToAdd( mergeCompdatKw.getRecord( i ) ); - newCompdatKw.addRecord( std::move( newRecToAdd ) ); - } - - m_fileDeck->erase( existing_pos ); - m_fileDeck->insert( existing_pos, newCompdatKw ); - } - else if ( fallbackPosition >= 0 ) - { - // existing kw not found, insert a new one at fallback position - auto position = internal::positionToIndex( fallbackPosition, m_fileDeck ); - if ( position.has_value() ) - { - m_fileDeck->insert( position.value(), mergeCompdatKw ); - } - else - { - return false; - } - } - else - { - return false; - } - } + m_fileDeck->erase( existing_pos ); + m_fileDeck->insert( existing_pos, newCompdatKw ); + } + else + { + // existing kw not found, insert a new one + m_fileDeck->insert( insertIdx.value(), mergeCompdatKw ); + position++; } // increase wells and connections in welldims to make sure they are big enough - return setWelldims( (int)welldims[0] + 1, (int)( welldims[1] + additionalConnections ), (int)welldims[2], (int)welldims[3] ); + if ( setWelldims( (int)welldims[0] + 1, (int)( welldims[1] + additionalConnections ), (int)welldims[2], (int)welldims[3] ) ) + { + return position; + } + return -1; } //-------------------------------------------------------------------------------------------------- @@ -299,52 +454,25 @@ bool RifOpmFlowDeckFile::mergeWellDeck( int timeStep, std::string filename, int //-------------------------------------------------------------------------------------------------- bool RifOpmFlowDeckFile::openWellAtTimeStep( int timeStep, std::string openText ) { - Opm::ErrorGuard errors{}; - - auto datePos = internal::locateTimeStep( m_fileDeck, timeStep ); + auto datePos = internal::locateTimeStep( m_fileDeck, timeStep + 1 ); if ( datePos.has_value() ) { - auto deck = Opm::Parser{}.parseString( openText, defaultParseContext(), errors ); - Opm::DeckKeyword newKw( *deck.begin() ); - - m_fileDeck->insert( datePos.value(), newKw ); - - return true; + auto insertPos = datePos.value(); + insertPos--; + return internal::insertDataAtPosition( m_fileDeck, openText, insertPos ); } - return false; } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -Opm::ParseContext RifOpmFlowDeckFile::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; -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RifOpmFlowDeckFile::openWellAtDeckPosition( int deckPosition, std::string openText ) { - Opm::ErrorGuard errors{}; - - auto position = internal::positionToIndex( deckPosition, m_fileDeck ); - if ( position.has_value() ) + auto index = internal::positionToIndex( deckPosition, m_fileDeck ); + if ( index.has_value() ) { - // position found, insert before - auto deck = Opm::Parser{}.parseString( openText, defaultParseContext(), errors ); - Opm::DeckKeyword newKw( *deck.begin() ); - m_fileDeck->insert( position.value(), newKw ); - return true; + return internal::insertDataAtPosition( m_fileDeck, openText, index.value() ); } return false; } @@ -361,14 +489,9 @@ std::vector RifOpmFlowDeckFile::keywords() for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ ) { auto& kw = m_fileDeck->operator[]( it ); - if ( kw.name() == "DATES" ) + if ( kw.name() == Opm::ParserKeywords::DATES::keywordName ) { - const auto& rec = kw.getRecord( 0 ); - int day = rec.getItem( "DAY" ).get( 0 ); - auto month = rec.getItem( "MONTH" ).getTrimmedString( 0 ); - int year = rec.getItem( "YEAR" ).get( 0 ); - - std::string dateStr = std::format( "DATES ({}/{}/{})", day, month, year ); + std::string dateStr = kw.name() + " " + internal::datesKeywordToString( kw ); values.push_back( dateStr ); } else @@ -385,27 +508,79 @@ std::vector RifOpmFlowDeckFile::keywords() bool RifOpmFlowDeckFile::hasDatesKeyword() { if ( m_fileDeck.get() == nullptr ) return false; - auto pos = m_fileDeck->find( "DATES" ); + auto pos = m_fileDeck->find( Opm::ParserKeywords::DATES::keywordName ); return pos.has_value(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifOpmFlowDeckFile::isRestartFile() +{ + if ( m_fileDeck.get() == nullptr ) return false; + auto pos = m_fileDeck->find( Opm::ParserKeywords::RESTART::keywordName ); + return pos.has_value(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RifOpmFlowDeckFile::dateStrings() +{ + std::vector values; + + if ( m_fileDeck.get() == nullptr ) return values; + + for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ ) + { + auto& kw = m_fileDeck->operator[]( it ); + if ( kw.name() == Opm::ParserKeywords::DATES::keywordName ) + { + std::string dateStr = kw.name() + " " + internal::datesKeywordToString( kw ); + values.push_back( internal::datesKeywordToString( kw ) ); + } + } + return values; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RifOpmFlowDeckFile::dates() +{ + std::vector values; + if ( m_fileDeck.get() == nullptr ) return values; + for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ ) + { + auto& kw = m_fileDeck->operator[]( it ); + if ( kw.name() == Opm::ParserKeywords::DATES::keywordName ) + { + const auto& rec = kw.getRecord( 0 ); + values.push_back( Opm::TimeService::timeFromEclipse( rec ) ); + } + } + + return values; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RifOpmFlowDeckFile::welldims() { + using W = Opm::ParserKeywords::WELLDIMS; if ( m_fileDeck.get() == nullptr ) return {}; - auto pos = m_fileDeck->find( "WELLDIMS" ); + auto pos = m_fileDeck->find( W::keywordName ); if ( pos.has_value() ) { std::vector dims; auto& kw = m_fileDeck->operator[]( pos.value() ); const auto& rec = kw.getRecord( 0 ); - dims.push_back( rec.getItem( "MAXWELLS" ).get( 0 ) ); - dims.push_back( rec.getItem( "MAXCONN" ).get( 0 ) ); - dims.push_back( rec.getItem( "MAXGROUPS" ).get( 0 ) ); - dims.push_back( rec.getItem( "MAX_GROUPSIZE" ).get( 0 ) ); + dims.push_back( rec.getItem().get( 0 ) ); + dims.push_back( rec.getItem().get( 0 ) ); + dims.push_back( rec.getItem().get( 0 ) ); + dims.push_back( rec.getItem().get( 0 ) ); return dims; } @@ -417,8 +592,9 @@ std::vector RifOpmFlowDeckFile::welldims() //-------------------------------------------------------------------------------------------------- bool RifOpmFlowDeckFile::setWelldims( int maxWells, int maxConnections, int maxGroups, int maxWellsInGroup ) { + using W = Opm::ParserKeywords::WELLDIMS; if ( m_fileDeck.get() == nullptr ) return false; - auto pos = m_fileDeck->find( "WELLDIMS" ); + auto pos = m_fileDeck->find( W::keywordName ); if ( pos.has_value() ) { std::vector dims; @@ -426,10 +602,11 @@ bool RifOpmFlowDeckFile::setWelldims( int maxWells, int maxConnections, int maxG auto& oldkw = m_fileDeck->operator[]( pos.value() ); Opm::DeckKeyword newKw( Opm::ParserKeyword( oldkw.name() ) ); - newKw.addRecord( Opm::DeckRecord{ { item( "MAXWELLS", maxWells ), - item( "MAXCONN", maxConnections ), - item( "MAXGROUPS", maxGroups ), - item( "MAX_GROUPSIZE", maxWellsInGroup ) } } ); + + newKw.addRecord( Opm::DeckRecord{ { item( W::MAXWELLS::itemName, maxWells ), + item( W::MAXCONN::itemName, maxConnections ), + item( W::MAXGROUPS::itemName, maxGroups ), + item( W::MAX_GROUPSIZE::itemName, maxWellsInGroup ) } } ); m_fileDeck->erase( pos.value() ); m_fileDeck->insert( pos.value(), newKw ); @@ -453,21 +630,21 @@ bool RifOpmFlowDeckFile::mergeMswData( std::vector& mswFileData ) for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ ) { auto& kw = m_fileDeck->operator[]( it ); - if ( kw.name() != "DATES" ) continue; + if ( kw.name() != Opm::ParserKeywords::DATES::keywordName ) continue; Opm::FileDeck::Index insertPos( it ); if ( mswFileData[curTimeStep] != prevFileData ) { - auto deck = Opm::Parser{}.parseString( mswFileData[curTimeStep], defaultParseContext(), errors ); + 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() == "WELSPECS" ) && ( curTimeStep == 0 ) ) + if ( ( newKw.name() == Opm::ParserKeywords::WELSPECS::keywordName ) && ( curTimeStep == 0 ) ) { - const auto found = m_fileDeck->find( "WELSPECS" ); + const auto found = m_fileDeck->find( Opm::ParserKeywords::WELSPECS::keywordName ); if ( !found.has_value() ) { m_fileDeck->insert( insertPos, newKw ); @@ -487,9 +664,9 @@ bool RifOpmFlowDeckFile::mergeMswData( std::vector& mswFileData ) m_fileDeck->insert( existing_pos, newWelspecsKw ); } } - else if ( ( newKw.name() == "COMPDAT" ) && ( curTimeStep == 0 ) ) + else if ( ( newKw.name() == Opm::ParserKeywords::COMPDAT::keywordName ) && ( curTimeStep == 0 ) ) { - const auto found = m_fileDeck->find( "COMPDAT" ); + const auto found = m_fileDeck->find( Opm::ParserKeywords::COMPDAT::keywordName ); if ( !found.has_value() ) { m_fileDeck->insert( insertPos, newKw ); @@ -540,3 +717,21 @@ bool RifOpmFlowDeckFile::restartAtTimeStep( int timeStep, std::string deckName ) m_fileDeck->insert_skiprest(); return true; } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifOpmFlowDeckFile::stopAtTimeStep( int timeStep ) +{ + if ( !m_fileDeck ) return false; + + auto datePos = internal::locateTimeStep( m_fileDeck, timeStep ); + if ( datePos.has_value() ) + { + auto pkw = Opm::ParserKeyword( Opm::ParserKeywords::END::keywordName ); + Opm::DeckKeyword newKw( pkw ); + m_fileDeck->insert( datePos.value(), newKw ); + } + + return true; +} diff --git a/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.h b/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.h index 328ee39b97..d71805567c 100644 --- a/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.h +++ b/ApplicationLibCode/FileInterface/RifOpmFlowDeckFile.h @@ -42,24 +42,32 @@ public: bool loadDeck( std::string filename ); bool saveDeck( std::string folder, std::string filename ); - bool mergeWellDeck( int timeStep, std::string filename, int fallbackPosition ); + bool mergeWellDeckAtTimeStep( int timeStep, std::string filename ); + int mergeWellDeckAtPosition( int position, std::string filename ); + bool mergeMswData( std::vector& mswFileData ); bool openWellAtTimeStep( int timeStep, std::string openText ); bool openWellAtDeckPosition( int deckPosition, std::string openText ); bool restartAtTimeStep( int timeStep, std::string deckName ); + bool stopAtTimeStep( int timeStep ); std::vector keywords(); bool hasDatesKeyword(); - std::vector welldims(); - bool setWelldims( int maxWells, int maxConnections, int maxGroups, int maxWellsInGroup ); + bool isRestartFile(); + std::vector dateStrings(); + std::vector dates(); + + std::vector welldims(); + bool setWelldims( int maxWells, int maxConnections, int maxGroups, int maxWellsInGroup ); private: - Opm::DeckItem item( std::string name, std::string value ); - Opm::DeckItem item( std::string name, int value ); - Opm::DeckItem defaultItem( std::string name, int cols ); - static Opm::ParseContext defaultParseContext(); + Opm::DeckItem item( std::string name, std::string value ); + Opm::DeckItem item( std::string name, int value ); + Opm::DeckItem defaultItem( std::string name, int cols ); + + void splitDatesIfNecessary(); private: std::unique_ptr m_fileDeck; diff --git a/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.cpp b/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.cpp index 68dd9ffe7e..58451f68be 100644 --- a/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.cpp +++ b/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.cpp @@ -91,6 +91,10 @@ bool RimGenericJob::execute() process.setCommand( cmd ); if ( !cmdLine.isEmpty() ) process.addParameters( cmdLine ); process.setWorkingDirectory( workingDirectory() ); + for ( const auto& [name, value] : environment() ) + { + process.addEnvironmentVariable( name, value ); + } runOk = process.execute(); } diff --git a/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.h b/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.h index 63f0687877..c92abeceb3 100644 --- a/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.h +++ b/ApplicationLibCode/ProjectDataModel/Jobs/RimGenericJob.h @@ -23,6 +23,8 @@ #include #include +#include + //================================================================================================== /// /// @@ -40,10 +42,11 @@ public: protected: void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; - virtual QString title() = 0; - virtual QStringList command() = 0; - virtual QString workingDirectory() const; - virtual bool onPrepare() = 0; - virtual bool onRun() = 0; - virtual void onCompleted( bool success ) = 0; + virtual QString title() = 0; + virtual QStringList command() = 0; + virtual std::map environment() = 0; + virtual QString workingDirectory() const; + virtual bool onPrepare() = 0; + virtual bool onRun() = 0; + virtual void onCompleted( bool success ) = 0; }; diff --git a/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.cpp b/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.cpp index 611779e111..911f17d8cd 100644 --- a/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.cpp +++ b/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.cpp @@ -80,6 +80,7 @@ void caf::AppEnum::setUp() //-------------------------------------------------------------------------------------------------- RimOpmFlowJob::RimOpmFlowJob() : m_fileDeckHasDates( false ) + , m_fileDeckIsRestart( false ) { CAF_PDM_InitObject( "Opm Flow Simulation", ":/opm.png" ); @@ -95,17 +96,11 @@ RimOpmFlowJob::RimOpmFlowJob() CAF_PDM_InitField( &m_pauseBeforeRun, "PauseBeforeRun", false, "Pause before running OPM Flow" ); CAF_PDM_InitField( &m_addNewWell, "AddNewWell", true, "Add New Well" ); CAF_PDM_InitField( &m_openWellDeckPosition, "OpenWellDeckPosition", -1, "Open Well at Keyword Index" ); - CAF_PDM_InitField( &m_includeMSWData, "IncludeMswData", false, "Add MSW Data" ); + CAF_PDM_InitField( &m_includeMSWData, "IncludeMswData", false, "Include MSW Data (experimental)" ); CAF_PDM_InitField( &m_addToEnsemble, "AddToEnsemble", false, "Add Runs to Ensemble" ); CAF_PDM_InitField( &m_useRestart, "UseRestart", false, "Restart Simulation at Well Open Date" ); CAF_PDM_InitField( &m_currentRunId, "CurrentRunID", 0, "Current Run ID" ); - caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_addToEnsemble ); - caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_pauseBeforeRun ); - caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_useRestart ); - caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_includeMSWData ); - caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_addNewWell ); - CAF_PDM_InitField( &m_wellOpenType, "WellOpenType", caf::AppEnum( WellOpenType::OPEN_AT_DATE ), "Open Well" ); CAF_PDM_InitField( &m_wellOpenKeyword, "WellOpenKeyword", QString( "WCONPROD" ), "Open Well Keyword" ); m_wellOpenKeyword.uiCapability()->setUiEditorTypeName( caf::PdmUiComboBoxEditor::uiEditorTypeName() ); @@ -114,6 +109,8 @@ RimOpmFlowJob::RimOpmFlowJob() CAF_PDM_InitField( &m_wellOpenText, "WellOpenText", QString( "'GRUP' 5000 4* 100 20 5" ), "Open Well Parameters" ); CAF_PDM_InitField( &m_openTimeStep, "OpenTimeStep", 0, " " ); + CAF_PDM_InitField( &m_endTimeStep, "EndTimeStep", 0, " " ); + CAF_PDM_InitField( &m_endTimeStepEnabled, "EndTimeStepEnabled", false, "Stop Simulation at Date" ); CAF_PDM_InitField( &m_runButton, "runButton", false, "" ); caf::PdmUiPushButtonEditor::configureEditorLabelHidden( &m_runButton ); @@ -127,6 +124,13 @@ RimOpmFlowJob::RimOpmFlowJob() caf::PdmUiPushButtonEditor::configureEditorLabelLeft( &m_openSelectButton ); m_openSelectButton.xmlCapability()->disableIO(); + caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_addToEnsemble ); + caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_pauseBeforeRun ); + caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_useRestart ); + caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_includeMSWData ); + caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_addNewWell ); + caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_endTimeStepEnabled ); + setDeletable( true ); } @@ -217,15 +221,15 @@ void RimOpmFlowJob::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& if ( m_wellOpenType() == WellOpenType::OPEN_AT_DATE ) { wellGrp->add( &m_openTimeStep ); - wellGrp->add( &m_useRestart ); - if ( !m_useRestart() ) + if ( !m_fileDeckIsRestart ) { - wellGrp->add( &m_includeMSWData ); + wellGrp->add( &m_useRestart ); } else { - m_includeMSWData = false; + m_useRestart = false; } + if ( m_eclipseCase() ) wellGrp->add( &m_includeMSWData ); } else if ( m_wellOpenType == WellOpenType::OPEN_BY_POSITION ) { @@ -241,6 +245,14 @@ void RimOpmFlowJob::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& auto opmGrp = uiOrdering.addNewGroup( "Opm Flow" ); opmGrp->add( &m_runButton ); opmGrp->add( &m_pauseBeforeRun ); + if ( m_fileDeckHasDates ) + { + opmGrp->add( &m_endTimeStepEnabled ); + if ( m_endTimeStepEnabled() ) + { + opmGrp->add( &m_endTimeStep ); + } + } opmGrp->add( &m_addToEnsemble ); if ( m_addToEnsemble() ) { @@ -263,11 +275,16 @@ QList RimOpmFlowJob::calculateValueOptions( const caf::P { RimTools::wellPathOptionItems( &options ); } - else if ( fieldNeedingOptions == &m_openTimeStep ) + else if ( ( fieldNeedingOptions == &m_openTimeStep ) || ( fieldNeedingOptions == &m_endTimeStep ) ) { - if ( auto ec = m_eclipseCase() ) + openDeckFile(); + if ( m_deckFile != nullptr ) { - RimTools::timeStepsForCase( ec, &options ); + auto timeStepNames = m_deckFile->dateStrings(); + for ( int i = 0; i < static_cast( timeStepNames.size() - 1 ); ++i ) + { + options.push_back( caf::PdmOptionItemInfo( QString::fromStdString( timeStepNames[i] ), QVariant::fromValue( i ) ) ); + } } } else if ( fieldNeedingOptions == &m_wellOpenKeyword ) @@ -381,11 +398,13 @@ bool RimOpmFlowJob::openDeckFile() if ( deckLoadOk ) { - m_fileDeckHasDates = m_deckFile->hasDatesKeyword(); + m_fileDeckHasDates = m_deckFile->hasDatesKeyword(); + m_fileDeckIsRestart = m_deckFile->isRestartFile(); } else { - m_fileDeckHasDates = false; + m_fileDeckHasDates = false; + m_fileDeckIsRestart = false; m_deckFile.reset(); } } @@ -501,14 +520,6 @@ QString RimOpmFlowJob::wellTempFile( int timeStep, bool includeMSW, bool include return workingDirectory() + "/ri_new_well" + postfix + deckExtension(); } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -QString RimOpmFlowJob::openWellTempFile() const -{ - return workingDirectory() + "/ri_open_well" + deckExtension(); -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -546,6 +557,19 @@ QStringList RimOpmFlowJob::command() return cmd; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::map RimOpmFlowJob::environment() +{ + auto opmPref = RiaPreferencesOpm::current(); + if ( opmPref->useWsl() ) + { + return RiaWslTools::wslEnvironmentVariables(); + } + return {}; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -580,22 +604,21 @@ bool RimOpmFlowJob::onPrepare() return false; } - if ( m_includeMSWData && m_wellOpenType == WellOpenType::OPEN_AT_DATE ) - { - if ( m_eclipseCase() ) - { - std::vector mswData; - int i = 0; - for ( auto& date : m_eclipseCase->timeStepDates() ) - { - mswData.push_back( exportMswWellSettings( date, i++ ) ); - } + int mergePosition = m_openWellDeckPosition(); - if ( !m_deckFile->mergeMswData( mswData ) ) - { - RiaLogging::error( "Failed to merge MSW data into file deck." ); - return false; - } + if ( ( m_includeMSWData ) && ( m_wellOpenType == WellOpenType::OPEN_AT_DATE ) ) + { + std::vector 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 @@ -608,13 +631,22 @@ bool RimOpmFlowJob::onPrepare() return false; } - int fallbackPosition = ( m_fileDeckHasDates && m_wellOpenType == WellOpenType::OPEN_AT_DATE ) ? -1 : m_openWellDeckPosition() - 1; - - // merge new well settings from resinsight into DATA deck - if ( !m_deckFile->mergeWellDeck( m_openTimeStep(), wellTempFile().toStdString(), fallbackPosition ) ) + if ( m_wellOpenType == WellOpenType::OPEN_AT_DATE ) { - RiaLogging::error( "Unable to merge new well data into DATA file. Are there WELSPECS and COMPDAT keywords?" ); - return false; + if ( !m_deckFile->mergeWellDeckAtTimeStep( m_openTimeStep(), wellTempFile().toStdString() ) ) + { + RiaLogging::error( "Unable to merge new well data into DATA file at selected time step due to parse errors." ); + return false; + } + } + else + { + mergePosition = m_deckFile->mergeWellDeckAtPosition( mergePosition, wellTempFile().toStdString() ); + if ( mergePosition < m_openWellDeckPosition() ) + { + RiaLogging::error( "Unable to merge new well data into DATA file due to parse errors." ); + return false; + } } QFile::remove( wellTempFile() ); } @@ -622,32 +654,17 @@ bool RimOpmFlowJob::onPrepare() QString openWellText = generateBasicOpenWellText(); // open new well at selected timestep - if ( m_fileDeckHasDates && m_wellOpenType == WellOpenType::OPEN_AT_DATE ) + if ( m_wellOpenType == WellOpenType::OPEN_AT_DATE ) { if ( !m_deckFile->openWellAtTimeStep( m_openTimeStep(), openWellText.toStdString() ) ) { - RiaLogging::error( "Unable to open new well in DATA file." ); + RiaLogging::error( "Unable to open new well at selected timestep in DATA file." ); return false; } - - if ( m_useRestart() ) - { - if ( !copyUnrstFileToWorkDir() ) - { - RiaLogging::error( "Unable to locate UNRST file from input case." ); - return false; - } - - if ( !m_deckFile->restartAtTimeStep( std::max( m_openTimeStep() - 1, 0 ), restartDeckName().toStdString() ) ) - { - RiaLogging::error( "Unable to insert restart keywords in DATA file." ); - return false; - } - } } else { - if ( !m_deckFile->openWellAtDeckPosition( m_openWellDeckPosition, openWellText.toStdString() ) ) + if ( !m_deckFile->openWellAtDeckPosition( mergePosition, openWellText.toStdString() ) ) { RiaLogging::error( "Unable to open new well at selected position in DATA file." ); return false; @@ -655,6 +672,30 @@ bool RimOpmFlowJob::onPrepare() } } + if ( m_useRestart() ) + { + if ( !copyUnrstFileToWorkDir() ) + { + RiaLogging::error( "Unable to locate UNRST file from input case." ); + return false; + } + + if ( !m_deckFile->restartAtTimeStep( m_openTimeStep(), restartDeckName().toStdString() ) ) + { + RiaLogging::error( "Unable to insert restart keywords in DATA file." ); + return false; + } + } + + if ( m_endTimeStepEnabled() ) + { + if ( !m_deckFile->stopAtTimeStep( m_endTimeStep() ) ) + { + RiaLogging::error( "Unable to insert END keyword in DATA file." ); + return false; + } + } + caf::ProgressInfo saveProgress( 1, "Saving updated information to DATA file.", false ); // save DATA file to working folder @@ -835,7 +876,7 @@ void RimOpmFlowJob::exportBasicWellSettings() //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -std::string RimOpmFlowJob::exportMswWellSettings( QDateTime date, int timeStep ) +std::string RimOpmFlowJob::exportMswWellSettings( int timeStep ) { RicExportCompletionDataSettingsUi exportSettings; @@ -945,4 +986,22 @@ QString RimOpmFlowJob::readFileContent( QString filename ) return fileContent; } return ""; -} \ No newline at end of file +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimOpmFlowJob::datesInFileDeck() +{ + std::vector dates; + + if ( openDeckFile() ) + { + for ( auto tt : m_deckFile->dates() ) + { + dates.push_back( QDateTime::fromSecsSinceEpoch( tt ) ); + } + } + + return dates; +} diff --git a/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.h b/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.h index 94d5851e00..096f3d2ce8 100644 --- a/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.h +++ b/ApplicationLibCode/ProjectDataModel/Jobs/RimOpmFlowJob.h @@ -62,12 +62,13 @@ protected: QList calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override; void initAfterRead() override; - QString title() override; - QStringList command() override; - QString workingDirectory() const override; - bool onPrepare() override; - bool onRun() override; - void onCompleted( bool success ) override; + QString title() override; + QStringList command() override; + std::map environment() override; + QString workingDirectory() const override; + bool onPrepare() override; + bool onRun() override; + void onCompleted( bool success ) override; bool openDeckFile(); bool copyUnrstFileToWorkDir(); @@ -76,14 +77,15 @@ private: RimEclipseCase* findExistingCase( QString filename ); QString deckExtension() const; QString wellTempFile( int timeStep = -1, bool includeMSW = false, bool includeLGR = false ) const; - QString openWellTempFile() const; QString baseDeckName() const; QString restartDeckName() const; + std::vector datesInFileDeck(); + static QString readFileContent( QString filename ); void exportBasicWellSettings(); - std::string exportMswWellSettings( QDateTime date, int timeStep ); + std::string exportMswWellSettings( int timeStep ); QString generateBasicOpenWellText(); void selectOpenWellPosition(); @@ -105,6 +107,8 @@ private: caf::PdmPtrField m_gridEnsemble; caf::PdmPtrField m_summaryEnsemble; caf::PdmField m_openTimeStep; + caf::PdmField m_endTimeStepEnabled; + caf::PdmField m_endTimeStep; caf::PdmField m_addNewWell; caf::PdmField> m_wellOpenType; caf::PdmField m_includeMSWData; @@ -115,4 +119,5 @@ private: QString m_deckName; std::unique_ptr m_deckFile; bool m_fileDeckHasDates; + bool m_fileDeckIsRestart; }; diff --git a/ResInsightVersion.cmake b/ResInsightVersion.cmake index bfce88838f..f47bfd76bd 100644 --- a/ResInsightVersion.cmake +++ b/ResInsightVersion.cmake @@ -11,7 +11,7 @@ set(RESINSIGHT_VERSION_TEXT "-dev") # Must be unique and increasing within one combination of major/minor/patch version # The uniqueness of this text is independent of RESINSIGHT_VERSION_TEXT # Format of text must be ".xx" -set(RESINSIGHT_DEV_VERSION ".02") +set(RESINSIGHT_DEV_VERSION ".03") set(STRPRODUCTVER ${RESINSIGHT_MAJOR_VERSION}.${RESINSIGHT_MINOR_VERSION}.${RESINSIGHT_PATCH_VERSION}${RESINSIGHT_VERSION_TEXT}${RESINSIGHT_DEV_VERSION})