mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-26 07:00:17 -05:00
Sector model export removed all ACTNUM and FAULTS keywords from the deck and re-inserted the sector-specific replacements at the end of the GRID section, which always lands in the main DATA file. Decks that keep these keywords in separate include files lost their file organization, leaving the original include files empty while the data was written inline. Collapse duplicates to a single occurrence instead of removing all occurrences, so the subsequent in-place replacement keeps the keyword in its original file when the deck is saved with OutputMode::COPY. ACTNUM keeps the last occurrence since a later ACTNUM overrides an earlier one; FAULTS keeps the first so fault definitions do not move past keywords referencing them.
1417 lines
53 KiB
C++
1417 lines
53 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2025 Equinor ASA
|
|
//
|
|
// ResInsight is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE.
|
|
//
|
|
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
|
// for more details.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "RifOpmFlowDeckFile.h"
|
|
|
|
#include "RiaLogging.h"
|
|
|
|
#include "RifOpmDeckTools.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/A.hpp"
|
|
#include "opm/input/eclipse/Parser/ParserKeywords/B.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/G.hpp"
|
|
#include "opm/input/eclipse/Parser/ParserKeywords/I.hpp"
|
|
#include "opm/input/eclipse/Parser/ParserKeywords/P.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 <cmath>
|
|
#include <expected>
|
|
#include <format>
|
|
#include <memory>
|
|
#include <optional>
|
|
|
|
namespace internal
|
|
{
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
static std::optional<Opm::FileDeck::Index> locateTimeStep( std::unique_ptr<Opm::FileDeck>& fileDeck, int timeStep )
|
|
{
|
|
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() != Opm::ParserKeywords::DATES::keywordName ) continue;
|
|
|
|
if ( currentStep == timeStep )
|
|
{
|
|
return ++it;
|
|
}
|
|
currentStep++;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
static std::optional<Opm::FileDeck::Index> locateKeywordAtTimeStep( std::unique_ptr<Opm::FileDeck>& fileDeck, int timeStep, std::string keyword )
|
|
{
|
|
auto startIdx = internal::locateTimeStep( fileDeck, timeStep );
|
|
if ( startIdx.has_value() )
|
|
{
|
|
auto idx = startIdx.value();
|
|
idx++;
|
|
// locate keyword for the selected step, break if another date is found
|
|
for ( auto it = idx; it != fileDeck->stop(); it++ )
|
|
{
|
|
auto& kw = fileDeck->operator[]( it );
|
|
if ( kw.name() == Opm::ParserKeywords::DATES::keywordName )
|
|
{
|
|
break;
|
|
}
|
|
else if ( kw.name() == Opm::ParserKeywords::SCHEDULE::keywordName )
|
|
{
|
|
break;
|
|
}
|
|
else if ( kw.name() == keyword )
|
|
{
|
|
return it;
|
|
}
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
static std::optional<Opm::FileDeck::Index> positionToIndex( int deckPosition, std::unique_ptr<Opm::FileDeck>& fileDeck )
|
|
{
|
|
auto it = fileDeck->start();
|
|
it = it + deckPosition;
|
|
if ( it != fileDeck->stop() ) return it;
|
|
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<D::DAY>().get<int>( 0 );
|
|
auto month = rec.getItem<D::MONTH>().get<std::string>( 0 );
|
|
int year = rec.getItem<D::YEAR>().get<int>( 0 );
|
|
|
|
std::string dateStr;
|
|
if ( rec.size() > 3 )
|
|
{
|
|
auto time = rec.getItem<D::TIME>().get<std::string>( 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 insertKeywordAtIndex( std::unique_ptr<Opm::FileDeck>& fileDeck, Opm::DeckKeyword& keyword, Opm::FileDeck::Index insertIdx )
|
|
{
|
|
try
|
|
{
|
|
fileDeck->insert( insertIdx, keyword );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
static std::optional<Opm::FileDeck::Index> findSectionInsertionPoint( std::unique_ptr<Opm::FileDeck>& fileDeck, const std::string& section )
|
|
{
|
|
// Find the specified section
|
|
auto sectionIdx = fileDeck->find( section );
|
|
if ( !sectionIdx.has_value() )
|
|
{
|
|
return std::nullopt; // Section not found
|
|
}
|
|
|
|
auto insertIdx = sectionIdx.value();
|
|
insertIdx++; // Start after the section keyword
|
|
|
|
// Find a good insertion point within the section
|
|
for ( auto it = insertIdx; it != fileDeck->stop(); it++ )
|
|
{
|
|
auto& kw = fileDeck->operator[]( it );
|
|
|
|
// Stop if we hit another major section
|
|
if ( kw.name() == Opm::ParserKeywords::RUNSPEC::keywordName || kw.name() == Opm::ParserKeywords::GRID::keywordName ||
|
|
kw.name() == Opm::ParserKeywords::EDIT::keywordName || kw.name() == Opm::ParserKeywords::PROPS::keywordName ||
|
|
kw.name() == Opm::ParserKeywords::REGIONS::keywordName || kw.name() == Opm::ParserKeywords::SOLUTION::keywordName ||
|
|
kw.name() == Opm::ParserKeywords::SUMMARY::keywordName || kw.name() == Opm::ParserKeywords::SCHEDULE::keywordName )
|
|
{
|
|
insertIdx = it;
|
|
break;
|
|
}
|
|
|
|
// Keep moving forward in the current section
|
|
insertIdx = it;
|
|
insertIdx++;
|
|
}
|
|
|
|
// If insertIdx is at stop() (end of deck), we can't insert there as it's beyond valid blocks
|
|
// We need to insert at the end of the last block instead
|
|
if ( insertIdx == fileDeck->stop() )
|
|
{
|
|
// Decrement to get the last valid keyword
|
|
auto lastKeyword = fileDeck->stop();
|
|
--lastKeyword;
|
|
|
|
// We want to insert after the last keyword, so increment the keyword_index
|
|
insertIdx.file_index = lastKeyword.file_index;
|
|
insertIdx.keyword_index = lastKeyword.keyword_index + 1;
|
|
}
|
|
|
|
return insertIdx;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
static std::optional<Opm::FileDeck::Index> findSectionStartPoint( std::unique_ptr<Opm::FileDeck>& fileDeck, const std::string& section )
|
|
{
|
|
auto sectionIdx = fileDeck->find( section );
|
|
if ( !sectionIdx.has_value() )
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto insertIdx = sectionIdx.value();
|
|
insertIdx++;
|
|
return insertIdx;
|
|
}
|
|
|
|
} // namespace internal
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifOpmFlowDeckFile::RifOpmFlowDeckFile()
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifOpmFlowDeckFile::~RifOpmFlowDeckFile()
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::expected<void, std::string> RifOpmFlowDeckFile::loadDeck( std::string filename )
|
|
{
|
|
Opm::ErrorGuard errors{};
|
|
|
|
try
|
|
{
|
|
auto deck = Opm::Parser{}.parseFile( filename, internal::defaultParseContext(), errors );
|
|
|
|
m_deck = std::make_unique<Opm::Deck>( deck );
|
|
m_fileDeck = std::make_unique<Opm::FileDeck>( deck );
|
|
|
|
splitDatesIfNecessary();
|
|
}
|
|
catch ( const std::exception& e )
|
|
{
|
|
m_deck.reset();
|
|
m_fileDeck.reset();
|
|
return std::unexpected( std::string( e.what() ) + " " + errors.formattedErrors() );
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// 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<Opm::DeckKeyword> 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 index
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::saveDeck( std::string folder, std::string filename )
|
|
{
|
|
if ( m_fileDeck.get() != nullptr )
|
|
{
|
|
try
|
|
{
|
|
m_fileDeck->dump( folder, filename, Opm::FileDeck::OutputMode::COPY );
|
|
return true;
|
|
}
|
|
catch ( const std::exception& e )
|
|
{
|
|
RiaLogging::error( QString( "Failed to save deck: %1" ).arg( e.what() ).toStdString() );
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::saveDeckInline( std::string folder, std::string filename )
|
|
{
|
|
if ( m_fileDeck.get() != nullptr )
|
|
{
|
|
try
|
|
{
|
|
m_fileDeck->dump( folder, filename, Opm::FileDeck::OutputMode::INLINE );
|
|
return true;
|
|
}
|
|
catch ( const std::exception& e )
|
|
{
|
|
RiaLogging::error( QString( "Failed to save deck: %1" ).arg( e.what() ).toStdString() );
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Returns updated position due to new inserts if successful, < 0 if failure
|
|
//--------------------------------------------------------------------------------------------------
|
|
int RifOpmFlowDeckFile::addKeywordAtPosition( int position, const Opm::DeckKeyword& keyword )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return -1;
|
|
|
|
// locate position
|
|
auto insertIdx = internal::positionToIndex( position, m_fileDeck );
|
|
if ( !insertIdx.has_value() )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// insert a new keyword
|
|
m_fileDeck->insert( insertIdx.value(), keyword );
|
|
// update position to keep keyword order
|
|
position++;
|
|
|
|
return position;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Returns updated position due to new inserts if successful, < 0 if failure
|
|
//--------------------------------------------------------------------------------------------------
|
|
int RifOpmFlowDeckFile::mergeKeywordAtPosition( int position, const Opm::DeckKeyword& keyword )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return -1;
|
|
|
|
// locate position
|
|
auto insertIdx = internal::positionToIndex( position, m_fileDeck );
|
|
if ( !insertIdx.has_value() )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// Check if we should merge, or create a new one
|
|
const auto foundKw = m_fileDeck->find( keyword.name() );
|
|
if ( foundKw.has_value() )
|
|
{
|
|
auto& existing_idx = foundKw.value();
|
|
auto& existing_kw = m_fileDeck->operator[]( existing_idx );
|
|
|
|
Opm::DeckKeyword updatedKw( existing_kw );
|
|
|
|
for ( size_t i = 0; i < keyword.size(); i++ )
|
|
{
|
|
Opm::DeckRecord newRecToAdd( keyword.getRecord( i ) );
|
|
updatedKw.addRecord( std::move( newRecToAdd ) );
|
|
}
|
|
|
|
m_fileDeck->erase( existing_idx );
|
|
m_fileDeck->insert( existing_idx, updatedKw );
|
|
}
|
|
else
|
|
{
|
|
// existing kw not found, insert a new one
|
|
m_fileDeck->insert( insertIdx.value(), keyword );
|
|
// update position to keep keyword order
|
|
position++;
|
|
}
|
|
|
|
return position;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::addKeywordAtTimeStep( int timeStep, const Opm::DeckKeyword& keyword, std::string insertAfterKeyword )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
auto dateIdx = internal::locateTimeStep( m_fileDeck, timeStep );
|
|
if ( dateIdx.has_value() )
|
|
{
|
|
auto insertIdx = dateIdx.value();
|
|
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;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::mergeKeywordAtTimeStep( int timeStep, const Opm::DeckKeyword& keyword, std::string insertAfterKeyword )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
auto dateIdx = internal::locateTimeStep( m_fileDeck, timeStep );
|
|
if ( dateIdx.has_value() )
|
|
{
|
|
auto insertIdx = dateIdx.value();
|
|
// look for keyword in the selected date section
|
|
auto existingKwFound = internal::locateKeywordAtTimeStep( m_fileDeck, timeStep, keyword.name() );
|
|
if ( existingKwFound.has_value() )
|
|
{
|
|
auto& existingIdx = existingKwFound.value();
|
|
auto& existingKw = m_fileDeck->operator[]( existingIdx );
|
|
|
|
Opm::DeckKeyword updatedKw( existingKw );
|
|
for ( size_t i = 0; i < keyword.size(); i++ )
|
|
{
|
|
Opm::DeckRecord newRecToAdd( keyword.getRecord( i ) );
|
|
updatedKw.addRecord( std::move( newRecToAdd ) );
|
|
}
|
|
|
|
m_fileDeck->erase( existingIdx );
|
|
m_fileDeck->insert( existingIdx, updatedKw );
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::openWellAtTimeStep( int timeStep, Opm::DeckKeyword& openKeyword )
|
|
{
|
|
auto dateIndex = internal::locateTimeStep( m_fileDeck, timeStep + 1 );
|
|
if ( dateIndex.has_value() )
|
|
{
|
|
auto insertIdx = dateIndex.value();
|
|
insertIdx--;
|
|
return internal::insertKeywordAtIndex( m_fileDeck, openKeyword, insertIdx );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::openWellAtDeckPosition( int deckPosition, Opm::DeckKeyword& openKeyword )
|
|
{
|
|
auto index = internal::positionToIndex( deckPosition, m_fileDeck );
|
|
if ( index.has_value() )
|
|
{
|
|
return internal::insertKeywordAtIndex( m_fileDeck, openKeyword, index.value() );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<std::string> RifOpmFlowDeckFile::keywords( bool includeDates )
|
|
{
|
|
std::vector<std::string> 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 && includeDates )
|
|
{
|
|
std::string dateStr = kw.name() + " " + internal::datesKeywordToString( kw );
|
|
values.push_back( dateStr );
|
|
}
|
|
else
|
|
{
|
|
values.push_back( kw.name() );
|
|
}
|
|
}
|
|
return values;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::optional<Opm::DeckKeyword> RifOpmFlowDeckFile::findKeyword( const std::string& keyword )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return std::nullopt;
|
|
|
|
auto keywordIdx = m_fileDeck->find( keyword );
|
|
if ( !keywordIdx.has_value() ) return std::nullopt;
|
|
|
|
return m_fileDeck->operator[]( keywordIdx.value() );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<Opm::DeckKeyword> RifOpmFlowDeckFile::findAllKeywords( const std::string& keyword )
|
|
{
|
|
std::vector<Opm::DeckKeyword> results;
|
|
|
|
if ( m_fileDeck.get() == nullptr ) return results;
|
|
|
|
// Iterate through all keywords in the deck
|
|
for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ )
|
|
{
|
|
const auto& kw = m_fileDeck->operator[]( it );
|
|
if ( kw.name() == keyword )
|
|
{
|
|
results.push_back( kw );
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::hasDatesKeyword()
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
auto idx = m_fileDeck->find( Opm::ParserKeywords::DATES::keywordName );
|
|
return idx.has_value();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::isRestartFile()
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
auto idx = m_fileDeck->find( Opm::ParserKeywords::RESTART::keywordName );
|
|
return idx.has_value();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::set<std::string> RifOpmFlowDeckFile::wellGroupsInFile()
|
|
{
|
|
std::set<std::string> 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::WELSPECS::keywordName )
|
|
{
|
|
for ( int i = 0; i < (int)kw.size(); i++ )
|
|
{
|
|
auto& item = kw.getRecord( i ).getItem( Opm::ParserKeywords::WELSPECS::GROUP::itemName );
|
|
values.insert( item.get<std::string>( 0 ) );
|
|
}
|
|
}
|
|
}
|
|
return values;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<std::string> RifOpmFlowDeckFile::dateStrings()
|
|
{
|
|
std::vector<std::string> 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<std::time_t> RifOpmFlowDeckFile::dates()
|
|
{
|
|
std::vector<std::time_t> 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;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::appendDateKeywords( const std::vector<std::time_t>& dates )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
auto kws = keywords();
|
|
if ( kws.empty() ) return false;
|
|
|
|
auto lastIdx = internal::positionToIndex( (int)kws.size() - 1, m_fileDeck );
|
|
if ( !lastIdx.has_value() ) return false;
|
|
|
|
auto lastKw = m_fileDeck->operator[]( lastIdx.value() );
|
|
m_fileDeck->erase( lastIdx.value() );
|
|
|
|
std::vector<std::time_t> revDates;
|
|
std::reverse_copy( dates.begin(), dates.end(), std::back_inserter( revDates ) );
|
|
|
|
for ( auto dt : revDates )
|
|
{
|
|
Opm::DeckKeyword newKw( ( Opm::ParserKeywords::DATES() ) );
|
|
Opm::DeckRecord newRec;
|
|
|
|
std::tm* lt = localtime( &dt );
|
|
|
|
std::string month = Opm::TimeService::eclipseMonthNames().at( lt->tm_mon + 1 );
|
|
|
|
newRec.addItem( RifOpmDeckTools::item( Opm::ParserKeywords::DATES::DAY::itemName, lt->tm_mday ) );
|
|
newRec.addItem( RifOpmDeckTools::item( Opm::ParserKeywords::DATES::MONTH::itemName, month ) );
|
|
newRec.addItem( RifOpmDeckTools::item( Opm::ParserKeywords::DATES::YEAR::itemName, lt->tm_year + 1900 ) );
|
|
newRec.addItem( RifOpmDeckTools::defaultItem( Opm::ParserKeywords::DATES::TIME::itemName ) );
|
|
|
|
newKw.addRecord( std::move( newRec ) );
|
|
|
|
m_fileDeck->insert( lastIdx.value(), newKw );
|
|
}
|
|
|
|
m_fileDeck->insert( lastIdx.value(), lastKw );
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<int> RifOpmFlowDeckFile::welldims()
|
|
{
|
|
using W = Opm::ParserKeywords::WELLDIMS;
|
|
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::MAXWELLS>().get<int>( 0 ) );
|
|
dims.push_back( rec.getItem<W::MAXCONN>().get<int>( 0 ) );
|
|
dims.push_back( rec.getItem<W::MAXGROUPS>().get<int>( 0 ) );
|
|
dims.push_back( rec.getItem<W::MAX_GROUPSIZE>().get<int>( 0 ) );
|
|
|
|
return dims;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::setWelldims( int maxWells, int maxConnections, int maxGroups, int maxWellsInGroup )
|
|
{
|
|
using W = Opm::ParserKeywords::WELLDIMS;
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
auto idx = m_fileDeck->find( W::keywordName );
|
|
if ( idx.has_value() )
|
|
{
|
|
auto& oldkw = m_fileDeck->operator[]( idx.value() );
|
|
|
|
Opm::DeckKeyword newKw( Opm::ParserKeyword( oldkw.name() ) );
|
|
|
|
newKw.addRecord( Opm::DeckRecord{ { RifOpmDeckTools::item( W::MAXWELLS::itemName, maxWells ),
|
|
RifOpmDeckTools::item( W::MAXCONN::itemName, maxConnections ),
|
|
RifOpmDeckTools::item( W::MAXGROUPS::itemName, maxGroups ),
|
|
RifOpmDeckTools::item( W::MAX_GROUPSIZE::itemName, maxWellsInGroup ) } } );
|
|
|
|
m_fileDeck->erase( idx.value() );
|
|
m_fileDeck->insert( idx.value(), newKw );
|
|
return true;
|
|
}
|
|
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;
|
|
|
|
bool foundWsegdims = true;
|
|
auto idx = m_fileDeck->find( W::keywordName );
|
|
if ( !idx.has_value() )
|
|
{
|
|
foundWsegdims = false;
|
|
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 ) } } );
|
|
|
|
if ( foundWsegdims ) m_fileDeck->erase( idx.value() );
|
|
m_fileDeck->insert( idx.value(), newKw );
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::setSpecgrid( int nx, int ny, int nz )
|
|
{
|
|
using S = Opm::ParserKeywords::SPECGRID;
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
auto idx = m_fileDeck->find( S::keywordName );
|
|
if ( !idx.has_value() ) return false;
|
|
|
|
auto& oldkw = m_fileDeck->operator[]( idx.value() );
|
|
|
|
Opm::DeckKeyword newKw( Opm::ParserKeyword( oldkw.name() ) );
|
|
newKw.addRecord( Opm::DeckRecord{ { RifOpmDeckTools::item( S::NX::itemName, nx ),
|
|
RifOpmDeckTools::item( S::NY::itemName, ny ),
|
|
RifOpmDeckTools::item( S::NZ::itemName, nz ),
|
|
RifOpmDeckTools::item( S::NUMRES::itemName, 1 ),
|
|
RifOpmDeckTools::item( S::COORD_TYPE::itemName, std::string( "F" ) ) } } );
|
|
|
|
m_fileDeck->erase( idx.value() );
|
|
m_fileDeck->insert( idx.value(), newKw );
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::setDimens( int nx, int ny, int nz )
|
|
{
|
|
using D = Opm::ParserKeywords::DIMENS;
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
auto idx = m_fileDeck->find( D::keywordName );
|
|
if ( !idx.has_value() ) return false;
|
|
|
|
auto& oldkw = m_fileDeck->operator[]( idx.value() );
|
|
|
|
Opm::DeckKeyword newKw( Opm::ParserKeyword( oldkw.name() ) );
|
|
newKw.addRecord( Opm::DeckRecord{ { RifOpmDeckTools::item( D::NX::itemName, nx ),
|
|
RifOpmDeckTools::item( D::NY::itemName, ny ),
|
|
RifOpmDeckTools::item( D::NZ::itemName, nz ) } } );
|
|
|
|
m_fileDeck->erase( idx.value() );
|
|
m_fileDeck->insert( idx.value(), newKw );
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<int> RifOpmFlowDeckFile::regdims()
|
|
{
|
|
using R = Opm::ParserKeywords::REGDIMS;
|
|
if ( m_fileDeck.get() == nullptr ) return {};
|
|
auto idx = m_fileDeck->find( R::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( R::NTFIP::itemName ).get<int>( 0 ) );
|
|
dims.push_back( rec.getItem( R::NMFIPR::itemName ).get<int>( 0 ) );
|
|
dims.push_back( rec.getItem( R::NRFREG::itemName ).get<int>( 0 ) );
|
|
dims.push_back( rec.getItem( R::NTFREG::itemName ).get<int>( 0 ) );
|
|
dims.push_back( rec.getItem( R::MAX_ETRACK::itemName ).get<int>( 0 ) );
|
|
dims.push_back( rec.getItem( R::NTCREG::itemName ).get<int>( 0 ) );
|
|
dims.push_back( rec.getItem( R::MAX_OPERNUM::itemName ).get<int>( 0 ) );
|
|
|
|
return dims;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::setRegdims( int maxRegions,
|
|
int maxRegionDefinitions,
|
|
int maxRegionFlowConnections,
|
|
int maxFIPRegions,
|
|
int maxEtrack,
|
|
int maxCompRegions,
|
|
int maxOperNum )
|
|
{
|
|
using R = Opm::ParserKeywords::REGDIMS;
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
auto idx = m_fileDeck->find( R::keywordName );
|
|
if ( idx.has_value() )
|
|
{
|
|
auto& oldkw = m_fileDeck->operator[]( idx.value() );
|
|
|
|
Opm::DeckKeyword newKw( Opm::ParserKeyword( oldkw.name() ) );
|
|
newKw.addRecord( Opm::DeckRecord{ { RifOpmDeckTools::item( R::NTFIP::itemName, maxRegions ),
|
|
RifOpmDeckTools::item( R::NMFIPR::itemName, maxRegionDefinitions ),
|
|
RifOpmDeckTools::item( R::NRFREG::itemName, maxRegionFlowConnections ),
|
|
RifOpmDeckTools::item( R::NTFREG::itemName, maxFIPRegions ),
|
|
RifOpmDeckTools::item( R::MAX_ETRACK::itemName, maxEtrack ),
|
|
RifOpmDeckTools::item( R::NTCREG::itemName, maxCompRegions ),
|
|
RifOpmDeckTools::item( R::MAX_OPERNUM::itemName, maxOperNum ) } } );
|
|
|
|
m_fileDeck->erase( idx.value() );
|
|
m_fileDeck->insert( idx.value(), newKw );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::ensureRegdimsKeyword()
|
|
{
|
|
using R = Opm::ParserKeywords::REGDIMS;
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
// Check if REGDIMS already exists
|
|
auto idx = m_fileDeck->find( R::keywordName );
|
|
if ( idx.has_value() )
|
|
{
|
|
return true; // Already exists
|
|
}
|
|
|
|
// Find RUNSPEC section to add REGDIMS
|
|
auto runspecIdx = m_fileDeck->find( Opm::ParserKeywords::RUNSPEC::keywordName );
|
|
if ( !runspecIdx.has_value() ) return false; // Cannot add REGDIMS without RUNSPEC section
|
|
|
|
// Look for a good insertion point after RUNSPEC and before GRID section
|
|
auto insertIdx = runspecIdx.value();
|
|
insertIdx++; // Start after RUNSPEC keyword
|
|
|
|
// Try to find GRID or other sections that should come after REGDIMS
|
|
for ( auto it = insertIdx; it != m_fileDeck->stop(); it++ )
|
|
{
|
|
auto& kw = m_fileDeck->operator[]( it );
|
|
if ( kw.name() == Opm::ParserKeywords::GRID::keywordName || kw.name() == Opm::ParserKeywords::SOLUTION::keywordName ||
|
|
kw.name() == Opm::ParserKeywords::SCHEDULE::keywordName )
|
|
{
|
|
insertIdx = it;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Create REGDIMS keyword with default values: "6* 1 /"
|
|
// Items: NTFIP NMFIPR NRFREG NTFREG MAX_ETRACK NTCREG MAX_OPERNUM
|
|
// Default values from REGDIMS schema: 1, 1, 0, 0, 0, 1, 0
|
|
// But we want to set MAX_OPERNUM to 1 to reserve one region
|
|
Opm::DeckKeyword regdimsKw( ( Opm::ParserKeyword( R::keywordName ) ) );
|
|
regdimsKw.addRecord( Opm::DeckRecord{ { RifOpmDeckTools::item( R::NTFIP::itemName, 1 ),
|
|
RifOpmDeckTools::item( R::NMFIPR::itemName, 1 ),
|
|
RifOpmDeckTools::item( R::NRFREG::itemName, 0 ),
|
|
RifOpmDeckTools::item( R::NTFREG::itemName, 0 ),
|
|
RifOpmDeckTools::item( R::MAX_ETRACK::itemName, 0 ),
|
|
RifOpmDeckTools::item( R::NTCREG::itemName, 1 ),
|
|
RifOpmDeckTools::item( R::MAX_OPERNUM::itemName, 1 ) } } );
|
|
|
|
m_fileDeck->insert( insertIdx, regdimsKw );
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::replaceAllKeywords( const std::string& keywordName, const std::vector<Opm::DeckKeyword>& keywords )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
// Find all indices where this keyword appears
|
|
std::vector<Opm::FileDeck::Index> indices;
|
|
for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ )
|
|
{
|
|
const auto& kw = m_fileDeck->operator[]( it );
|
|
if ( kw.name() == keywordName )
|
|
{
|
|
indices.push_back( it );
|
|
}
|
|
}
|
|
|
|
if ( indices.empty() ) return true; // No keywords to replace
|
|
|
|
// Save the position of the first occurrence - we'll insert new keywords here
|
|
Opm::FileDeck::Index firstPosition = indices[0];
|
|
|
|
// Remove ALL occurrences in reverse order to maintain valid indices
|
|
for ( auto it = indices.rbegin(); it != indices.rend(); ++it )
|
|
{
|
|
m_fileDeck->erase( *it );
|
|
}
|
|
|
|
// Insert all new keywords at the position where the first occurrence was
|
|
// Insert in reverse order so they end up in correct order
|
|
for ( auto kwIt = keywords.rbegin(); kwIt != keywords.rend(); ++kwIt )
|
|
{
|
|
m_fileDeck->insert( firstPosition, *kwIt );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<std::pair<Opm::FileDeck::Index, Opm::DeckKeyword>> RifOpmFlowDeckFile::findAllKeywordsWithIndices( const std::string& keyword )
|
|
{
|
|
std::vector<std::pair<Opm::FileDeck::Index, Opm::DeckKeyword>> results;
|
|
if ( m_fileDeck.get() == nullptr ) return results;
|
|
|
|
// Iterate through all keywords in the deck
|
|
for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ )
|
|
{
|
|
const auto& kw = m_fileDeck->operator[]( it );
|
|
if ( kw.name() == keyword )
|
|
{
|
|
results.push_back( std::make_pair( it, kw ) );
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::replaceKeywordAtIndex( const Opm::FileDeck::Index& index, const Opm::DeckKeyword& keyword )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
// Erase the keyword at the given index and insert the new one
|
|
m_fileDeck->erase( index );
|
|
m_fileDeck->insert( index, keyword );
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Remove all but one occurrence of the given keyword. Keeping an original occurrence in place
|
|
/// preserves its source block, so a later in-place replace keeps the keyword in its original
|
|
/// (possibly included) file when the deck is saved. Keep the first occurrence for cumulative
|
|
/// keywords (e.g. FAULTS), and the last occurrence for keywords where a later occurrence
|
|
/// overrides an earlier one (e.g. ACTNUM). Returns number of removed keywords.
|
|
//--------------------------------------------------------------------------------------------------
|
|
int RifOpmFlowDeckFile::removeDuplicateKeywords( const std::string& keywordName, bool keepFirstOccurrence )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return 0;
|
|
|
|
// Find all indices where this keyword appears
|
|
std::vector<Opm::FileDeck::Index> indices;
|
|
for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ )
|
|
{
|
|
const auto& kw = m_fileDeck->operator[]( it );
|
|
if ( kw.name() == keywordName )
|
|
{
|
|
indices.push_back( it );
|
|
}
|
|
}
|
|
|
|
if ( indices.size() < 2 ) return 0;
|
|
|
|
// Remove in reverse order to maintain valid indices, keeping one occurrence
|
|
auto first = indices.rbegin();
|
|
auto last = indices.rend();
|
|
if ( keepFirstOccurrence )
|
|
--last;
|
|
else
|
|
++first;
|
|
|
|
for ( auto it = first; it != last; ++it )
|
|
{
|
|
m_fileDeck->erase( *it );
|
|
}
|
|
|
|
return static_cast<int>( indices.size() ) - 1;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::removeKeywordAtIndex( const Opm::FileDeck::Index& index )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
// Erase the keyword at the given index and insert the new one
|
|
m_fileDeck->erase( index );
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Returns number of removed keywords
|
|
//--------------------------------------------------------------------------------------------------
|
|
int RifOpmFlowDeckFile::removeKeywords( const std::string& keywordName )
|
|
{
|
|
std::map<std::string, std::string> blockKeywordNames =
|
|
{ { Opm::ParserKeywords::ACTIONX::keywordName, Opm::ParserKeywords::ENDACTIO::keywordName },
|
|
{ Opm::ParserKeywords::BOX::keywordName, Opm::ParserKeywords::ENDBOX::keywordName },
|
|
{ Opm::ParserKeywords::ACTIONW::keywordName, Opm::ParserKeywords::ENDACTIO::keywordName } };
|
|
|
|
int nRemoved = 0;
|
|
if ( m_fileDeck.get() == nullptr ) return nRemoved;
|
|
|
|
// Find all the matching keywords
|
|
std::vector<Opm::FileDeck::Index> skipIndices;
|
|
for ( auto it = m_fileDeck->start(); it != m_fileDeck->stop(); it++ )
|
|
{
|
|
const auto& kw = m_fileDeck->operator[]( it );
|
|
if ( kw.name() == keywordName )
|
|
{
|
|
// If this is a block keyword, we need to skip all keywords until the corresponding end block
|
|
if ( blockKeywordNames.contains( keywordName ) )
|
|
{
|
|
const std::string endBlockName = blockKeywordNames.at( keywordName );
|
|
while ( it != m_fileDeck->stop() )
|
|
{
|
|
const auto& innerKw = m_fileDeck->operator[]( it );
|
|
if ( innerKw.name() == endBlockName )
|
|
{
|
|
skipIndices.push_back( it );
|
|
break;
|
|
}
|
|
skipIndices.push_back( it );
|
|
it++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
skipIndices.push_back( it );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove in reverse order to maintain valid indices
|
|
for ( auto it = skipIndices.rbegin(); it != skipIndices.rend(); ++it )
|
|
{
|
|
m_fileDeck->erase( *it );
|
|
nRemoved++;
|
|
}
|
|
|
|
return nRemoved;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::addIncludeKeyword( std::string section, std::string keyword, std::string filePath )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
// Find insertion point within the section
|
|
auto insertIdx = internal::findSectionInsertionPoint( m_fileDeck, section );
|
|
if ( !insertIdx.has_value() ) return false; // Section not found
|
|
|
|
// Create the INCLUDE keyword
|
|
Opm::DeckKeyword includeKw( ( Opm::ParserKeywords::INCLUDE() ) );
|
|
m_fileDeck->erase( insertIdx.value() );
|
|
|
|
includeKw.addRecord( Opm::DeckRecord{ { RifOpmDeckTools::item( Opm::ParserKeywords::INCLUDE::IncludeFile::itemName, filePath ) } } );
|
|
|
|
m_fileDeck->insert( insertIdx.value(), includeKw );
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
Opm::FileDeck* RifOpmFlowDeckFile::fileDeck()
|
|
{
|
|
return m_fileDeck.get();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
const Opm::Deck* RifOpmFlowDeckFile::deck() const
|
|
{
|
|
return m_deck.get();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::addKeyword( const std::string& section, const Opm::DeckKeyword& keyword )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
// Add keyword to specified section if it doesn't exist
|
|
auto insertPos = internal::findSectionInsertionPoint( m_fileDeck, section );
|
|
if ( !insertPos.has_value() )
|
|
{
|
|
return false; // Section not found
|
|
}
|
|
|
|
m_fileDeck->insert( insertPos.value(), keyword );
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::replaceKeyword( const std::string& section, const Opm::DeckKeyword& keyword )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
// Add keyword to specified section if it doesn't exist
|
|
auto insertPos = internal::findSectionInsertionPoint( m_fileDeck, section );
|
|
if ( !insertPos.has_value() )
|
|
{
|
|
return false; // Section not found
|
|
}
|
|
|
|
// Find and replace keyword in deck
|
|
auto keywordIdx = m_fileDeck->find( keyword.name() );
|
|
if ( keywordIdx.has_value() )
|
|
{
|
|
// Replace existing keyword
|
|
m_fileDeck->erase( keywordIdx.value() );
|
|
m_fileDeck->insert( keywordIdx.value(), keyword );
|
|
}
|
|
else
|
|
{
|
|
m_fileDeck->insert( insertPos.value(), keyword );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::insertKeywordAtSectionStart( const std::string& section, const Opm::DeckKeyword& keyword )
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
auto insertPos = internal::findSectionStartPoint( m_fileDeck, section );
|
|
if ( !insertPos.has_value() ) return false;
|
|
|
|
m_fileDeck->insert( insertPos.value(), keyword );
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::restartAtTimeStep( int timeStep, std::string deckName )
|
|
{
|
|
if ( !m_fileDeck ) return false;
|
|
|
|
m_fileDeck->rst_solution( deckName, timeStep );
|
|
m_fileDeck->insert_skiprest();
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::stopAtTimeStep( int timeStep )
|
|
{
|
|
if ( !m_fileDeck ) return false;
|
|
|
|
auto dateIdx = internal::locateTimeStep( m_fileDeck, timeStep );
|
|
if ( dateIdx.has_value() )
|
|
{
|
|
Opm::DeckKeyword newKw( ( Opm::ParserKeywords::END() ) );
|
|
m_fileDeck->insert( dateIdx.value(), newKw );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::replaceKeyword( const std::string& keyword, const std::vector<double>& data, bool dataKeyword )
|
|
{
|
|
try
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
// Find the keyword in the deck
|
|
auto keywordIdx = m_fileDeck->find( keyword );
|
|
if ( !keywordIdx.has_value() ) return false;
|
|
|
|
// Get the existing keyword to preserve location
|
|
const auto& existingKw = m_fileDeck->operator[]( keywordIdx.value() );
|
|
if ( dataKeyword != existingKw.isDataKeyword() )
|
|
{
|
|
return false; // Mismatch in data keyword flag
|
|
}
|
|
|
|
// Create a new keyword with the same settings
|
|
Opm::DeckKeyword newKw = existingKw.emptyStructuralCopy();
|
|
|
|
// Keywords ending with "NUM" should be integers (e.g., SATNUM, PVTNUM, EQLNUM)
|
|
bool isIntType = keyword.size() >= 3 && keyword.substr( keyword.size() - 3 ) == "NUM";
|
|
|
|
// Create a record with the new data
|
|
Opm::DeckRecord record;
|
|
if ( isIntType )
|
|
{
|
|
// Create integer item
|
|
Opm::DeckItem item( keyword, int() );
|
|
for ( const auto& value : data )
|
|
{
|
|
item.push_back( static_cast<int>( std::round( value ) ) );
|
|
}
|
|
record.addItem( std::move( item ) );
|
|
}
|
|
else
|
|
{
|
|
// Create double item
|
|
std::vector<Opm::Dimension> active_dimensions;
|
|
std::vector<Opm::Dimension> default_dimensions;
|
|
Opm::DeckItem item( keyword, double(), active_dimensions, default_dimensions );
|
|
for ( const auto& value : data )
|
|
{
|
|
item.push_back( value );
|
|
}
|
|
record.addItem( std::move( item ) );
|
|
}
|
|
|
|
newKw.addRecord( std::move( record ) );
|
|
|
|
// Replace the keyword in the deck
|
|
m_fileDeck->erase( keywordIdx.value() );
|
|
m_fileDeck->insert( keywordIdx.value(), newKw );
|
|
|
|
return true;
|
|
}
|
|
catch ( std::exception& )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifOpmFlowDeckFile::replaceKeyword( const std::string& keyword, const std::vector<int>& data, bool dataKeyword )
|
|
{
|
|
try
|
|
{
|
|
if ( m_fileDeck.get() == nullptr ) return false;
|
|
|
|
// Find the keyword in the deck
|
|
auto keywordIdx = m_fileDeck->find( keyword );
|
|
if ( !keywordIdx.has_value() ) return false;
|
|
|
|
// Get the existing keyword to preserve location
|
|
const auto& existingKw = m_fileDeck->operator[]( keywordIdx.value() );
|
|
if ( dataKeyword != existingKw.isDataKeyword() )
|
|
{
|
|
return false; // Mismatch in data keyword flag
|
|
}
|
|
|
|
// Create a new keyword with the same settings
|
|
Opm::DeckKeyword newKw = existingKw.emptyStructuralCopy();
|
|
|
|
// Create a record with the new data
|
|
Opm::DeckRecord record;
|
|
// Create integer item
|
|
Opm::DeckItem item( keyword, int() );
|
|
for ( const auto& value : data )
|
|
{
|
|
item.push_back( value );
|
|
}
|
|
record.addItem( std::move( item ) );
|
|
|
|
newKw.addRecord( std::move( record ) );
|
|
|
|
// Replace the keyword in the deck
|
|
m_fileDeck->erase( keywordIdx.value() );
|
|
m_fileDeck->insert( keywordIdx.value(), newKw );
|
|
|
|
return true;
|
|
}
|
|
catch ( std::exception& )
|
|
{
|
|
return false;
|
|
}
|
|
}
|