#14513 Orion Events: Add raw schedule text events

This commit is contained in:
Kristian Bendiksen committed 2026-08-24 13:26:19 +02:00
1 parent 1803c636a5
commit d6878ea562
14 files changed
+810 -78

No files matched your search

@@ -28,6 +28,7 @@
#include "ProjectDataModel/Jobs/RimKeywordFactory.h"
#include "RimEclipseCase.h"
#include "RimKeywordEvent.h"
#include "RimWellEventRawText.h"
#include "RimWellEventTimeline.h"
#include "RimWellEventWellSpec.h"
#include "RimWellPath.h"
@@ -47,13 +48,13 @@
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RicScheduleDataGenerator::generateSchedule( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const std::vector<QDateTime>& dates,
const std::set<const RimWellPath*>& mswWells,
bool firstDateAsComment,
bool alignColumns )
std::expected<QString, QString> RicScheduleDataGenerator::generateSchedule( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const std::vector<QDateTime>& dates,
const std::set<const RimWellPath*>& mswWells,
bool firstDateAsComment,
bool alignColumns )
{
QString result;
@@ -101,12 +102,10 @@ QString RicScheduleDataGenerator::generateSchedule( const RimWellEventTimeline&
{
if ( restartDate.has_value() && date < *restartDate ) continue;
QString dateSection =
auto dateSection =
generateDateSection( timeline, eclipseCase, sortedWellPaths, date, mswWells, isFirstDate && firstDateAsComment, alignColumns );
if ( !dateSection.isEmpty() )
{
result += dateSection;
}
if ( !dateSection ) return std::unexpected( dateSection.error() );
result += *dateSection;
isFirstDate = false;
}
@@ -159,13 +158,13 @@ void RicScheduleDataGenerator::mergeKeyword( std::map<QString, Opm::DeckKeyword>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RicScheduleDataGenerator::generateDateSection( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const QDateTime& date,
const std::set<const RimWellPath*>& mswWells,
bool dateAsComment,
bool alignColumns )
std::expected<QString, QString> RicScheduleDataGenerator::generateDateSection( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const QDateTime& date,
const std::set<const RimWellPath*>& mswWells,
bool dateAsComment,
bool alignColumns )
{
// Keyword priority order for output
static const std::vector<QString> keywordOrder = { "WELSPECS",
@@ -188,6 +187,33 @@ QString RicScheduleDataGenerator::generateDateSection( const RimWellEventTimelin
QString result;
auto events = timeline.getEventsAtDate( date );
std::vector<const RimWellEventRawText*> rawTextEvents;
for ( const auto* event : events )
{
if ( event->eventType() != RimWellEvent::EventType::RAW_TEXT ) continue;
if ( const auto* rawTextEvent = dynamic_cast<const RimWellEventRawText*>( event ) )
{
rawTextEvents.push_back( rawTextEvent );
}
}
std::stable_sort( rawTextEvents.begin(),
rawTextEvents.end(),
[]( const RimWellEventRawText* lhs, const RimWellEventRawText* rhs ) { return lhs->priority() < rhs->priority(); } );
auto appendRawText = [&]( RimWellEventRawText::Placement placement, const QString& anchorKeyword = QString() )
{
for ( const auto* rawTextEvent : rawTextEvents )
{
if ( rawTextEvent->placement() != placement ) continue;
if ( !anchorKeyword.isEmpty() && rawTextEvent->anchorKeyword().compare( anchorKeyword, Qt::CaseInsensitive ) != 0 ) continue;
result += rawTextEvent->text();
if ( !result.endsWith( '\n' ) ) result += '\n';
}
};
auto serializeKeyword = [&]( const Opm::DeckKeyword& kw )
{ return alignColumns ? RimKeywordFactory::deckKeywordToAlignedString( kw ) : RimKeywordFactory::deckKeywordToString( kw ); };
@@ -212,6 +238,7 @@ QString RicScheduleDataGenerator::generateDateSection( const RimWellEventTimelin
{
result += serializeKeyword( RimKeywordFactory::datesKeyword( date ) ) + "\n";
}
appendRawText( RimWellEventRawText::Placement::AFTER_DATE );
// Records for each keyword name are accumulated across wells, then serialised once below.
std::map<QString, Opm::DeckKeyword> keywordBlocks;
@@ -240,7 +267,6 @@ QString RicScheduleDataGenerator::generateDateSection( const RimWellEventTimelin
}
// Schedule-level keyword events (not tied to a specific well)
auto events = timeline.getEventsAtDate( date );
for ( auto* event : events )
{
if ( event->eventType() != RimWellEvent::EventType::SCHEDULE_KEYWORD ) continue;
@@ -280,6 +306,10 @@ QString RicScheduleDataGenerator::generateDateSection( const RimWellEventTimelin
const auto* keywordEvent = dynamic_cast<const RimKeywordEvent*>( event );
isRelevant = keywordEvent && keywordEvent->keywordName().compare( keywordName, Qt::CaseInsensitive ) == 0;
}
else if ( event->eventType() == RimWellEvent::EventType::RAW_TEXT )
{
continue;
}
else
{
auto eventKeyword = RifEventKeywordFormatter::buildWellEvent( event, event->wellName() );
@@ -304,43 +334,76 @@ QString RicScheduleDataGenerator::generateDateSection( const RimWellEventTimelin
result += "\n";
};
auto appendUnmergedBlocks = [&]( const QString& name )
std::set<QString> availableKeywords;
for ( const auto& [name, keyword] : keywordBlocks )
{
auto it = unmergedBlocks.find( name );
if ( it == unmergedBlocks.end() ) return;
for ( const auto& kw : it->second )
{
appendKeywordText( kw );
}
};
availableKeywords.insert( name );
}
for ( const auto& [name, blocks] : unmergedBlocks )
{
if ( !blocks.empty() ) availableKeywords.insert( name );
}
// Output keywords in priority order
std::set<QString> emitted;
for ( const auto& kw : keywordOrder )
for ( const auto* rawTextEvent : rawTextEvents )
{
if ( auto it = keywordBlocks.find( kw ); it != keywordBlocks.end() )
if ( rawTextEvent->text().isEmpty() )
{
return std::unexpected( QString( "Raw text event at %1 has no text" ).arg( date.toString( Qt::ISODate ) ) );
}
const bool anchored = rawTextEvent->placement() == RimWellEventRawText::Placement::BEFORE_KEYWORD ||
rawTextEvent->placement() == RimWellEventRawText::Placement::AFTER_KEYWORD;
if ( anchored && rawTextEvent->anchorKeyword().isEmpty() )
{
return std::unexpected( QString( "Raw text event at %1 requires an anchor keyword" ).arg( date.toString( Qt::ISODate ) ) );
}
if ( !anchored && !rawTextEvent->anchorKeyword().isEmpty() )
{
return std::unexpected(
QString( "Raw text event at %1 has an anchor keyword for a placement that does not use one" ).arg( date.toString( Qt::ISODate ) ) );
}
if ( anchored && !availableKeywords.contains( rawTextEvent->anchorKeyword() ) )
{
return std::unexpected( QString( "Raw text event at %1 references keyword '%2', which is not emitted on that date" )
.arg( date.toString( Qt::ISODate ), rawTextEvent->anchorKeyword() ) );
}
}
auto appendKeywordBlocks = [&]( const QString& name )
{
if ( !availableKeywords.contains( name ) ) return;
appendRawText( RimWellEventRawText::Placement::BEFORE_KEYWORD, name );
if ( auto it = keywordBlocks.find( name ); it != keywordBlocks.end() )
{
appendKeywordText( it->second );
}
appendUnmergedBlocks( kw );
emitted.insert( kw );
}
// Output remaining keywords not in the priority list
for ( const auto& [kw, deckKw] : keywordBlocks )
{
if ( emitted.contains( kw ) ) continue;
appendKeywordText( deckKw );
}
for ( const auto& [kw, blocks] : unmergedBlocks )
{
if ( emitted.contains( kw ) ) continue;
for ( const auto& deckKw : blocks )
if ( auto it = unmergedBlocks.find( name ); it != unmergedBlocks.end() )
{
appendKeywordText( deckKw );
for ( const auto& keyword : it->second )
{
appendKeywordText( keyword );
}
}
appendRawText( RimWellEventRawText::Placement::AFTER_KEYWORD, name );
};
// Output keywords in priority order.
std::set<QString> emitted;
for ( const auto& name : keywordOrder )
{
appendKeywordBlocks( name );
emitted.insert( name );
}
// Output remaining keywords alphabetically, as before.
for ( const auto& name : availableKeywords )
{
if ( emitted.contains( name ) ) continue;
appendKeywordBlocks( name );
}
appendRawText( RimWellEventRawText::Placement::END_OF_DATE );
return result;
}
@@ -21,6 +21,7 @@
#include <QDateTime>
#include <QString>
#include <expected>
#include <map>
#include <optional>
#include <set>
@@ -53,13 +54,13 @@ public:
// equals the simulation start date. Later dates are always emitted as DATES keywords.
// When alignColumns is true, keywords are serialised with a "--"-prefixed column-header comment
// and right-aligned, fixed-width columns instead of the compact default form.
static QString generateSchedule( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const std::vector<QDateTime>& dates,
const std::set<const RimWellPath*>& mswWells,
bool firstDateAsComment = true,
bool alignColumns = false );
static std::expected<QString, QString> generateSchedule( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const std::vector<QDateTime>& dates,
const std::set<const RimWellPath*>& mswWells,
bool firstDateAsComment = true,
bool alignColumns = false );
// Collect all unique dates from all wells' timelines
static std::vector<QDateTime> collectAllDates( const RimWellEventTimeline& timeline, const std::vector<RimWellPath*>& wellPaths );
@@ -67,13 +68,13 @@ public:
private:
// Generate schedule section for a single date. When dateAsComment is true, the date is
// written as a comment line instead of a DATES keyword.
static QString generateDateSection( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const QDateTime& date,
const std::set<const RimWellPath*>& mswWells,
bool dateAsComment = false,
bool alignColumns = false );
static std::expected<QString, QString> generateDateSection( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const QDateTime& date,
const std::set<const RimWellPath*>& mswWells,
bool dateAsComment = false,
bool alignColumns = false );
static std::optional<Opm::DeckKeyword>
generateWelspecsForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date );