diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp index 501750c816..13650e0d92 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp @@ -44,7 +44,9 @@ QString RicScheduleDataGenerator::generateSchedule( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, const std::vector& wellPaths, - const std::vector& dates ) + const std::vector& 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 RicScheduleDataGenerator::generateDateSection( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, const std::vector& wellPaths, - const QDateTime& date ) + const QDateTime& date, + bool includeWelsegs, + bool includeCompsegs ) { // Keyword priority order for output static const std::vector 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& keywordBlocks ) + std::map& 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 ) ); diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h index 66795029bb..e11207f8d1 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h @@ -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& wellPaths, - const std::vector& dates ); + const std::vector& dates, + bool includeWelsegs = true, + bool includeCompsegs = true ); // Collect all unique dates from all wells' timelines static std::vector collectAllDates( const RimWellEventTimeline& timeline, const std::vector& wellPaths ); @@ -57,7 +61,9 @@ private: static QString generateDateSection( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, const std::vector& wellPaths, - const QDateTime& date ); + const QDateTime& date, + bool includeWelsegs, + bool includeCompsegs ); static std::optional generateWelspecsForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date ); @@ -66,12 +72,15 @@ private: static std::optional 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& keywordBlocks ); + std::map& 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, diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.cpp index 15b34f533e..c19c8de100 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.cpp +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.cpp @@ -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 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(); diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.h b/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.h index 4db2cfa781..3e32193b17 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.h +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.h @@ -225,5 +225,7 @@ public: QString classKeywordReturnedType() const override; private: - caf::PdmField m_eclipseCaseId; + caf::PdmField m_eclipseCaseId; + caf::PdmField m_includeWelsegs; + caf::PdmField m_includeCompsegs; }; diff --git a/GrpcInterface/Python/rips/tests/test_well_events.py b/GrpcInterface/Python/rips/tests/test_well_events.py index dbc94dfae8..cc5f597575 100644 --- a/GrpcInterface/Python/rips/tests/test_well_events.py +++ b/GrpcInterface/Python/rips/tests/test_well_events.py @@ -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). diff --git a/GrpcInterface/Python/rips/well_events.py b/GrpcInterface/Python/rips/well_events.py index 470c7766ea..652138d0e9 100644 --- a/GrpcInterface/Python/rips/well_events.py +++ b/GrpcInterface/Python/rips/well_events.py @@ -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 ""