From c56e9211608f2a43a9e8dd31399659c0eba1b05f Mon Sep 17 00:00:00 2001 From: Kristian Bendiksen Date: Fri, 16 Oct 2026 14:47:35 +0200 Subject: [PATCH] #14958 Sim Events: Move PRESSURE_COMPONENTS to WELSPECS Move Pressure Drop and Length and Depth from the segment collection to the well path completion settings. PRESSURE_COMPONENTS is now a dated WELSPECS attribute used for the WELSEGS header at export, and is rejected on SEGMENT events. --- .../RicWellPathExportMswGeometryPath.cpp | 6 +- .../RicScheduleDataGenerator.cpp | 71 ++++++++++------- .../RicScheduleDataGenerator.h | 4 +- .../Completions/RimSegmentCollection.cpp | 77 ++---------------- .../Completions/RimSegmentCollection.h | 49 ++++-------- .../RimWellPathCompletionSettings.cpp | 78 +++++++++++++++++++ .../RimWellPathCompletionSettings.h | 24 ++++++ .../WellEvents/RimWellEventTimeline.cpp | 4 +- .../WellEvents/RimWellEventWellSpec.cpp | 9 ++- .../WellEvents/RimWellEventWellSpec.h | 11 ++- .../ProjectDataModel/WellPath/RimWellPath.cpp | 1 + .../RimcWellEventTimeline.cpp | 19 ++++- .../RimcWellEventTimeline.h | 1 + .../UnitTests/RimWellPathCompletions-Test.cpp | 7 +- .../well_event_schedule_simulator_events.py | 5 +- .../wells_and_fractures/modeled_well_path.py | 7 +- GrpcInterface/Python/rips/simulator_events.py | 36 +++++---- .../rips/tests/test_msw_completion_data.py | 4 +- .../rips/tests/test_simulator_events.py | 69 +++++++++++++--- .../rips/tests/test_wells_path_completions.py | 9 ++- docs/simulatorEvents.md | 21 ++++- 21 files changed, 326 insertions(+), 186 deletions(-) diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/MswExport/RicWellPathExportMswGeometryPath.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/MswExport/RicWellPathExportMswGeometryPath.cpp index b3457c1bb9..1cebcc0ddb 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/MswExport/RicWellPathExportMswGeometryPath.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/MswExport/RicWellPathExportMswGeometryPath.cpp @@ -83,7 +83,7 @@ std::vector buildLateralBranches( RimEclipseCase* auto mswParameters = wellPath->segmentCollection(); if ( !mswParameters ) return result; - const std::string infoType = mswParameters->lengthAndDepth().text().toStdString(); + const std::string infoType = wellPath->completionSettings()->lengthAndDepth().text().toStdString(); const std::vector> segmentIntervals = mswParameters->getSegmentIntervals(); const double tieInMD = wellPath->wellPathTieIn()->tieInMeasuredDepth(); const double tieInTVD = -wellPath->wellPathGeometry()->interpolatedPointAlongWellPath( tieInMD ).z(); @@ -313,7 +313,7 @@ RigMswWellExportData buildMswWellExportData( RimEclipseCase* const RiaDefines::EclipseUnitSystem unitSystem = eclipseCase->eclipseCaseData()->unitsType(); const RigMainGrid* mainGrid = eclipseCase->mainGrid(); - const std::string infoType = mswParameters->lengthAndDepth().text().toStdString(); + const std::string infoType = wellPath->completionSettings()->lengthAndDepth().text().toStdString(); auto cellIntersections = RicWellPathExportMswTableData::generateCellSegments( eclipseCase, wellPath ); double initialMD = RicWellPathExportMswTableData::computeIntitialMeasuredDepth( eclipseCase, wellPath, mswParameters, cellIntersections ); @@ -327,7 +327,7 @@ RigMswWellExportData buildMswWellExportData( RimEclipseCase* header.topLength = initialMD; header.topDepth = initialTVD; header.infoType = infoType; - header.pressureComponents = mswParameters->pressureDrop().text().toStdString(); + header.pressureComponents = wellPath->completionSettings()->pressureDrop().text().toStdString(); const bool includePerforations = ( completionType & CompletionType::PERFORATIONS ) == CompletionType::PERFORATIONS; const std::vector perforationIntervals = diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp index 8bb5a07fbe..8f68e5bf15 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp @@ -66,6 +66,37 @@ bool eventBelongsToWell( const RimWellEvent& event, const RimWellPath& well ) const RimWellPath* eventWell = event.wellPath(); return eventWell && exportNameForWell( *eventWell ) == exportNameForWell( well ); } + +// WELSPECS settings in effect for a well at a date: the latest WELSPECS event at or before the date, otherwise the +// baseline captured by the first event. Returns nullopt when the well has no WELSPECS events. +std::optional wellSpecDataAtDate( const RimWellEventTimeline& timeline, const RimWellPath& well, const QDateTime& date ) +{ + const RimWellEventWellSpec* firstWellSpec = nullptr; + const RimWellEventWellSpec* latestWellSpec = nullptr; + for ( auto* event : timeline.events() ) + { + if ( event->eventType() != RimWellEvent::EventType::WELLSPEC || !eventBelongsToWell( *event, well ) ) continue; + + auto* wellSpec = dynamic_cast( event ); + if ( !wellSpec ) continue; + if ( !firstWellSpec || wellSpec->eventDate() < firstWellSpec->eventDate() ) firstWellSpec = wellSpec; + if ( wellSpec->eventDate() <= date && ( !latestWellSpec || wellSpec->eventDate() > latestWellSpec->eventDate() ) ) + { + latestWellSpec = wellSpec; + } + } + + std::optional wellSpecData; + if ( latestWellSpec ) + { + wellSpecData = latestWellSpec->wellSpecData(); + } + else if ( firstWellSpec ) + { + wellSpecData = firstWellSpec->baselineData(); + } + return wellSpecData; +} } // namespace //-------------------------------------------------------------------------------------------------- @@ -388,7 +419,7 @@ std::expected RicScheduleDataGenerator::generateDateSection( c // MSW keywords describe the shared well tree, including all its laterals. if ( mswIt != group.end() ) { - generateMswForWell( eclipseCase, *( *mswIt )->topLevelWellPath(), date, events, keywordBlocks, unmergedBlocks, emittedSegmentLayouts ); + generateMswForWell( timeline, eclipseCase, *( *mswIt )->topLevelWellPath(), date, events, keywordBlocks, unmergedBlocks, emittedSegmentLayouts ); } for ( auto* well : group ) @@ -561,30 +592,7 @@ std::optional RicScheduleDataGenerator::generateWelspecsForWel if ( !hasEvents ) return std::nullopt; - const RimWellEventWellSpec* firstWellSpec = nullptr; - const RimWellEventWellSpec* latestWellSpec = nullptr; - for ( auto* event : timeline.events() ) - { - if ( event->eventType() != RimWellEvent::EventType::WELLSPEC || !eventBelongsToWell( *event, well ) ) continue; - - auto* wellSpec = dynamic_cast( event ); - if ( !wellSpec ) continue; - if ( !firstWellSpec || wellSpec->eventDate() < firstWellSpec->eventDate() ) firstWellSpec = wellSpec; - if ( wellSpec->eventDate() <= date && ( !latestWellSpec || wellSpec->eventDate() > latestWellSpec->eventDate() ) ) - { - latestWellSpec = wellSpec; - } - } - - std::optional wellSpecData; - if ( latestWellSpec ) - { - wellSpecData = latestWellSpec->wellSpecData(); - } - else if ( firstWellSpec ) - { - wellSpecData = firstWellSpec->baselineData(); - } + const auto wellSpecData = wellSpecDataAtDate( timeline, well, date ); std::string wellGroupName = well.completionSettings()->groupNameForExport().toStdString(); auto welspecsKw = RimKeywordFactory::welspecsKeyword( wellGroupName, &eclipseCase, &well, wellSpecData ? &wellSpecData.value() : nullptr ); @@ -639,7 +647,8 @@ void RicScheduleDataGenerator::generateCompletionsForWellGroup( RimEclipseCase& //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RicScheduleDataGenerator::generateMswForWell( RimEclipseCase& eclipseCase, +void RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline& timeline, + RimEclipseCase& eclipseCase, RimWellPath& wellPath, const QDateTime& date, const std::vector& eventsAtDate, @@ -684,7 +693,15 @@ void RicScheduleDataGenerator::generateMswForWell( RimEclipseCase& if ( !mswDataResult.has_value() ) return; - const auto& mswData = mswDataResult.value(); + auto& mswData = mswDataResult.value(); + + // The pressure drop components are a WELSPECS event setting, so they can change over time. + if ( const auto wellSpecData = wellSpecDataAtDate( timeline, wellPath, date ) ) + { + auto header = mswData.welsegsHeader(); + header.pressureComponents = RimWellPathCompletionSettings::PressureDropEnum( wellSpecData->pressureDrop ).text().toStdString(); + mswData.setWelsegsHeader( header ); + } // WELSEGS and COMPSEGS are not merged across wells (see generateDateSection): each well keeps // its own block, appended in well order. diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h index a4f3bc3800..3718c5edaf 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h @@ -111,7 +111,9 @@ private: // The caller decides which wells get MSW keywords (see mswWells in generateSchedule). // WELSEGS / COMPSEGS are written for segment/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, + // The WELSEGS pressure drop components come from the WELSPECS event in effect at the date. + static void generateMswForWell( const RimWellEventTimeline& timeline, + RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date, const std::vector& eventsAtDate, diff --git a/ApplicationLibCode/ProjectDataModel/Completions/RimSegmentCollection.cpp b/ApplicationLibCode/ProjectDataModel/Completions/RimSegmentCollection.cpp index c0e46ff5a1..e2cfd14306 100644 --- a/ApplicationLibCode/ProjectDataModel/Completions/RimSegmentCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Completions/RimSegmentCollection.cpp @@ -50,29 +50,6 @@ void RimSegmentCollection::ReferenceMDEnum::setUp() setDefault( RimSegmentCollection::ReferenceMDType::AUTO_REFERENCE_MD ); } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -template <> -void RimSegmentCollection::PressureDropEnum::setUp() -{ - addItem( RimSegmentCollection::PressureDropType::HYDROSTATIC, "H--", "Hydrostatic" ); - addItem( RimSegmentCollection::PressureDropType::HYDROSTATIC_FRICTION, "HF-", "Hydrostatic + Friction" ); - addItem( RimSegmentCollection::PressureDropType::HYDROSTATIC_FRICTION_ACCELERATION, "HFA", "Hydrostatic + Friction + Acceleration" ); - setDefault( RimSegmentCollection::PressureDropType::HYDROSTATIC_FRICTION ); -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -template <> -void RimSegmentCollection::LengthAndDepthEnum::setUp() -{ - addItem( RimSegmentCollection::LengthAndDepthType::INC, "INC", "Incremental" ); - addItem( RimSegmentCollection::LengthAndDepthType::ABS, "ABS", "Absolute" ); - setDefault( RimSegmentCollection::LengthAndDepthType::ABS ); -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -104,8 +81,6 @@ RimSegmentCollection::RimSegmentCollection() CAF_PDM_InitScriptableField( &m_roughnessFactor, "RoughnessFactor", defaultRoughnessFactor( unitSystem ), "Roughness Factor" ); CAF_PDM_InitScriptableFieldNoDefault( &m_segmentIntervals, "Intervals", "Segment Intervals" ); - CAF_PDM_InitScriptableFieldNoDefault( &m_pressureDrop, "PressureDrop", "Pressure Drop" ); - CAF_PDM_InitScriptableFieldNoDefault( &m_lengthAndDepth, "LengthAndDepth", "Length and Depth" ); CAF_PDM_InitScriptableField( &m_enforceMaxSegmentLength, "EnforceMaxSegmentLength", false, "Enforce Max Segment Length" ); CAF_PDM_InitScriptableField( &m_maxSegmentLength, "MaxSegmentLength", 200.0, "Max Segment Length" ); m_maxSegmentLength.uiCapability()->setUiHidden( true ); @@ -200,22 +175,6 @@ double RimSegmentCollection::roughnessFactor() const return m_roughnessFactor(); } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -RimSegmentCollection::PressureDropEnum RimSegmentCollection::pressureDrop() const -{ - return m_pressureDrop(); -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -RimSegmentCollection::LengthAndDepthEnum RimSegmentCollection::lengthAndDepth() const -{ - return m_lengthAndDepth(); -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -304,22 +263,6 @@ void RimSegmentCollection::setRoughnessFactor( double roughnessFactor ) m_roughnessFactor = roughnessFactor; } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RimSegmentCollection::setPressureDrop( PressureDropType pressureDropType ) -{ - m_pressureDrop = pressureDropType; -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RimSegmentCollection::setLengthAndDepth( LengthAndDepthType lengthAndDepthType ) -{ - m_lengthAndDepth = lengthAndDepthType; -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -501,8 +444,6 @@ void RimSegmentCollection::updateFromTopLevelWell( const RimSegmentCollection* t { m_refMDType = topLevelWellParameters->m_refMDType(); m_refMD = topLevelWellParameters->m_refMD(); - m_pressureDrop = topLevelWellParameters->m_pressureDrop(); - m_lengthAndDepth = topLevelWellParameters->m_lengthAndDepth(); m_enforceMaxSegmentLength = topLevelWellParameters->m_enforceMaxSegmentLength(); m_maxSegmentLength = topLevelWellParameters->m_maxSegmentLength(); m_compsegsSegmentNumber = topLevelWellParameters->m_compsegsSegmentNumber(); @@ -522,22 +463,16 @@ void RimSegmentCollection::importLegacyData( const RimMswCompletionParameters* l legacyParameters->linerDiameter() != defaultLinerDiameter( RiaDefines::EclipseUnitSystem::UNITS_METRIC ) || legacyParameters->roughnessFactor() != defaultRoughnessFactor( RiaDefines::EclipseUnitSystem::UNITS_METRIC ) || legacyParameters->diameterRoughnessMode() != RimMswCompletionParameters::DiameterRoughnessMode::UNIFORM || - legacyParameters->pressureDrop() != RimMswCompletionParameters::PressureDropType::HYDROSTATIC_FRICTION || - legacyParameters->lengthAndDepth() != RimMswCompletionParameters::LengthAndDepthType::ABS || legacyParameters->enforceMaxSegmentLength() || legacyParameters->storedMaxSegmentLength() != 200.0 || !legacyParameters->diameterRoughnessIntervals()->intervals().empty() || !legacyParameters->customSegmentIntervals()->intervals().empty(); if ( !hasLegacyData ) return; - m_refMDType = static_cast( static_cast( legacyParameters->referenceMDType() ) ); - m_refMD = legacyParameters->storedReferenceMD(); - m_customValuesForLateral = legacyParameters->customValuesForLateral(); - m_linerDiameter = legacyParameters->linerDiameter(); - m_roughnessFactor = legacyParameters->roughnessFactor(); - m_pressureDrop = static_cast( - static_cast( static_cast( legacyParameters->pressureDrop() ) ) ); - m_lengthAndDepth = static_cast( - static_cast( static_cast( legacyParameters->lengthAndDepth() ) ) ); + m_refMDType = static_cast( static_cast( legacyParameters->referenceMDType() ) ); + m_refMD = legacyParameters->storedReferenceMD(); + m_customValuesForLateral = legacyParameters->customValuesForLateral(); + m_linerDiameter = legacyParameters->linerDiameter(); + m_roughnessFactor = legacyParameters->roughnessFactor(); m_enforceMaxSegmentLength = legacyParameters->enforceMaxSegmentLength(); m_maxSegmentLength = legacyParameters->storedMaxSegmentLength(); @@ -636,8 +571,6 @@ void RimSegmentCollection::defineUiOrdering( QString uiConfigName, caf::PdmUiOrd if ( wellPath->isTopLevelWellPath() ) { - uiOrdering.add( &m_pressureDrop ); - uiOrdering.add( &m_lengthAndDepth ); uiOrdering.add( &m_compsegsSegmentNumber ); auto* segmentationGroup = uiOrdering.addNewGroup( "Segmentation" ); diff --git a/ApplicationLibCode/ProjectDataModel/Completions/RimSegmentCollection.h b/ApplicationLibCode/ProjectDataModel/Completions/RimSegmentCollection.h index b47d694f95..6fa1982d4c 100644 --- a/ApplicationLibCode/ProjectDataModel/Completions/RimSegmentCollection.h +++ b/ApplicationLibCode/ProjectDataModel/Completions/RimSegmentCollection.h @@ -47,19 +47,6 @@ public: MANUAL_REFERENCE_MD }; - enum class PressureDropType - { - HYDROSTATIC, - HYDROSTATIC_FRICTION, - HYDROSTATIC_FRICTION_ACCELERATION - }; - - enum class LengthAndDepthType - { - ABS, - INC - }; - enum class CompsegsSegmentNumberType { ASSIGNED_BY_SIMULATOR, @@ -67,31 +54,25 @@ public: }; using ReferenceMDEnum = caf::AppEnum; - using PressureDropEnum = caf::AppEnum; - using LengthAndDepthEnum = caf::AppEnum; using CompsegsSegmentNumberEnum = caf::AppEnum; RimSegmentCollection(); - ReferenceMDType referenceMDType() const; - double manualReferenceMD() const; - double linerDiameter( RiaDefines::EclipseUnitSystem unitSystem ) const; - double linerDiameter() const; - double roughnessFactor() const; - double roughnessFactor( RiaDefines::EclipseUnitSystem unitSystem ) const; - PressureDropEnum pressureDrop() const; - LengthAndDepthEnum lengthAndDepth() const; - double maxSegmentLength() const; - bool singleSegmentBeforeFirstPerforation() const; - bool singleSegmentAfterLastPerforation() const; - bool exportCompsegsSegmentNumber() const; + ReferenceMDType referenceMDType() const; + double manualReferenceMD() const; + double linerDiameter( RiaDefines::EclipseUnitSystem unitSystem ) const; + double linerDiameter() const; + double roughnessFactor() const; + double roughnessFactor( RiaDefines::EclipseUnitSystem unitSystem ) const; + double maxSegmentLength() const; + bool singleSegmentBeforeFirstPerforation() const; + bool singleSegmentAfterLastPerforation() const; + bool exportCompsegsSegmentNumber() const; void setReferenceMDType( ReferenceMDType refType ); void setManualReferenceMD( double manualRefMD ); void setLinerDiameter( double diameter ); void setRoughnessFactor( double roughnessFactor ); - void setPressureDrop( PressureDropType pressureDropType ); - void setLengthAndDepth( LengthAndDepthType lengthAndDepthType ); void setCompsegsSegmentNumber( CompsegsSegmentNumberType segmentNumberType ); void setSingleSegmentBeforeFirstPerforation( bool enable ); void setSingleSegmentAfterLastPerforation( bool enable ); @@ -142,12 +123,10 @@ private: caf::PdmChildArrayField m_segmentIntervals; - caf::PdmField m_pressureDrop; - caf::PdmField m_lengthAndDepth; - caf::PdmField m_enforceMaxSegmentLength; - caf::PdmField m_maxSegmentLength; - caf::PdmField m_singleSegmentBeforeFirstPerforation; - caf::PdmField m_singleSegmentAfterLastPerforation; + caf::PdmField m_enforceMaxSegmentLength; + caf::PdmField m_maxSegmentLength; + caf::PdmField m_singleSegmentBeforeFirstPerforation; + caf::PdmField m_singleSegmentAfterLastPerforation; caf::PdmField m_compsegsSegmentNumber; }; diff --git a/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathCompletionSettings.cpp b/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathCompletionSettings.cpp index 926245a2d8..7f5be0cc12 100644 --- a/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathCompletionSettings.cpp +++ b/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathCompletionSettings.cpp @@ -74,6 +74,23 @@ void RimWellPathCompletionSettings::HydrostaticDensityEnum::setUp() setDefault( RimWellPathCompletionSettings::SEGMENTED ); } +template <> +void RimWellPathCompletionSettings::PressureDropEnum::setUp() +{ + addItem( RimWellPathCompletionSettings::PressureDropType::HYDROSTATIC, "H--", "Hydrostatic" ); + addItem( RimWellPathCompletionSettings::PressureDropType::HYDROSTATIC_FRICTION, "HF-", "Hydrostatic + Friction" ); + addItem( RimWellPathCompletionSettings::PressureDropType::HYDROSTATIC_FRICTION_ACCELERATION, "HFA", "Hydrostatic + Friction + Acceleration" ); + setDefault( RimWellPathCompletionSettings::PressureDropType::HYDROSTATIC_FRICTION ); +} + +template <> +void RimWellPathCompletionSettings::LengthAndDepthEnum::setUp() +{ + addItem( RimWellPathCompletionSettings::LengthAndDepthType::INC, "INC", "Incremental" ); + addItem( RimWellPathCompletionSettings::LengthAndDepthType::ABS, "ABS", "Absolute" ); + setDefault( RimWellPathCompletionSettings::LengthAndDepthType::ABS ); +} + } // namespace caf namespace @@ -120,6 +137,9 @@ RimWellPathCompletionSettings::RimWellPathCompletionSettings() CAF_PDM_InitScriptableFieldNoDefault( &m_hydrostaticDensity, "HydrostaticDensity", "Hydrostatic Density" ); CAF_PDM_InitScriptableField( &m_fluidInPlaceRegion, "FluidInPlaceRegion", 0, "Fluid In-Place Region" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_pressureDrop, "PressureDrop", "Pressure Drop" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_lengthAndDepth, "LengthAndDepth", "Length and Depth" ); + CAF_PDM_InitFieldNoDefault( &m_mswParameters, "MswParameters", "Multi Segment Well Parameters" ); m_mswParameters = new RimMswCompletionParameters; m_mswParameters.uiCapability()->setUiTreeChildrenHidden( true ); @@ -190,6 +210,8 @@ RimWellPathCompletionSettings& RimWellPathCompletionSettings::operator=( const R m_wellBoreFluidPVTTable = rhs.m_wellBoreFluidPVTTable; m_hydrostaticDensity = rhs.m_hydrostaticDensity; m_fluidInPlaceRegion = rhs.m_fluidInPlaceRegion; + m_pressureDrop = rhs.m_pressureDrop; + m_lengthAndDepth = rhs.m_lengthAndDepth; return *this; } @@ -427,6 +449,38 @@ int RimWellPathCompletionSettings::fluidInPlaceRegion() const return m_fluidInPlaceRegion(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellPathCompletionSettings::PressureDropEnum RimWellPathCompletionSettings::pressureDrop() const +{ + return m_pressureDrop(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellPathCompletionSettings::LengthAndDepthEnum RimWellPathCompletionSettings::lengthAndDepth() const +{ + return m_lengthAndDepth(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellPathCompletionSettings::setPressureDrop( PressureDropType pressureDropType ) +{ + m_pressureDrop = pressureDropType; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellPathCompletionSettings::setLengthAndDepth( LengthAndDepthType lengthAndDepthType ) +{ + m_lengthAndDepth = lengthAndDepthType; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -443,6 +497,26 @@ RimMswCompletionParameters* RimWellPathCompletionSettings::mswCompletionParamete return m_mswParameters(); } +//-------------------------------------------------------------------------------------------------- +/// Copy WELSEGS header settings from legacy MSW parameters, if they differ from the defaults +//-------------------------------------------------------------------------------------------------- +void RimWellPathCompletionSettings::importLegacyMswData( const RimMswCompletionParameters* legacyParameters ) +{ + if ( !legacyParameters ) return; + + const auto legacyPressureDrop = static_cast( legacyParameters->pressureDrop() ); + if ( legacyPressureDrop != RimMswCompletionParameters::PressureDropType::HYDROSTATIC_FRICTION ) + { + m_pressureDrop = static_cast( static_cast( legacyPressureDrop ) ); + } + + const auto legacyLengthAndDepth = static_cast( legacyParameters->lengthAndDepth() ); + if ( legacyLengthAndDepth != RimMswCompletionParameters::LengthAndDepthType::ABS ) + { + m_lengthAndDepth = static_cast( static_cast( legacyLengthAndDepth ) ); + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -464,6 +538,10 @@ void RimWellPathCompletionSettings::defineUiOrdering( QString uiConfigName, caf: compExportGroup->add( &m_wellBoreFluidPVTTable ); compExportGroup->add( &m_hydrostaticDensity ); compExportGroup->add( &m_fluidInPlaceRegion ); + + caf::PdmUiGroup* mswGroup = uiOrdering.addNewGroup( "Multi Segment Well (WELSEGS)" ); + mswGroup->add( &m_pressureDrop ); + mswGroup->add( &m_lengthAndDepth ); } uiOrdering.skipRemainingFields( true ); diff --git a/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathCompletionSettings.h b/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathCompletionSettings.h index 67b0cc125c..36cb8c4aba 100644 --- a/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathCompletionSettings.h +++ b/ApplicationLibCode/ProjectDataModel/Completions/RimWellPathCompletionSettings.h @@ -65,6 +65,21 @@ public: }; using HydrostaticDensityEnum = caf::AppEnum; + enum class PressureDropType + { + HYDROSTATIC, + HYDROSTATIC_FRICTION, + HYDROSTATIC_FRICTION_ACCELERATION + }; + using PressureDropEnum = caf::AppEnum; + + enum class LengthAndDepthType + { + ABS, + INC + }; + using LengthAndDepthEnum = caf::AppEnum; + public: RimWellPathCompletionSettings(); RimWellPathCompletionSettings( const RimWellPathCompletionSettings& rhs ); @@ -75,10 +90,15 @@ public: WellType wellType() const; bool allowWellCrossFlow() const; + PressureDropEnum pressureDrop() const; + LengthAndDepthEnum lengthAndDepth() const; + void setGroupName( const QString& name ); void setReferenceDepth( std::optional depth ); void setWellType( WellType wellType ); void setAllowWellCrossFlow( bool allowCrossFlow ); + void setPressureDrop( PressureDropType pressureDropType ); + void setLengthAndDepth( LengthAndDepthType lengthAndDepthType ); void setWellNameForExport( const QString& name ); void updateWellPathNameHasChanged( const QString& newWellPathName, const QString& previousWellPathName ); @@ -103,6 +123,7 @@ public: static QRegularExpression wellNameForExportRegExp(); RimMswCompletionParameters* mswCompletionParameters() const; + void importLegacyMswData( const RimMswCompletionParameters* legacyParameters ); protected: void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; @@ -127,6 +148,9 @@ private: caf::PdmField m_hydrostaticDensity; caf::PdmField m_fluidInPlaceRegion; + caf::PdmField m_pressureDrop; + caf::PdmField m_lengthAndDepth; + caf::PdmChildField m_mswParameters; // OBSOLETE fields - kept for backward compatibility when reading old files diff --git a/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventTimeline.cpp b/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventTimeline.cpp index 17b96c9fe6..e152b7d57f 100644 --- a/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventTimeline.cpp +++ b/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventTimeline.cpp @@ -295,7 +295,8 @@ RimWellEventWellSpec* RimWellEventTimeline::addWellSpecEvent( RimWellPath* wellP if ( wellPath && wellPath->completionSettings() ) { auto* settings = wellPath->completionSettings(); - event->setBaselineData( { settings->groupName(), settings->allowWellCrossFlow(), settings->referenceDepth(), settings->wellType() } ); + event->setBaselineData( + { settings->groupName(), settings->allowWellCrossFlow(), settings->referenceDepth(), settings->wellType(), settings->pressureDrop() } ); } m_events.push_back( event ); @@ -531,6 +532,7 @@ bool RimWellEventTimeline::applyWellSpecEvent( const RimWellEventWellSpec& event settings->setAllowWellCrossFlow( data.allowCrossFlow ); settings->setReferenceDepth( data.referenceDepth ); settings->setWellType( data.wellType ); + settings->setPressureDrop( data.pressureDrop ); settings->updateConnectedEditors(); return true; } diff --git a/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventWellSpec.cpp b/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventWellSpec.cpp index d8c2ed1f9a..4f34bca1d1 100644 --- a/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventWellSpec.cpp +++ b/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventWellSpec.cpp @@ -38,6 +38,7 @@ RimWellEventWellSpec::RimWellEventWellSpec() "WellType", caf::AppEnum( RimWellPathCompletionSettings::OIL ), "Preferred Fluid Phase" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_pressureDrop, "PressureDrop", "Pressure Drop" ); CAF_PDM_InitField( &m_baselineGroupName, "BaselineGroupName", QString(), "Baseline Group Name" ); CAF_PDM_InitField( &m_baselineAllowCrossFlow, "BaselineAllowCrossFlow", true, "Baseline Allow Cross-Flow" ); @@ -46,6 +47,7 @@ RimWellEventWellSpec::RimWellEventWellSpec() "BaselineWellType", caf::AppEnum( RimWellPathCompletionSettings::OIL ), "Baseline Preferred Fluid Phase" ); + CAF_PDM_InitFieldNoDefault( &m_baselinePressureDrop, "BaselinePressureDrop", "Baseline Pressure Drop" ); setDeletable( true ); } @@ -62,7 +64,7 @@ RimWellEventWellSpec::~RimWellEventWellSpec() //-------------------------------------------------------------------------------------------------- RimWellSpecData RimWellEventWellSpec::wellSpecData() const { - return { m_groupName(), m_allowCrossFlow(), m_referenceDepth(), m_wellType() }; + return { m_groupName(), m_allowCrossFlow(), m_referenceDepth(), m_wellType(), m_pressureDrop() }; } //-------------------------------------------------------------------------------------------------- @@ -70,7 +72,7 @@ RimWellSpecData RimWellEventWellSpec::wellSpecData() const //-------------------------------------------------------------------------------------------------- RimWellSpecData RimWellEventWellSpec::baselineData() const { - return { m_baselineGroupName(), m_baselineAllowCrossFlow(), m_baselineReferenceDepth(), m_baselineWellType() }; + return { m_baselineGroupName(), m_baselineAllowCrossFlow(), m_baselineReferenceDepth(), m_baselineWellType(), m_baselinePressureDrop() }; } //-------------------------------------------------------------------------------------------------- @@ -82,6 +84,7 @@ void RimWellEventWellSpec::setWellSpecData( const RimWellSpecData& data ) m_allowCrossFlow = data.allowCrossFlow; m_referenceDepth = data.referenceDepth; m_wellType = data.wellType; + m_pressureDrop = data.pressureDrop; } //-------------------------------------------------------------------------------------------------- @@ -93,6 +96,7 @@ void RimWellEventWellSpec::setBaselineData( const RimWellSpecData& data ) m_baselineAllowCrossFlow = data.allowCrossFlow; m_baselineReferenceDepth = data.referenceDepth; m_baselineWellType = data.wellType; + m_baselinePressureDrop = data.pressureDrop; } //-------------------------------------------------------------------------------------------------- @@ -121,6 +125,7 @@ void RimWellEventWellSpec::defineUiOrdering( QString uiConfigName, caf::PdmUiOrd uiOrdering.add( &m_allowCrossFlow ); uiOrdering.add( &m_referenceDepth ); uiOrdering.add( &m_wellType ); + uiOrdering.add( &m_pressureDrop ); uiOrdering.skipRemainingFields(); } diff --git a/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventWellSpec.h b/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventWellSpec.h index f84e99a10b..b64286587c 100644 --- a/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventWellSpec.h +++ b/ApplicationLibCode/ProjectDataModel/WellEvents/RimWellEventWellSpec.h @@ -27,10 +27,11 @@ struct RimWellSpecData { - QString groupName; - bool allowCrossFlow = true; - std::optional referenceDepth; - RimWellPathCompletionSettings::WellType wellType = RimWellPathCompletionSettings::OIL; + QString groupName; + bool allowCrossFlow = true; + std::optional referenceDepth; + RimWellPathCompletionSettings::WellType wellType = RimWellPathCompletionSettings::OIL; + RimWellPathCompletionSettings::PressureDropType pressureDrop = RimWellPathCompletionSettings::PressureDropType::HYDROSTATIC_FRICTION; }; //================================================================================================== @@ -64,8 +65,10 @@ private: caf::PdmField m_allowCrossFlow; caf::PdmField> m_referenceDepth; caf::PdmField> m_wellType; + caf::PdmField m_pressureDrop; caf::PdmField m_baselineGroupName; caf::PdmField m_baselineAllowCrossFlow; caf::PdmField> m_baselineReferenceDepth; caf::PdmField> m_baselineWellType; + caf::PdmField m_baselinePressureDrop; }; diff --git a/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPath.cpp b/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPath.cpp index 2d5dad971d..f20241f818 100644 --- a/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPath.cpp +++ b/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPath.cpp @@ -583,6 +583,7 @@ void RimWellPath::initAfterRead() } m_completions->segmentCollection()->importLegacyData( m_completionSettings->mswCompletionParameters() ); + m_completionSettings->importLegacyMswData( m_completionSettings->mswCompletionParameters() ); // Migrate an old direct file path reference into the project-level well formations collection. if ( RimProject::current()->isProjectFileVersionEqualOrOlderThan( "2026.09.2" ) && !m_wellFormationsFile() && diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.cpp index d8e36ff994..7ff3a1d8a0 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.cpp +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.cpp @@ -394,6 +394,13 @@ RimcWellEventTimeline_addWellSpecEvent::RimcWellEventTimeline_addWellSpecEvent( "", "", "Preferred Fluid Phase" ); + CAF_PDM_InitScriptableField( &m_pressureDrop, + "PressureDrop", + QString(), + "", + "", + "", + "WELSEGS pressure drop components (H--, HF- or HFA). Empty keeps the current completion setting" ); } //-------------------------------------------------------------------------------------------------- @@ -419,8 +426,18 @@ std::expected RimcWellEventTimeline_addWellSpecE } } + auto pressureDrop = m_wellPath()->completionSettings()->pressureDrop(); + if ( !m_pressureDrop().isEmpty() ) + { + if ( !RimWellPathCompletionSettings::PressureDropEnum::isValid( m_pressureDrop() ) ) + { + return std::unexpected( QString( "Invalid pressure drop: %1. Expected H--, HF- or HFA" ).arg( m_pressureDrop() ) ); + } + pressureDrop = RimWellPathCompletionSettings::PressureDropEnum::fromText( m_pressureDrop() ); + } + auto* event = timeline->addWellSpecEvent( m_wellPath(), date ); - event->setWellSpecData( { m_groupName(), m_allowCrossFlow(), m_referenceDepth(), m_wellType() } ); + event->setWellSpecData( { m_groupName(), m_allowCrossFlow(), m_referenceDepth(), m_wellType(), pressureDrop } ); return event; } diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.h b/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.h index 1c0df2eb5f..5fd9eeaab8 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.h +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.h @@ -177,6 +177,7 @@ private: caf::PdmField m_allowCrossFlow; caf::PdmField> m_referenceDepth; caf::PdmField> m_wellType; + caf::PdmField m_pressureDrop; }; //================================================================================================== diff --git a/ApplicationLibCode/UnitTests/RimWellPathCompletions-Test.cpp b/ApplicationLibCode/UnitTests/RimWellPathCompletions-Test.cpp index a2f1c53c9a..66068bb7c6 100644 --- a/ApplicationLibCode/UnitTests/RimWellPathCompletions-Test.cpp +++ b/ApplicationLibCode/UnitTests/RimWellPathCompletions-Test.cpp @@ -7,6 +7,7 @@ #include "RimSegmentCollection.h" #include "RimSegmentInterval.h" #include "RimWellPath.h" +#include "RimWellPathCompletionSettings.h" #include "RimWellPathCompletions.h" #include "cafPdmDefaultObjectFactory.h" @@ -178,6 +179,8 @@ TEST( RimWellPathCompletions, LegacyMigrationPreservesDisabledSettings ) RimSegmentCollection segments; segments.importLegacyData( &legacyParameters ); + RimWellPathCompletionSettings completionSettings; + completionSettings.importLegacyMswData( &legacyParameters ); EXPECT_EQ( RimSegmentCollection::ReferenceMDType::AUTO_REFERENCE_MD, segments.referenceMDType() ); auto* migratedMaxLength = dynamic_cast*>( segments.findField( "MaxSegmentLength" ) ); @@ -189,8 +192,8 @@ TEST( RimWellPathCompletions, LegacyMigrationPreservesDisabledSettings ) EXPECT_DOUBLE_EQ( 345.0, migratedMaxLength->value() ); EXPECT_TRUE( migratedCustomValues->value() ); EXPECT_FALSE( enforceMaxLength->value() ); - EXPECT_EQ( RimSegmentCollection::PressureDropType::HYDROSTATIC_FRICTION_ACCELERATION, segments.pressureDrop() ); - EXPECT_EQ( RimSegmentCollection::LengthAndDepthType::INC, segments.lengthAndDepth() ); + EXPECT_EQ( RimWellPathCompletionSettings::PressureDropType::HYDROSTATIC_FRICTION_ACCELERATION, completionSettings.pressureDrop() ); + EXPECT_EQ( RimWellPathCompletionSettings::LengthAndDepthType::INC, completionSettings.lengthAndDepth() ); segments.setReferenceMDType( RimSegmentCollection::ReferenceMDType::MANUAL_REFERENCE_MD ); EXPECT_DOUBLE_EQ( 123.0, segments.manualReferenceMD() ); diff --git a/GrpcInterface/Python/rips/PythonExamples/experimental/well_event_schedule_simulator_events.py b/GrpcInterface/Python/rips/PythonExamples/experimental/well_event_schedule_simulator_events.py index 02abf16062..1f1b52eb1c 100644 --- a/GrpcInterface/Python/rips/PythonExamples/experimental/well_event_schedule_simulator_events.py +++ b/GrpcInterface/Python/rips/PythonExamples/experimental/well_event_schedule_simulator_events.py @@ -63,11 +63,12 @@ WELL W1 = "{well_name}" WELL W1 # WELSPECS updates completion export settings and emits WELSPECS. Attributes # are optional: the second event inherits GROUP from the first event. - 2024-01-05 WELSPECS GROUP="SIMULATOR_EVENTS_GROUP" CROSSFLOW=True REFDEPTH=1002 PHASE=WATER + # PRESSURE_COMPONENTS sets the WELSEGS pressure drop components for the well. + 2024-01-05 WELSPECS GROUP="SIMULATOR_EVENTS_GROUP" CROSSFLOW=True REFDEPTH=1002 PHASE=WATER PRESSURE_COMPONENTS=HFA 2024-04-15 WELSPECS CROSSFLOW=False REFDEPTH=1000 PHASE=OIL # COMMENT is stored on the event and safely emitted as a schedule comment. - STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 PRESSURE_COMPONENTS=HFA COMMENT="Install production segment" + STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 COMMENT="Install production segment" # Perforations; COMPLETION_NUMBER groups connections for COMPLUMP. Same-date # perforations are kept as separate events during normalization.{filter_comment} diff --git a/GrpcInterface/Python/rips/PythonExamples/wells_and_fractures/modeled_well_path.py b/GrpcInterface/Python/rips/PythonExamples/wells_and_fractures/modeled_well_path.py index 35037f0143..4e53271315 100644 --- a/GrpcInterface/Python/rips/PythonExamples/wells_and_fractures/modeled_well_path.py +++ b/GrpcInterface/Python/rips/PythonExamples/wells_and_fractures/modeled_well_path.py @@ -84,12 +84,17 @@ msw_settings.custom_values_for_lateral = False msw_settings.enforce_max_segment_length = False msw_settings.liner_diameter = 0.152 msw_settings.max_segment_length = 200 -msw_settings.pressure_drop = "HF-" msw_settings.reference_md_type = "GridEntryPoint" msw_settings.roughness_factor = 1e-05 msw_settings.user_defined_reference_md = 0 msw_settings.update() +# The WELSEGS header settings are part of the completion settings +completion_settings = well_path.completion_settings() +completion_settings.pressure_drop = "HF-" +completion_settings.length_and_depth = "ABS" +completion_settings.update() + # Optionally update the Perforation Non-Darcy settings non_darcy_parameters = perforation_coll.non_darcy_parameters() non_darcy_parameters.non_darcy_flow_type = "UserDefined" diff --git a/GrpcInterface/Python/rips/simulator_events.py b/GrpcInterface/Python/rips/simulator_events.py index e880c0eba3..f19c45f9a9 100644 --- a/GrpcInterface/Python/rips/simulator_events.py +++ b/GrpcInterface/Python/rips/simulator_events.py @@ -151,8 +151,9 @@ Notes on the grammar: Eclipse well keyword (``WCONHIST``, ``WELTARG``, ``WRFTPLT``, ``WCONPROD``, ...), which is passed through generically with the well name injected as WELL. Event - ``WELSPECS`` accepts partial updates to ``GROUP``, ``CROSSFLOW``, ``REFDEPTH`` - and ``PHASE`` (OIL/GAS/WATER/LIQUID). Omitted values inherit the previous + ``WELSPECS`` accepts partial updates to ``GROUP``, ``CROSSFLOW``, ``REFDEPTH``, + ``PHASE`` (OIL/GAS/WATER/LIQUID) and ``PRESSURE_COMPONENTS`` (H--/HF-/HFA, + the WELSEGS pressure drop components). Omitted values inherit the previous WELSPECS state, initially using the well's completion export settings. There may be multiple WELSPECS events for a well, but not at the same timestamp. Each emits a WELSPECS record with the cumulative state. Event values are @@ -426,6 +427,7 @@ class WellSpecState: crossflow: bool refdepth: Optional[float] phase: str + pressure_components: str @dataclass @@ -1786,7 +1788,6 @@ _SEGMENT_KNOWN = { "MDEND", "INNER_DIAMETER", "ROUGHNESS", - "PRESSURE_COMPONENTS", "COMMENT", } _PRESSURE_COMPONENTS = {"H--", "HF-", "HFA"} @@ -1800,7 +1801,14 @@ _VALVE_KNOWN = {"MD", "TYPE", "STATE", "CV", "AREA", "COMMENT"} | { } _STATE_REQUIRED = ("STATE",) _STATE_KNOWN = {"STATE", "COMMENT"} -_WELLSPEC_KNOWN = {"GROUP", "CROSSFLOW", "REFDEPTH", "PHASE", "COMMENT"} +_WELLSPEC_KNOWN = { + "GROUP", + "CROSSFLOW", + "REFDEPTH", + "PHASE", + "PRESSURE_COMPONENTS", + "COMMENT", +} _WELLSPEC_PHASES = {"OIL", "GAS", "WATER", "LIQUID"} _COMPLETION_IGNORED = {"FILTER"} _PERF_IGNORED = _COMPLETION_IGNORED # backwards-compatible alias @@ -2007,6 +2015,7 @@ def _prepare_wellspec_events( crossflow=bool(completion_settings.allow_well_cross_flow), refdepth=completion_settings.reference_depth_for_export, phase=_enum_text(completion_settings.well_type_for_export), + pressure_components=_enum_text(completion_settings.pressure_drop), ) wellspecs = sorted( @@ -2059,6 +2068,13 @@ def _prepare_wellspec_events( errors.append("PHASE must be OIL, GAS, WATER, or LIQUID") else: next_state.phase = phase + if "PRESSURE_COMPONENTS" in attrs: + value = attrs["PRESSURE_COMPONENTS"].value + components = value.upper() if isinstance(value, str) else "" + if components not in _PRESSURE_COMPONENTS: + errors.append("PRESSURE_COMPONENTS must be H--, HF-, or HFA") + else: + next_state.pressure_components = components if errors: report.errors.extend(_event_message(event, error) for error in errors) @@ -2543,13 +2559,6 @@ def _apply_segment( ) if "ROUGHNESS" in attrs: kwargs["roughness"] = float(_as_number(attrs["ROUGHNESS"], event.loc)) - pressure_components = None - if "PRESSURE_COMPONENTS" in attrs: - pressure_components = str(attrs["PRESSURE_COMPONENTS"].value).upper() - if pressure_components not in _PRESSURE_COMPONENTS: - raise SimulatorEventsParseError( - "PRESSURE_COMPONENTS must be H--, HF-, or HFA", event.loc - ) except SimulatorEventsParseError as exc: _record_event_parse_error(event, exc, report) return @@ -2561,10 +2570,6 @@ def _apply_segment( diameter=kwargs.get("inner_diameter", 0.152), roughness_factor=kwargs.get("roughness", 1.0e-5), ) - if pressure_components is not None: - msw_settings = well_path.segment_collection() - msw_settings.pressure_drop = pressure_components - msw_settings.update() _apply_event_comment(event, timeline_event) report.events_applied += 1 @@ -2654,6 +2659,7 @@ def _apply_wellspec( allow_cross_flow=state.crossflow, reference_depth=state.refdepth, well_type=state.phase, + pressure_drop=state.pressure_components, ) _apply_event_comment(event, timeline_event) report.events_applied += 1 diff --git a/GrpcInterface/Python/rips/tests/test_msw_completion_data.py b/GrpcInterface/Python/rips/tests/test_msw_completion_data.py index 878d1d62fc..8d3f6fd17b 100644 --- a/GrpcInterface/Python/rips/tests/test_msw_completion_data.py +++ b/GrpcInterface/Python/rips/tests/test_msw_completion_data.py @@ -249,14 +249,12 @@ def test_msw_settings_read(rips_instance, initialize_test): assert msw_settings.liner_diameter == 0.152, "Liner diameter should be 0.152m" assert msw_settings.roughness_factor == 1e-05, "Roughness factor should be 1e-05" - # Check pressure drop setting - assert msw_settings.pressure_drop == "HF-", "Pressure drop should be HF-" - # Check max segment length setting assert msw_settings.max_segment_length == 200.0, "Max segment length should be 200m" # Verify completion settings completion_settings = well_y1.completion_settings() + assert completion_settings.pressure_drop == "HF-", "Pressure drop should be HF-" assert completion_settings.well_name_for_export == "Well-1", ( "Well name for export should be Well-1" ) diff --git a/GrpcInterface/Python/rips/tests/test_simulator_events.py b/GrpcInterface/Python/rips/tests/test_simulator_events.py index 841f5ad2d7..eb8c5f8b97 100644 --- a/GrpcInterface/Python/rips/tests/test_simulator_events.py +++ b/GrpcInterface/Python/rips/tests/test_simulator_events.py @@ -67,11 +67,11 @@ class FakeCompletionSettings: self.allow_well_cross_flow = True self.reference_depth_for_export = None self.well_type_for_export = "OIL" + self.pressure_drop = "HF-" class FakeSegmentCollection: def __init__(self): - self.pressure_drop = "HF-" self.segment_interval_calls = [] self.update_calls = 0 @@ -1880,17 +1880,38 @@ class TestApplying: "allow_cross_flow": False, "reference_depth": 1002.0, "well_type": "GAS", + "pressure_drop": "HF-", } assert timeline.wellspec_calls[1]["group_name"] == "my_group" assert timeline.wellspec_calls[1]["allow_cross_flow"] is True assert timeline.wellspec_calls[1]["well_type"] == "WATER" + def test_wellspec_pressure_components_are_cumulative(self): + text = ( + 'SIMEVENTS 1.2\nWELL "55_33-A-1"\n' + " 2018-01-01 WELSPECS PRESSURE_COMPONENTS=hfa\n" + " 2019-01-01 WELSPECS GROUP=G1\n" + " 2020-01-01 WELSPECS PRESSURE_COMPONENTS=H--\n" + ) + timeline, report = self._apply(text) + + assert report.errors == [] + assert [call["pressure_drop"] for call in timeline.wellspec_calls] == [ + "HFA", + "HFA", + "H--", + ] + @pytest.mark.parametrize( "attributes,expected_error", [ ("CROSSFLOW=YES", "CROSSFLOW must be True or False"), ("REFDEPTH=deep", "REFDEPTH must be numeric"), ("PHASE=steam", "PHASE must be OIL, GAS, WATER, or LIQUID"), + ( + "PRESSURE_COMPONENTS=INVALID", + "PRESSURE_COMPONENTS must be H--, HF-, or HFA", + ), ("GROUP=1", "GROUP must be a non-empty string"), ("UNKNOWN=1", "unknown WELSPECS attribute"), ("COMMENT=empty", "needs at least one setting attribute"), @@ -1905,11 +1926,11 @@ class TestApplying: assert expected_error in report.errors[0] assert timeline.wellspec_calls == [] - def test_segment_mapping_creates_custom_interval_and_sets_pressure_drop(self): + def test_segment_mapping_creates_custom_interval(self): text = ( 'SIMEVENTS 1.2\nWELL "55_33-A-1"\n' " 2024-01-01 SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 " - "ROUGHNESS=1.0e-5 PRESSURE_COMPONENTS=HFA\n" + "ROUGHNESS=1.0e-5\n" ) timeline, report = self._apply(text) assert report.events_applied == 1 @@ -1928,20 +1949,17 @@ class TestApplying: "roughness_factor": pytest.approx(1.0e-5), } ] - assert well.segment_collection().pressure_drop == "HFA" - assert well.segment_collection().update_calls == 1 - def test_segment_rejects_invalid_pressure_components(self): + def test_segment_rejects_pressure_components(self): text = ( 'SIMEVENTS 1.2\nWELL "55_33-A-1"\n' - " 2024-01-01 SEGMENT MDSTART=0 MDEND=2500 " - "PRESSURE_COMPONENTS=INVALID\n" + " 2024-01-01 SEGMENT MDSTART=0 MDEND=2500 PRESSURE_COMPONENTS=HFA\n" ) timeline, report = self._apply(text) assert report.events_applied == 0 assert report.events_skipped == 1 - assert "PRESSURE_COMPONENTS must be H--, HF-, or HFA" in report.errors[0] + assert "unknown SEGMENT attribute(s): PRESSURE_COMPONENTS" in report.errors[0] assert timeline.segment_calls == [] def test_tubing_is_reported_as_renamed_event(self): @@ -3009,7 +3027,8 @@ class TestSimulatorEventsIntegration: "DATE STARTUP = 2024-01-01\n" "DURATION RAMP = 31d\n" f'WELL "{well.name}"\n' - " STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 PRESSURE_COMPONENTS=HFA\n" + " STARTUP WELSPECS PRESSURE_COMPONENTS=HFA\n" + " STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5\n" " STARTUP + RAMP PERFORATION MDSTART=2000 MDEND=2200 DIAMETER=0.1 SKIN=0.5 COMPLETION_NUMBER=1\n" " 2024-05-15T14:45:30.700 PERFORATION MDSTART=2300 MDEND=2350 DIAMETER=0.1 SKIN=0.4 COMPLETION_NUMBER=2\n" " 2024-03-01 VALVE MD=2100 TYPE=ICV STATE=OPEN CV=0.7 AREA=0.0001\n" @@ -3026,16 +3045,16 @@ class TestSimulatorEventsIntegration: report = apply_simulator_events_document(document, timeline, project) assert report.errors == [] assert report.warnings == [] - assert report.events_applied == 11 + assert report.events_applied == 12 segment_intervals = well.descendants(rips.SegmentInterval) assert any( segment.start_md == 0.0 and segment.end_md == 2500.0 for segment in segment_intervals ) - assert well.segment_collection().pressure_drop == "HFA" timeline.set_timestamp(timestamp="2024-12-24") + assert well.completion_settings().pressure_drop == "HFA" schedule = timeline.generate_schedule_text( eclipse_case=case, export_msw_for_wells=[well] ) @@ -3052,6 +3071,32 @@ class TestSimulatorEventsIntegration: assert keyword in schedule, f"{keyword} missing from schedule" # The datetime perforation must surface as a DATES entry with TIME. assert "14:45:31" in schedule + assert "'HFA'" in schedule.split("WELSEGS", 1)[1].split("/", 1)[0] + + def test_welsegs_uses_pressure_components_from_wellspec_at_date( + self, project_with_case_and_wells + ): + project, case, timeline = project_with_case_and_wells + well = next(wp for wp in project.well_paths() if "A" in wp.name) + document = parse_simulator_events( + "SIMEVENTS 1.2\n" + f'WELL "{well.name}"\n' + " 2024-01-01 WELSPECS PRESSURE_COMPONENTS=HFA\n" + " 2024-01-01 SEGMENT MDSTART=0 MDEND=2500\n" + " 2024-01-01 PERFORATION MDSTART=2000 MDEND=2200\n" + " 2024-06-01 WELSPECS PRESSURE_COMPONENTS=H--\n" + " 2024-06-01 PERFORATION MDSTART=2300 MDEND=2350\n" + ) + report = apply_simulator_events_document(document, timeline, project) + assert report.errors == [] + + schedule = timeline.generate_schedule_text( + eclipse_case=case, export_msw_for_wells=[well] + ) + headers = [block.split("/", 1)[0] for block in schedule.split("WELSEGS\n")[1:]] + assert len(headers) == 2 + assert "'HFA'" in headers[0] + assert "'H--'" in headers[1] def test_apply_unknown_well_warns_and_applies_nothing( self, project_with_case_and_wells diff --git a/GrpcInterface/Python/rips/tests/test_wells_path_completions.py b/GrpcInterface/Python/rips/tests/test_wells_path_completions.py index d6128dca2c..f111ff2313 100644 --- a/GrpcInterface/Python/rips/tests/test_wells_path_completions.py +++ b/GrpcInterface/Python/rips/tests/test_wells_path_completions.py @@ -107,12 +107,18 @@ def test_add_well_path_completions(rips_instance, initialize_test): assert completions_settings_updated.well_name_for_export == "TestWellName" assert completions_settings_updated.well_type_for_export == "LIQUID" + completions_settings.pressure_drop = "HFA" + completions_settings.length_and_depth = "INC" + completions_settings.update() + completions_settings_updated = well_path.completion_settings() + assert completions_settings_updated.pressure_drop == "HFA" + assert completions_settings_updated.length_and_depth == "INC" + segment_collection = well_path.segment_collection() segment_collection.custom_values_for_lateral = True segment_collection.enforce_max_segment_length = True segment_collection.liner_diameter = 20.0 segment_collection.max_segment_length = 123.05 - segment_collection.pressure_drop = "HFA" segment_collection.reference_md_type = "UserDefined" segment_collection.roughness_factor = 1.3 segment_collection.user_defined_reference_md = 1234.56 @@ -123,7 +129,6 @@ def test_add_well_path_completions(rips_instance, initialize_test): assert segment_collection_updated.enforce_max_segment_length assert segment_collection_updated.liner_diameter == 20.0 assert segment_collection_updated.max_segment_length == 123.05 - assert segment_collection_updated.pressure_drop == "HFA" assert segment_collection_updated.reference_md_type == "UserDefined" assert segment_collection_updated.roughness_factor == 1.3 assert segment_collection_updated.user_defined_reference_md == 1234.56 diff --git a/docs/simulatorEvents.md b/docs/simulatorEvents.md index 2e7144eb6c..78136ac2b4 100644 --- a/docs/simulatorEvents.md +++ b/docs/simulatorEvents.md @@ -34,7 +34,8 @@ FILTER POROPERM = "PORO > 0.4 AND PERMX > 100.0" WELL A1 = "55_33-A-1" # alias declaration (has '=') WELL A1 # opens an event block via the alias - @STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 PRESSURE_COMPONENTS=HFA + @STARTUP WELSPECS GROUP="OP" PRESSURE_COMPONENTS=HFA + @STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 @STARTUP + RAMP PERFORATION MDSTART=2000 MDEND=2200 RADIUS=0.05 SKIN=0.5 COMPLETION_NUMBER=1 FILTER=POROPERM @2024-05-15T14:45:30 PERFORATION MDSTART=2300 MDEND=2350 RADIUS=0.05 @2024-03-01 VALVE MD=2100 TYPE=ICV STATE=OPEN CV=0.7 AREA=0.0001 @@ -79,8 +80,11 @@ event_line = "@" , date_expr , event_type , { attribute } ; event_type = completion_event_type | eclipse_keyword ; completion_event_type = "PERFORATION" | "SEGMENT" | "VALVE" | "STATE" | "WELSPECS" ; segment_attribute = "MDSTART" | "MDEND" | "INNER_DIAMETER" | "ROUGHNESS" - | "PRESSURE_COMPONENTS" | "COMMENT" ; + | "COMMENT" ; (* accepted attribute names when event_type is "SEGMENT" *) +wellspec_attribute = "GROUP" | "CROSSFLOW" | "REFDEPTH" | "PHASE" + | "PRESSURE_COMPONENTS" | "COMMENT" ; + (* accepted attribute names when event_type is "WELSPECS" *) date_expr = ( iso_datetime | date_ident ) , { sign , term } ; duration_expr = term , { sign , term } ; @@ -215,7 +219,6 @@ Each event adds the underlying segment timeline event and a corresponding **Segm | `MDEND` | yes | segment event and segment interval `end_md` | | `INNER_DIAMETER` | no | segment event `inner_diameter` [m] | | `ROUGHNESS` | no | segment event `roughness` [m] | -| `PRESSURE_COMPONENTS` | no | MSW `pressure_drop`: `H--`, `HF-` or `HFA` | **VALVE** → `add_valve_event` (requires an existing perforation at the MD) @@ -234,6 +237,18 @@ Each event adds the underlying segment timeline event and a corresponding **Segm |---|---|---| | `STATE` | yes | `well_state`: `OPEN`, `SHUT` or `STOP` | +**WELSPECS** → `add_wellspec_event` + +Well-level settings. At least one setting attribute is required; omitted values inherit the previous WELSPECS event for the well, initially the well's completion settings. Only one WELSPECS event per well and timestamp is allowed. `timeline.set_timestamp()` writes the values in effect back to the completion settings. + +| Attribute | Required | Maps to | +|---|---|---| +| `GROUP` | no | `group_name` | +| `CROSSFLOW` | no | `allow_cross_flow`: `True` or `False` | +| `REFDEPTH` | no | `reference_depth` [m] | +| `PHASE` | no | `well_type`: `OIL`, `GAS`, `WATER` or `LIQUID` | +| `PRESSURE_COMPONENTS` | no | `pressure_drop`: `H--`, `HF-` or `HFA`. Written to the WELSEGS header of the MSW keywords exported at or after the event date | + Unknown attributes on a completion event are an error and skip the event. On completion events other than `PERFORATION`, `FILTER` is ignored with a warning. ### Well keyword events