Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@
#include <QString>

#include <map>
#include <optional>
#include <vector>

class RimEclipseCase;
class RimWellPath;
class RimWellEvent;
class RimWellEventTimeline;

namespace Opm
{
class DeckKeyword;
} // namespace Opm

//==================================================================================================
///
/// Generator for Eclipse schedule data based on well events
Expand All @@ -37,11 +43,15 @@ class RimWellEventTimeline;
class RicScheduleDataGenerator
{
public:
// Generate schedule for multiple wells at specified dates
// Generate schedule for multiple wells at specified dates.
// includeWelsegs / includeCompsegs gate the corresponding multi-segment-well keywords;
// WSEGVALV and WSEGAICD remain unaffected.
static QString generateSchedule( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const std::vector<QDateTime>& dates );
const std::vector<QDateTime>& dates,
bool includeWelsegs = true,
bool includeCompsegs = true );

// Collect all unique dates from all wells' timelines
static std::vector<QDateTime> collectAllDates( const RimWellEventTimeline& timeline, const std::vector<RimWellPath*>& wellPaths );
Expand All @@ -51,28 +61,33 @@ class RicScheduleDataGenerator
static QString generateDateSection( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const QDateTime& date );
const QDateTime& date,
bool includeWelsegs,
bool includeCompsegs );

static QString
static std::optional<Opm::DeckKeyword>
generateWelspecsForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date );

// Generate COMPDAT for a well at a specific date based on events
static QString
static std::optional<Opm::DeckKeyword>
generateCompdatForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date );

// Generate WELSEGS and COMPSEGS for a well at a specific date, populating keyword blocks map
static void generateMswForWell( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
RimWellPath& well,
const QDateTime& date,
std::map<QString, QString>& keywordBlocks );
// Generate WELSEGS / COMPSEGS / WSEGVALV / WSEGAICD for a well at a specific date, merging into the accumulator.
// includeWelsegs/includeCompsegs suppress only those two keywords; WSEGVALV/WSEGAICD always emit.
static void generateMswForWell( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
RimWellPath& well,
const QDateTime& date,
std::map<QString, Opm::DeckKeyword>& keywordBlocks,
bool includeWelsegs,
bool includeCompsegs );

// Generate well control keywords (WCONPROD, WCONINJE, WELOPEN) for a well at a specific date, populating keyword blocks map
static void generateWellControlForWell( const RimWellEventTimeline& timeline,
const RimWellPath& well,
const QDateTime& date,
std::map<QString, QString>& keywordBlocks );
// Generate well control / well keyword event keywords for a well at a specific date, merging into the accumulator
static void generateWellControlForWell( const RimWellEventTimeline& timeline,
const RimWellPath& well,
const QDateTime& date,
std::map<QString, Opm::DeckKeyword>& keywordBlocks );

// Extract keyword name from the first non-comment line of a keyword block
static QString extractKeywordName( const QString& block );
// Append records of `kw` into the entry for `name`, creating it from `kw` if absent
static void mergeKeyword( std::map<QString, Opm::DeckKeyword>& acc, const QString& name, Opm::DeckKeyword kw );
};
222 changes: 174 additions & 48 deletions ApplicationLibCode/FileInterface/RifEventKeywordFormatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,33 @@
#include "opm/input/eclipse/Deck/DeckOutput.hpp"
#include "opm/input/eclipse/Deck/DeckRecord.hpp"
#include "opm/input/eclipse/Parser/Parser.hpp"
#include "opm/input/eclipse/Parser/ParserItem.hpp"
#include "opm/input/eclipse/Parser/ParserKeyword.hpp"
#include "opm/input/eclipse/Parser/ParserKeywords/W.hpp"
#include "opm/input/eclipse/Parser/ParserRecord.hpp"

#include <optional>
#include <sstream>
#include <unordered_map>
#include <unordered_set>

