mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 06:38:13 -05:00
#14683 Avoid building an Opm::Parser per keyword in schedule generation
Constructing an Opm::Parser populates the complete keyword database (2774 keywords, measured at 2.4 ms per construction) and this was done once per keyword event in RifEventKeywordFormatter::buildKeyword, and twice more per keyword in the column-aligned serialisation in RimKeywordFactory. Schedule generation was therefore completely dominated by parser construction and grew directly with the number of events on the timeline. Use a single shared parser instance instead, since the parser is stateless once constructed. Also compute the events for a date once in generateDateSection and pass them to the per-well helpers, instead of calling RimWellEventTimeline::getEventsAtDate four times per well and date, which scaled as dates x wells x events. Generating a schedule for a production data set with 1737 events over 490 dates goes from 4.2 s to 0.1 s, and 21000 events over 14 wells now complete in 0.5 s.
This commit is contained in:
1 parent
51a4fa74bf
commit
30254df9e9
4 files changed
+56
-43
No files matched your search
@@ -266,7 +266,7 @@ std::expected<QString, QString> RicScheduleDataGenerator::generateDateSection( c
|
||||
{
|
||||
if ( !well ) continue;
|
||||
|
||||
if ( auto welspecs = generateWelspecsForWell( timeline, eclipseCase, *well, date ) )
|
||||
if ( auto welspecs = generateWelspecsForWell( timeline, eclipseCase, *well, date, events ) )
|
||||
{
|
||||
mergeKeyword( keywordBlocks, "WELSPECS", std::move( *welspecs ) );
|
||||
mergeKeyword( keywordBlocks,
|
||||
@@ -274,10 +274,10 @@ std::expected<QString, QString> RicScheduleDataGenerator::generateDateSection( c
|
||||
RimKeywordFactory::compordKeyword( well->completionSettings()->wellNameForExport().toStdString() ) );
|
||||
}
|
||||
|
||||
generateCompletionsForWell( timeline, eclipseCase, *well, date, keywordBlocks );
|
||||
generateCompletionsForWell( eclipseCase, *well, date, events, keywordBlocks );
|
||||
|
||||
generateMswForWell( timeline, eclipseCase, *well, date, keywordBlocks, unmergedBlocks, mswWells );
|
||||
generateWellControlForWell( timeline, *well, date, keywordBlocks );
|
||||
generateMswForWell( eclipseCase, *well, date, events, keywordBlocks, unmergedBlocks, mswWells );
|
||||
generateWellControlForWell( *well, events, keywordBlocks );
|
||||
}
|
||||
|
||||
// Schedule-level keyword events (not tied to a specific well)
|
||||
@@ -424,15 +424,14 @@ std::expected<QString, QString> RicScheduleDataGenerator::generateDateSection( c
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::optional<Opm::DeckKeyword> RicScheduleDataGenerator::generateWelspecsForWell( const RimWellEventTimeline& timeline,
|
||||
RimEclipseCase& eclipseCase,
|
||||
RimWellPath& well,
|
||||
const QDateTime& date )
|
||||
std::optional<Opm::DeckKeyword> RicScheduleDataGenerator::generateWelspecsForWell( const RimWellEventTimeline& timeline,
|
||||
RimEclipseCase& eclipseCase,
|
||||
RimWellPath& well,
|
||||
const QDateTime& date,
|
||||
const std::vector<RimWellEvent*>& eventsAtDate )
|
||||
{
|
||||
auto events = timeline.getEventsAtDate( date );
|
||||
|
||||
bool hasEvents = false;
|
||||
for ( auto* event : events )
|
||||
for ( auto* event : eventsAtDate )
|
||||
{
|
||||
if ( ( event->eventType() == RimWellEvent::EventType::PERF || event->eventType() == RimWellEvent::EventType::VALVE ||
|
||||
event->eventType() == RimWellEvent::EventType::TUBING || event->eventType() == RimWellEvent::EventType::WELLSPEC ) &&
|
||||
@@ -479,16 +478,14 @@ std::optional<Opm::DeckKeyword> RicScheduleDataGenerator::generateWelspecsForWel
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RicScheduleDataGenerator::generateCompletionsForWell( const RimWellEventTimeline& timeline,
|
||||
RimEclipseCase& eclipseCase,
|
||||
void RicScheduleDataGenerator::generateCompletionsForWell( RimEclipseCase& eclipseCase,
|
||||
RimWellPath& well,
|
||||
const QDateTime& date,
|
||||
const std::vector<RimWellEvent*>& eventsAtDate,
|
||||
std::map<QString, Opm::DeckKeyword>& keywordBlocks )
|
||||
{
|
||||
auto events = timeline.getEventsAtDate( date );
|
||||
|
||||
bool hasPerfEvents = false;
|
||||
for ( auto* event : events )
|
||||
for ( auto* event : eventsAtDate )
|
||||
{
|
||||
if ( event->eventType() == RimWellEvent::EventType::PERF && event->wellName() == well.name() )
|
||||
{
|
||||
@@ -511,10 +508,10 @@ void RicScheduleDataGenerator::generateCompletionsForWell( const RimWellEventTim
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline& timeline,
|
||||
RimEclipseCase& eclipseCase,
|
||||
void RicScheduleDataGenerator::generateMswForWell( RimEclipseCase& eclipseCase,
|
||||
RimWellPath& wellPath,
|
||||
const QDateTime& date,
|
||||
const std::vector<RimWellEvent*>& eventsAtDate,
|
||||
std::map<QString, Opm::DeckKeyword>& keywordBlocks,
|
||||
std::map<QString, std::vector<Opm::DeckKeyword>>& unmergedBlocks,
|
||||
const std::set<const RimWellPath*>& mswWells )
|
||||
@@ -532,10 +529,9 @@ void RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline&
|
||||
}
|
||||
|
||||
// MSW is generated only when there are valve/tubing/perforation events at this exact date.
|
||||
auto events = timeline.getEventsAtDate( date );
|
||||
bool hasMswEvents = false;
|
||||
bool hasPerfEvents = false;
|
||||
for ( auto* event : events )
|
||||
for ( auto* event : eventsAtDate )
|
||||
{
|
||||
if ( event->wellName() != wellPath.name() ) continue;
|
||||
|
||||
@@ -591,14 +587,11 @@ void RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline&
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RicScheduleDataGenerator::generateWellControlForWell( const RimWellEventTimeline& timeline,
|
||||
const RimWellPath& well,
|
||||
const QDateTime& date,
|
||||
void RicScheduleDataGenerator::generateWellControlForWell( const RimWellPath& well,
|
||||
const std::vector<RimWellEvent*>& eventsAtDate,
|
||||
std::map<QString, Opm::DeckKeyword>& keywordBlocks )
|
||||
{
|
||||
auto events = timeline.getEventsAtDate( date );
|
||||
|
||||
for ( auto* event : events )
|
||||
for ( auto* event : eventsAtDate )
|
||||
{
|
||||
if ( event->wellName() != well.name() ) continue;
|
||||
|
||||
|
||||
@@ -76,33 +76,35 @@ private:
|
||||
bool dateAsComment = false,
|
||||
bool alignColumns = false );
|
||||
|
||||
static std::optional<Opm::DeckKeyword>
|
||||
generateWelspecsForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date );
|
||||
static std::optional<Opm::DeckKeyword> generateWelspecsForWell( const RimWellEventTimeline& timeline,
|
||||
RimEclipseCase& eclipseCase,
|
||||
RimWellPath& well,
|
||||
const QDateTime& date,
|
||||
const std::vector<RimWellEvent*>& eventsAtDate );
|
||||
|
||||
// Generate COMPDAT (and COMPLUMP, when perforations carry a completion number) for a well at a
|
||||
// specific date based on events, merging both into the accumulator.
|
||||
static void generateCompletionsForWell( const RimWellEventTimeline& timeline,
|
||||
RimEclipseCase& eclipseCase,
|
||||
static void generateCompletionsForWell( RimEclipseCase& eclipseCase,
|
||||
RimWellPath& well,
|
||||
const QDateTime& date,
|
||||
const std::vector<RimWellEvent*>& eventsAtDate,
|
||||
std::map<QString, Opm::DeckKeyword>& keywordBlocks );
|
||||
|
||||
// Generate WELSEGS / COMPSEGS / WSEGVALV / WSEGAICD for a well at a specific date.
|
||||
// WSEGVALV / WSEGAICD are merged into keywordBlocks; WELSEGS / COMPSEGS cannot be merged across
|
||||
// wells and are appended as separate per-well blocks in unmergedBlocks.
|
||||
// All four keywords are emitted only when the well is present in mswWells; otherwise none are.
|
||||
static void generateMswForWell( const RimWellEventTimeline& timeline,
|
||||
RimEclipseCase& eclipseCase,
|
||||
static void generateMswForWell( RimEclipseCase& eclipseCase,
|
||||
RimWellPath& well,
|
||||
const QDateTime& date,
|
||||
const std::vector<RimWellEvent*>& eventsAtDate,
|
||||
std::map<QString, Opm::DeckKeyword>& keywordBlocks,
|
||||
std::map<QString, std::vector<Opm::DeckKeyword>>& unmergedBlocks,
|
||||
const std::set<const RimWellPath*>& mswWells );
|
||||
|
||||
// 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,
|
||||
static void generateWellControlForWell( const RimWellPath& well,
|
||||
const std::vector<RimWellEvent*>& eventsAtDate,
|
||||
std::map<QString, Opm::DeckKeyword>& keywordBlocks );
|
||||
|
||||
// Append records of `kw` into the entry for `name`, creating it from `kw` if absent
|
||||
|
||||
@@ -46,6 +46,16 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
// Constructing an Opm::Parser builds the complete keyword database (several thousand keywords) and
|
||||
// costs milliseconds. The parser is stateless once constructed, so a single shared instance is used
|
||||
// for all keyword lookups. Building one per keyword made schedule generation scale linearly in the
|
||||
// number of events with a very large constant (see issue: GenerateSchedule does not scale).
|
||||
const Opm::Parser& sharedParser()
|
||||
{
|
||||
static const Opm::Parser parser;
|
||||
return parser;
|
||||
}
|
||||
|
||||
QString keywordToString( const Opm::DeckKeyword& kw )
|
||||
{
|
||||
if ( kw.name().empty() ) return {};
|
||||
@@ -63,8 +73,7 @@ bool RifEventKeywordFormatter::isRecordlessKeyword( const Opm::DeckKeyword& keyw
|
||||
{
|
||||
try
|
||||
{
|
||||
static const Opm::Parser parser;
|
||||
const auto& parserKeyword = parser.getKeyword( keyword.name() );
|
||||
const auto& parserKeyword = sharedParser().getKeyword( keyword.name() );
|
||||
return parserKeyword.begin() == parserKeyword.end();
|
||||
}
|
||||
catch ( const std::exception& )
|
||||
@@ -85,8 +94,7 @@ std::optional<Opm::DeckKeyword> RifEventKeywordFormatter::buildKeyword( const QS
|
||||
|
||||
try
|
||||
{
|
||||
Opm::Parser parser;
|
||||
const Opm::ParserKeyword& parserKw = parser.getKeyword( kwName );
|
||||
const Opm::ParserKeyword& parserKw = sharedParser().getKeyword( kwName );
|
||||
Opm::DeckKeyword kw( parserKw );
|
||||
|
||||
const size_t numRecords = static_cast<size_t>( std::distance( parserKw.begin(), parserKw.end() ) );
|
||||
|
||||
@@ -64,6 +64,18 @@
|
||||
///
|
||||
///
|
||||
//==================================================================================================
|
||||
namespace
|
||||
{
|
||||
// A single shared parser instance is used for all keyword schema lookups. Constructing an
|
||||
// Opm::Parser populates the complete keyword database and costs milliseconds, which is far too
|
||||
// expensive to repeat for every keyword being serialised.
|
||||
const Opm::Parser& sharedParser()
|
||||
{
|
||||
static const Opm::Parser parser;
|
||||
return parser;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace RimKeywordFactory
|
||||
{
|
||||
|
||||
@@ -932,8 +944,7 @@ QString deckKeywordToAlignedString( const Opm::DeckKeyword& keyword )
|
||||
std::vector<std::string> canonicalItemOrder;
|
||||
try
|
||||
{
|
||||
Opm::Parser parser;
|
||||
const Opm::ParserKeyword& parserKeyword = parser.getKeyword( keyword.name() );
|
||||
const Opm::ParserKeyword& parserKeyword = sharedParser().getKeyword( keyword.name() );
|
||||
if ( std::distance( parserKeyword.begin(), parserKeyword.end() ) == 1 )
|
||||
{
|
||||
const Opm::ParserRecord& parserRecord = parserKeyword.getRecord( 0 );
|
||||
@@ -1078,8 +1089,7 @@ QString deckKeywordToAlignedString( const Opm::DeckKeyword& keyword )
|
||||
bool slashTerminated = true;
|
||||
try
|
||||
{
|
||||
Opm::Parser parser;
|
||||
slashTerminated = !parser.getKeyword( keyword.name() ).hasFixedSize();
|
||||
slashTerminated = !sharedParser().getKeyword( keyword.name() ).hasFixedSize();
|
||||
}
|
||||
catch ( ... )
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user