mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
#14873 Schedule export: valve events only emit WSEGVALV/WSEGAICD
VALVE events no longer trigger WELSPECS, COMPORD, WELSEGS and COMPSEGS. WELSEGS/COMPSEGS are only regenerated for tubing and perforation events. Fixes #14873
This commit is contained in:
1 parent
f0dc8c47f0
commit
164dee3f4b
3 files changed
+98
-17
No files matched your search
@@ -107,6 +107,10 @@ std::expected<QString, QString> RicScheduleDataGenerator::generateSchedule( cons
|
||||
}
|
||||
}
|
||||
|
||||
// Segment layout (WELSEGS + COMPSEGS) last written per export well name, used to detect when a
|
||||
// valve-only date changes the segment structure and the segments must be redefined.
|
||||
std::map<QString, QString> emittedSegmentLayouts;
|
||||
|
||||
// Generate section for each date. The first retained date may be emitted as a comment instead
|
||||
// of a DATES keyword when firstDateAsComment is set. A RESTART marker truncates earlier dates,
|
||||
// including dates that only carry an insert-date event.
|
||||
@@ -115,8 +119,14 @@ std::expected<QString, QString> RicScheduleDataGenerator::generateSchedule( cons
|
||||
{
|
||||
if ( restartDate.has_value() && date < *restartDate ) continue;
|
||||
|
||||
auto dateSection =
|
||||
generateDateSection( timeline, eclipseCase, sortedWellPaths, date, mswWells, isFirstDate && firstDateAsComment, alignColumns );
|
||||
auto dateSection = generateDateSection( timeline,
|
||||
eclipseCase,
|
||||
sortedWellPaths,
|
||||
date,
|
||||
mswWells,
|
||||
emittedSegmentLayouts,
|
||||
isFirstDate && firstDateAsComment,
|
||||
alignColumns );
|
||||
if ( !dateSection ) return std::unexpected( dateSection.error() );
|
||||
result += *dateSection;
|
||||
isFirstDate = false;
|
||||
@@ -223,6 +233,7 @@ std::expected<QString, QString> RicScheduleDataGenerator::generateDateSection( c
|
||||
const std::vector<RimWellPath*>& wellPaths,
|
||||
const QDateTime& date,
|
||||
const std::set<const RimWellPath*>& mswWells,
|
||||
std::map<QString, QString>& emittedSegmentLayouts,
|
||||
bool dateAsComment,
|
||||
bool alignColumns )
|
||||
{
|
||||
@@ -324,7 +335,7 @@ std::expected<QString, QString> RicScheduleDataGenerator::generateDateSection( c
|
||||
|
||||
generateCompletionsForWell( eclipseCase, *well, date, events, keywordBlocks );
|
||||
|
||||
generateMswForWell( eclipseCase, *well, date, events, keywordBlocks, unmergedBlocks, mswWells );
|
||||
generateMswForWell( eclipseCase, *well, date, events, keywordBlocks, unmergedBlocks, mswWells, emittedSegmentLayouts );
|
||||
generateWellControlForWell( *well, events, keywordBlocks );
|
||||
}
|
||||
|
||||
@@ -481,8 +492,8 @@ std::optional<Opm::DeckKeyword> RicScheduleDataGenerator::generateWelspecsForWel
|
||||
bool hasEvents = false;
|
||||
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 ) &&
|
||||
if ( ( event->eventType() == RimWellEvent::EventType::PERF || event->eventType() == RimWellEvent::EventType::TUBING ||
|
||||
event->eventType() == RimWellEvent::EventType::WELLSPEC ) &&
|
||||
event->wellName() == well.name() )
|
||||
{
|
||||
hasEvents = true;
|
||||
@@ -562,7 +573,8 @@ void RicScheduleDataGenerator::generateMswForWell( RimEclipseCase&
|
||||
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 )
|
||||
const std::set<const RimWellPath*>& mswWells,
|
||||
std::map<QString, QString>& emittedSegmentLayouts )
|
||||
{
|
||||
// MSW keywords are exported only for the wells the caller explicitly requested.
|
||||
if ( !mswWells.contains( &wellPath ) )
|
||||
@@ -577,23 +589,23 @@ void RicScheduleDataGenerator::generateMswForWell( RimEclipseCase&
|
||||
}
|
||||
|
||||
// MSW is generated only when there are valve/tubing/perforation events at this exact date.
|
||||
bool hasMswEvents = false;
|
||||
bool hasPerfEvents = false;
|
||||
bool hasValveEvents = false;
|
||||
bool hasGeometryEvents = false; // tubing or perforation changes require WELSEGS/COMPSEGS to be redefined
|
||||
for ( auto* event : eventsAtDate )
|
||||
{
|
||||
if ( event->wellName() != wellPath.name() ) continue;
|
||||
|
||||
if ( event->eventType() == RimWellEvent::EventType::VALVE || event->eventType() == RimWellEvent::EventType::TUBING )
|
||||
if ( event->eventType() == RimWellEvent::EventType::VALVE )
|
||||
{
|
||||
hasMswEvents = true;
|
||||
hasValveEvents = true;
|
||||
}
|
||||
else if ( event->eventType() == RimWellEvent::EventType::PERF )
|
||||
else if ( event->eventType() == RimWellEvent::EventType::TUBING || event->eventType() == RimWellEvent::EventType::PERF )
|
||||
{
|
||||
hasPerfEvents = true;
|
||||
hasGeometryEvents = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !hasMswEvents && !hasPerfEvents )
|
||||
if ( !hasValveEvents && !hasGeometryEvents )
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -620,10 +632,22 @@ void RicScheduleDataGenerator::generateMswForWell( RimEclipseCase&
|
||||
int maxSegments = 0;
|
||||
int maxBranches = 0;
|
||||
auto welsegsKw = RimKeywordFactory::welsegsKeyword( mswData, maxSegments, maxBranches );
|
||||
appendUnmerged( "WELSEGS", std::move( welsegsKw ) );
|
||||
auto compsegsKw = RimKeywordFactory::compsegsKeyword( mswData );
|
||||
|
||||
auto compsegsKw = RimKeywordFactory::compsegsKeyword( mswData );
|
||||
appendUnmerged( "COMPSEGS", std::move( compsegsKw ) );
|
||||
// Tubing and perforation changes always redefine the segments. A valve event usually only updates
|
||||
// WSEGVALV/WSEGAICD, but a valve becoming active adds valve segments and remaps connections, so the
|
||||
// segments are also redefined when the layout differs from the one last written for this well.
|
||||
const QString segmentLayout = RimKeywordFactory::deckKeywordToString( welsegsKw ) + RimKeywordFactory::deckKeywordToString( compsegsKw );
|
||||
const QString wellName = wellPath.completionSettings() ? wellPath.completionSettings()->wellNameForExport() : wellPath.name();
|
||||
auto layoutIt = emittedSegmentLayouts.find( wellName );
|
||||
const bool layoutChanged = layoutIt == emittedSegmentLayouts.end() || layoutIt->second != segmentLayout;
|
||||
|
||||
if ( hasGeometryEvents || layoutChanged )
|
||||
{
|
||||
emittedSegmentLayouts[wellName] = segmentLayout;
|
||||
appendUnmerged( "WELSEGS", std::move( welsegsKw ) );
|
||||
appendUnmerged( "COMPSEGS", std::move( compsegsKw ) );
|
||||
}
|
||||
|
||||
auto wsegvalvKw = RimKeywordFactory::wsegvalvKeyword( mswData );
|
||||
mergeKeyword( keywordBlocks, "WSEGVALV", std::move( wsegvalvKw ) );
|
||||
|
||||
@@ -84,6 +84,7 @@ private:
|
||||
const std::vector<RimWellPath*>& wellPaths,
|
||||
const QDateTime& date,
|
||||
const std::set<const RimWellPath*>& mswWells,
|
||||
std::map<QString, QString>& emittedSegmentLayouts,
|
||||
bool dateAsComment = false,
|
||||
bool alignColumns = false );
|
||||
|
||||
@@ -105,13 +106,16 @@ private:
|
||||
// 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.
|
||||
// WELSEGS / COMPSEGS are written for tubing/perforation events, and for valve events when the
|
||||
// segment layout differs from the one last written for the well (tracked in emittedSegmentLayouts).
|
||||
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 );
|
||||
const std::set<const RimWellPath*>& mswWells,
|
||||
std::map<QString, QString>& emittedSegmentLayouts );
|
||||
|
||||
// Generate well control / well keyword event keywords for a well at a specific date, merging into the accumulator
|
||||
static void generateWellControlForWell( const RimWellPath& well,
|
||||
|
||||
@@ -1342,6 +1342,59 @@ class TestScheduleGeneration:
|
||||
f"Schedule should contain well name ({well_path.name} or {well_name_no_spaces})"
|
||||
)
|
||||
|
||||
def test_valve_only_date_redefines_segments_when_layout_changes(
|
||||
self, project_with_case_and_well
|
||||
):
|
||||
"""A valve activated on a later date adds valve segments, so WELSEGS/COMPSEGS must be redefined (issue #14873)."""
|
||||
project, case, timeline = project_with_case_and_well
|
||||
well_path = project.well_paths()[0]
|
||||
|
||||
timeline.add_tubing_event(
|
||||
event_date="2024-01-01",
|
||||
well_path=well_path,
|
||||
start_md=1500.0,
|
||||
end_md=2300.0,
|
||||
inner_diameter=0.22,
|
||||
roughness=9.0e-5,
|
||||
)
|
||||
timeline.add_perf_event(
|
||||
event_date="2024-01-01",
|
||||
well_path=well_path,
|
||||
start_md=2000.0,
|
||||
end_md=2200.0,
|
||||
diameter=0.1,
|
||||
skin_factor=0.5,
|
||||
state="OPEN",
|
||||
)
|
||||
timeline.add_valve_event(
|
||||
event_date="2024-03-01",
|
||||
well_path=well_path,
|
||||
measured_depth=2100.0,
|
||||
valve_type="ICV",
|
||||
state="OPEN",
|
||||
flow_coefficient=0.7,
|
||||
area=0.0001,
|
||||
)
|
||||
|
||||
timeline.set_timestamp(timestamp="2024-12-31")
|
||||
schedule_text = timeline.generate_schedule_text(
|
||||
eclipse_case=case,
|
||||
export_msw_for_wells=[well_path],
|
||||
first_date_as_comment=False,
|
||||
)
|
||||
|
||||
sections = schedule_text.split("DATES\n")
|
||||
january = [s for s in sections if "1 'JAN' 2024" in s or "1 JAN 2024" in s]
|
||||
march = [s for s in sections if "1 'MAR' 2024" in s or "1 MAR 2024" in s]
|
||||
assert len(january) == 1 and len(march) == 1
|
||||
|
||||
assert "WELSEGS" in january[0]
|
||||
assert "WSEGVALV" in march[0]
|
||||
assert "WELSEGS" in march[0], "New valve segments must be defined"
|
||||
assert "COMPSEGS" in march[0], "Connections must be remapped to valve segments"
|
||||
assert "WELSPECS" not in march[0]
|
||||
assert "COMPDAT" not in march[0]
|
||||
|
||||
def test_schedule_contains_wsegvalv_keyword(self, project_with_case_and_well):
|
||||
"""Verify WSEGVALV keyword is generated for valve events."""
|
||||
project, case, timeline = project_with_case_and_well
|
||||
|
||||
Reference in new issue
Block a user