namespace
{
QString keywordToString( const Opm::DeckKeyword& kw )
{
if ( kw.name().empty() ) return {};
std::ostringstream oss;
Opm::DeckOutput out( oss, 10 );
kw.write( out );
return QString::fromStdString( oss.str() );
}
} // namespace

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RifEventKeywordFormatter::formatKeyword( const QString& keywordName, const std::vector<RimWellEventKeywordItem*>& items )
std::optional<Opm::DeckKeyword> RifEventKeywordFormatter::buildKeyword( const QString& keywordName,
const std::vector<RimWellEventKeywordItem*>& items )
{
QString keyword = keywordName.toUpper();
std::string kwName = keyword.toStdString();
Expand All @@ -52,52 +70,148 @@ QString RifEventKeywordFormatter::formatKeyword( const QString& keywordName, con
const Opm::ParserKeyword& parserKw = parser.getKeyword( kwName );
Opm::DeckKeyword kw( parserKw );

// Build deck items from stored data
std::vector<Opm::DeckItem> deckItems;
for ( const auto* item : items )
const Opm::ParserRecord& parserRecord = parserKw.getRecord( 0 );

auto stringValue = []( const RimWellEventKeywordItem* item ) -> std::string
{
switch ( item->itemType() )
{
case RimWellEventKeywordItem::ItemType::STRING:
deckItems.push_back( RifOpmDeckTools::item( item->itemName().toStdString(), item->stringValue().toStdString() ) );
break;
return item->stringValue().toStdString();
case RimWellEventKeywordItem::ItemType::INTEGER:
deckItems.push_back( RifOpmDeckTools::item( item->itemName().toStdString(), item->intValue() ) );
break;
return std::to_string( item->intValue() );
case RimWellEventKeywordItem::ItemType::DOUBLE:
deckItems.push_back( RifOpmDeckTools::item( item->itemName().toStdString(), item->doubleValue() ) );
break;
return std::to_string( item->doubleValue() );
case RimWellEventKeywordItem::ItemType::FLAG:
return "";
}
}
return "";
};

if ( !deckItems.empty() )
// RPTRST / RPTSCHED-style keywords have a single ALL-sized item that holds a free-form
// list of mnemonics ("BASIC=2 DEN ROCKC ..."). Emit each user-supplied key either as
// "KEY=VALUE" (typed value) or bare "KEY" (FLAG), all packed into one DeckItem.
const bool isMnemonicList = parserRecord.size() == 1 && parserRecord.get( 0 ).sizeType() == Opm::ParserItem::item_size::ALL;

if ( isMnemonicList )
{
kw.addRecord( Opm::DeckRecord{ std::move( deckItems ) } );
}
std::vector<std::string> tokens;
tokens.reserve( items.size() );
for ( const auto* item : items )
{
const std::string name = item->itemName().toStdString();
if ( item->itemType() == RimWellEventKeywordItem::ItemType::FLAG )
{
tokens.push_back( name );
}
else
{
tokens.push_back( name + "=" + stringValue( item ) );
}
}

// Return empty string if keyword creation failed
if ( kw.name().empty() )
if ( !tokens.empty() )
{
std::vector<Opm::DeckItem> deckItems;
deckItems.push_back( RifOpmDeckTools::rawStringItem( parserRecord.get( 0 ).name(), std::move( tokens ) ) );
kw.addRecord( Opm::DeckRecord{ std::move( deckItems ) } );
}
}
else
{
return "";
// Positional keyword (WCONHIST, WELTARG, ...): emit items in the schema-defined order
// regardless of caller-supplied order (e.g. Python dict insertion order).
std::unordered_map<std::string, const RimWellEventKeywordItem*> userItemsByName;
userItemsByName.reserve( items.size() );
for ( const auto* item : items )
{
userItemsByName.emplace( item->itemName().toStdString(), item );
}

// Highest canonical index actually provided by the user. Trailing unspecified items are
// dropped, intermediate gaps become default markers ("1*").
std::optional<size_t> lastProvidedIdx;
std::unordered_set<std::string> canonicalNames;
canonicalNames.reserve( parserRecord.size() );
for ( size_t i = 0; i < parserRecord.size(); ++i )
{
const std::string& name = parserRecord.get( i ).name();
canonicalNames.insert( name );
if ( userItemsByName.contains( name ) )
{
lastProvidedIdx = i;
}
}

auto appendDeckItem = [&]( std::vector<Opm::DeckItem>& out, const std::string& name, const RimWellEventKeywordItem* item )
{
switch ( item->itemType() )
{
case RimWellEventKeywordItem::ItemType::STRING:
out.push_back( RifOpmDeckTools::item( name, item->stringValue().toStdString() ) );
break;
case RimWellEventKeywordItem::ItemType::INTEGER:
out.push_back( RifOpmDeckTools::item( name, item->intValue() ) );
break;
case RimWellEventKeywordItem::ItemType::DOUBLE:
out.push_back( RifOpmDeckTools::item( name, item->doubleValue() ) );
break;
case RimWellEventKeywordItem::ItemType::FLAG:
// FLAG only makes sense for mnemonic-list keywords; for positional schemas
// there is no meaningful value to emit, so render as a default marker.
out.push_back( RifOpmDeckTools::defaultItem( name ) );
break;
}
};

std::vector<Opm::DeckItem> deckItems;
if ( lastProvidedIdx.has_value() )
{
deckItems.reserve( *lastProvidedIdx + 1 );
for ( size_t i = 0; i <= *lastProvidedIdx; ++i )
{
const std::string& name = parserRecord.get( i ).name();
auto it = userItemsByName.find( name );
if ( it == userItemsByName.end() )
{
deckItems.push_back( RifOpmDeckTools::defaultItem( name ) );
}
else
{
appendDeckItem( deckItems, name, it->second );
}
}
}

// Items not in the keyword's schema are still emitted in caller-supplied order
// after the canonical block.
for ( const auto* item : items )
{
const std::string name = item->itemName().toStdString();
if ( canonicalNames.contains( name ) ) continue;
appendDeckItem( deckItems, name, item );
}

if ( !deckItems.empty() )
{
kw.addRecord( Opm::DeckRecord{ std::move( deckItems ) } );
}
}

