mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 06:38:13 -05:00
#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:
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).
|
||||
|
||||
@@ -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 ""
|
||||
Reference in new issue
Block a user