#14064 Well Event: Make WELSEGS and COMPSEGS optional in schedule export

Add include_welsegs and include_compsegs flags (default True, preserving
current behaviour) to the schedule-generation API. Plumbed through
RimcWellEventTimeline_generateSchedule -> RicScheduleDataGenerator ->
generateMswForWell, gating only the WELSEGS / COMPSEGS merge calls.
WSEGVALV and WSEGAICD are unaffected.

Python: generate_schedule_text gains include_welsegs / include_compsegs
keyword arguments that forward to generate_schedule. Callers wanting
control-only / well-header-only schedule updates can now suppress the
verbose MSW table blocks.
This commit is contained in:
Kristian Bendiksen committed 2026-05-28 08:21:17 +02:00
1 parent 6ef024cee9
commit 80417b9947
6 files changed
+120 -20

No files matched your search

@@ -44,7 +44,9 @@
QString RicScheduleDataGenerator::generateSchedule( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const std::vector<QDateTime>& dates )
const std::vector<QDateTime>& dates,
bool includeWelsegs,
bool includeCompsegs )
{
QString result;
@@ -56,7 +58,7 @@ QString RicScheduleDataGenerator::generateSchedule( const RimWellEventTimeline&
// Generate section for each date
for ( const auto& date : dates )
{
QString dateSection = generateDateSection( timeline, eclipseCase, wellPaths, date );
QString dateSection = generateDateSection( timeline, eclipseCase, wellPaths, date, includeWelsegs, includeCompsegs );
if ( !dateSection.isEmpty() )
{
result += dateSection;
@@ -115,7 +117,9 @@ 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 QDateTime& date,
bool includeWelsegs,
bool includeCompsegs )
{
// Keyword priority order for output
static const std::vector<QString> keywordOrder = { "WELSPECS",
@@ -158,7 +162,7 @@ QString RicScheduleDataGenerator::generateDateSection( const RimWellEventTimelin
mergeKeyword( keywordBlocks, "COMPDAT", std::move( *compdat ) );
}
generateMswForWell( timeline, eclipseCase, *well, date, keywordBlocks );
generateMswForWell( timeline, eclipseCase, *well, date, keywordBlocks, includeWelsegs, includeCompsegs );
generateWellControlForWell( timeline, *well, date, keywordBlocks );
}
@@ -271,7 +275,9 @@ void RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline&
RimEclipseCase& eclipseCase,
RimWellPath& wellPath,
const QDateTime& date,
std::map<QString, Opm::DeckKeyword>& keywordBlocks )
std::map<QString, Opm::DeckKeyword>& keywordBlocks,
bool includeWelsegs,
bool includeCompsegs )
{
auto* mswParams = wellPath.mswCompletionParameters();
if ( !mswParams )
@@ -313,13 +319,19 @@ void RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline&
const auto& mswData = mswDataResult.value();
int maxSegments = 0;
int maxBranches = 0;
auto welsegsKw = RimKeywordFactory::welsegsKeyword( mswData, maxSegments, maxBranches );
mergeKeyword( keywordBlocks, "WELSEGS", std::move( welsegsKw ) );
if ( includeWelsegs )
{
int maxSegments = 0;
int maxBranches = 0;
auto welsegsKw = RimKeywordFactory::welsegsKeyword( mswData, maxSegments, maxBranches );
mergeKeyword( keywordBlocks, "WELSEGS", std::move( welsegsKw ) );
}
auto compsegsKw = RimKeywordFactory::compsegsKeyword( mswData );
mergeKeyword( keywordBlocks, "COMPSEGS", std::move( compsegsKw ) );
if ( includeCompsegs )
{
auto compsegsKw = RimKeywordFactory::compsegsKeyword( mswData );
mergeKeyword( keywordBlocks, "COMPSEGS", std::move( compsegsKw ) );
}
auto wsegvalvKw = RimKeywordFactory::wsegvalvKeyword( mswData );
mergeKeyword( keywordBlocks, "WSEGVALV", std::move( wsegvalvKw ) );
@@ -43,11 +43,15 @@ class DeckKeyword;
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 );
@@ -57,7 +61,9 @@ private:
static QString generateDateSection( const RimWellEventTimeline& timeline,
RimEclipseCase& eclipseCase,
const std::vector<RimWellPath*>& wellPaths,
const QDateTime& date );
const QDateTime& date,
bool includeWelsegs,
bool includeCompsegs );
static std::optional<Opm::DeckKeyword>
generateWelspecsForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date );
@@ -66,12 +72,15 @@ private:
static std::optional<Opm::DeckKeyword>
generateCompdatForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date );
// Generate WELSEGS / COMPSEGS / WSEGVALV / WSEGAICD for a well at a specific date, merging into the accumulator
// 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 );
std::map<QString, Opm::DeckKeyword>& keywordBlocks,
bool includeWelsegs,
bool includeCompsegs );
// Generate well control / well keyword event keywords for a well at a specific date, merging into the accumulator
static void generateWellControlForWell( const RimWellEventTimeline& timeline,
@@ -570,6 +570,8 @@ RimcWellEventTimeline_generateSchedule::RimcWellEventTimeline_generateSchedule(
CAF_PDM_InitObject( "Generate Schedule", "", "", "Generate Eclipse schedule text for all wells in the collection" );
CAF_PDM_InitScriptableField( &m_eclipseCaseId, "EclipseCaseId", -1, "", "", "", "Eclipse Case ID" );
CAF_PDM_InitScriptableField( &m_includeWelsegs, "IncludeWelsegs", true, "", "", "", "Include WELSEGS keyword in the exported schedule" );
CAF_PDM_InitScriptableField( &m_includeCompsegs, "IncludeCompsegs", true, "", "", "", "Include COMPSEGS keyword in the exported schedule" );
}
//--------------------------------------------------------------------------------------------------
@@ -633,7 +635,8 @@ std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_generateSche
return std::unexpected( QString( "No well paths with events found" ) );
}
QString scheduleText = RicScheduleDataGenerator::generateSchedule( *timeline, *eclipseCase, wellPathsWithEvents, dates );
QString scheduleText =
RicScheduleDataGenerator::generateSchedule( *timeline, *eclipseCase, wellPathsWithEvents, dates, m_includeWelsegs(), m_includeCompsegs() );
// Return the schedule text in a data container
auto* dataObject = new RimcDataContainerString();
@@ -225,5 +225,7 @@ public:
QString classKeywordReturnedType() const override;
private:
caf::PdmField<int> m_eclipseCaseId;
caf::PdmField<int> m_eclipseCaseId;
caf::PdmField<bool> m_includeWelsegs;
caf::PdmField<bool> m_includeCompsegs;
};
@@ -1231,6 +1231,65 @@ class TestScheduleGeneration:
assert jan_pos < feb_pos, "January should come before February"
assert feb_pos < jun_pos, "February should come before June"
def test_welsegs_compsegs_optional(self, project_with_case_and_well):
"""Regression for #14064: callers can suppress WELSEGS and/or COMPSEGS
without affecting other keywords (WSEGVALV/WSEGAICD/COMPDAT/WELSPECS).
"""
project, case, timeline = project_with_case_and_well
well_paths = project.well_paths()
assert len(well_paths) >= 1
wp = well_paths[0]
timeline.add_tubing_event(
event_date="2024-01-01",
well_path=wp,
start_md=0.0,
end_md=2500.0,
inner_diameter=0.15,
roughness=1.0e-5,
)
timeline.add_perf_event(
event_date="2024-01-01",
well_path=wp,
start_md=2000.0,
end_md=2200.0,
diameter=0.1,
state="OPEN",
)
timeline.set_timestamp(timestamp="2024-12-31")
# Baseline (defaults) must still emit WELSEGS and COMPSEGS.
baseline = timeline.generate_schedule_text(eclipse_case=case)
assert "WELSEGS" in baseline
assert "COMPSEGS" in baseline
# Suppress both.
suppressed = timeline.generate_schedule_text(
eclipse_case=case, include_welsegs=False, include_compsegs=False
)
print(f"\nSchedule with WELSEGS/COMPSEGS suppressed:\n{suppressed}")
assert "WELSEGS" not in suppressed, (
f"WELSEGS should be suppressed:\n{suppressed}"
)
assert "COMPSEGS" not in suppressed, (
f"COMPSEGS should be suppressed:\n{suppressed}"
)
# COMPDAT (perforation completions) is unrelated to the MSW flags.
assert "COMPDAT" in suppressed
# Independent toggling.
only_compsegs_off = timeline.generate_schedule_text(
eclipse_case=case, include_compsegs=False
)
assert "WELSEGS" in only_compsegs_off
assert "COMPSEGS" not in only_compsegs_off
only_welsegs_off = timeline.generate_schedule_text(
eclipse_case=case, include_welsegs=False
)
assert "WELSEGS" not in only_welsegs_off
assert "COMPSEGS" in only_welsegs_off
def test_keywords_grouped_across_wells(self, project_with_case_and_well):
"""Regression for #14063: WELSPECS / COMPDAT records for multiple wells on
the same date must appear under a single keyword header (not one block per well).
+17 -2
View File
@@ -281,7 +281,12 @@ def add_keyword_event(
@add_method(WellEventTimeline)
def generate_schedule_text(self: WellEventTimeline, eclipse_case: Case) -> str:
def generate_schedule_text(
self: WellEventTimeline,
eclipse_case: Case,
include_welsegs: bool = True,
include_compsegs: bool = True,
) -> str:
"""Generate Eclipse schedule text for all wells in the collection.
The timeline is shared across all wells in the well path collection.
@@ -293,6 +298,12 @@ def generate_schedule_text(self: WellEventTimeline, eclipse_case: Case) -> str:
Arguments:
eclipse_case (Case): Eclipse case to use for schedule generation.
include_welsegs (bool): When False, omit the WELSEGS keyword from the
output. Other multi-segment-well keywords (COMPSEGS, WSEGVALV,
WSEGAICD) are not affected. Defaults to True.
include_compsegs (bool): When False, omit the COMPSEGS keyword from
the output. WELSEGS, WSEGVALV, WSEGAICD are not affected.
Defaults to True.
Returns:
str: Eclipse schedule text containing DATES, COMPDAT, WELSEGS, WCONPROD, etc.
@@ -330,7 +341,11 @@ def generate_schedule_text(self: WellEventTimeline, eclipse_case: Case) -> str:
print(schedule_text)
```
"""
container = self.generate_schedule(eclipse_case_id=eclipse_case.id)
container = self.generate_schedule(
eclipse_case_id=eclipse_case.id,
include_welsegs=include_welsegs,
include_compsegs=include_compsegs,
)
if container and container.values:
return "".join(container.values)
return ""