// Format with OPM DeckOutput
std::ostringstream oss;
Opm::DeckOutput out( oss, 10 );
kw.write( out );
return QString::fromStdString( oss.str() );
if ( kw.name().empty() ) return std::nullopt;
return kw;
}
catch ( const std::exception& e )
{
RiaLogging::error( std::format( "Failed to create keyword '{}': {}", keyword, e.what() ) );
return "";
return std::nullopt;
}
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RifEventKeywordFormatter::formatWconprod( const RimWellEventControl* controlEvent, const QString& wellName )
std::optional<Opm::DeckKeyword> RifEventKeywordFormatter::buildWconprod( const RimWellEventControl* controlEvent, const QString& wellName )
{
using W = Opm::ParserKeywords::WCONPROD;

Expand Down Expand Up @@ -133,17 +247,13 @@ QString RifEventKeywordFormatter::formatWconprod( const RimWellEventControl* con

Opm::DeckKeyword kw{ W() };
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );

std::ostringstream oss;
Opm::DeckOutput out( oss, 10 );
kw.write( out );
return QString::fromStdString( oss.str() );
return kw;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RifEventKeywordFormatter::formatWconinje( const RimWellEventControl* controlEvent, const QString& wellName )
std::optional<Opm::DeckKeyword> RifEventKeywordFormatter::buildWconinje( const RimWellEventControl* controlEvent, const QString& wellName )
{
using W = Opm::ParserKeywords::WCONINJE;

Expand Down Expand Up @@ -171,41 +281,57 @@ QString RifEventKeywordFormatter::formatWconinje( const RimWellEventControl* con

Opm::DeckKeyword kw{ W() };
kw.addRecord( Opm::DeckRecord{ std::move( items ) } );

std::ostringstream oss;
Opm::DeckOutput out( oss, 10 );
kw.write( out );
return QString::fromStdString( oss.str() );
return kw;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RifEventKeywordFormatter::formatWellEvent( const RimWellEvent* event, const QString& wellName )
std::optional<Opm::DeckKeyword> RifEventKeywordFormatter::buildWellEvent( const RimWellEvent* event, const QString& wellName )
{
if ( event->eventType() == RimWellEvent::EventType::WCONTROL )
{
auto* controlEvent = dynamic_cast<const RimWellEventControl*>( event );
const auto* controlEvent = dynamic_cast<const RimWellEventControl*>( event );
if ( controlEvent )
{
if ( controlEvent->isProducer() )
{
return formatWconprod( controlEvent, wellName );
}
else
{
return formatWconinje( controlEvent, wellName );
}
return controlEvent->isProducer() ? buildWconprod( controlEvent, wellName ) : buildWconinje( controlEvent, wellName );
}
}
else if ( event->eventType() == RimWellEvent::EventType::KEYWORD )
{
auto* keywordEvent = dynamic_cast<const RimWellEventKeyword*>( event );
const auto* keywordEvent = dynamic_cast<const RimWellEventKeyword*>( event );
if ( keywordEvent )
{
return formatKeyword( keywordEvent->keywordName(), keywordEvent->items() );
return buildKeyword( keywordEvent->keywordName(), keywordEvent->items() );
}
}

return {};
return std::nullopt;
}

//--------------------------------------------------------------------------------------------------
/// QString-returning wrappers: build the keyword and serialize once.
//--------------------------------------------------------------------------------------------------
QString RifEventKeywordFormatter::formatKeyword( const QString& keywordName, const std::vector<RimWellEventKeywordItem*>& items )
{
auto kw = buildKeyword( keywordName, items );
return kw ? keywordToString( *kw ) : QString();
}

QString RifEventKeywordFormatter::formatWconprod( const RimWellEventControl* controlEvent, const QString& wellName )
{
auto kw = buildWconprod( controlEvent, wellName );
return kw ? keywordToString( *kw ) : QString();
}

QString RifEventKeywordFormatter::formatWconinje( const RimWellEventControl* controlEvent, const QString& wellName )
{
auto kw = buildWconinje( controlEvent, wellName );
return kw ? keywordToString( *kw ) : QString();
}

QString RifEventKeywordFormatter::formatWellEvent( const RimWellEvent* event, const QString& wellName )
{
auto kw = buildWellEvent( event, wellName );
return kw ? keywordToString( *kw ) : QString();
}
Loading
Loading