#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.
This commit is contained in:
Kristian Bendiksen committed 2026-10-19 09:28:29 +02:00
1 parent 2f86d4c210
commit c56e921160
21 files changed
+326 -186

No files matched your search

@@ -83,7 +83,7 @@ std::vector<RigMswBranch> 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<std::pair<double, double>> 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<const RimPerforationInterval*> perforationIntervals =
@@ -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<RimWellSpecData> 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<RimWellEventWellSpec*>( 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<RimWellSpecData> wellSpecData;
if ( latestWellSpec )
{
wellSpecData = latestWellSpec->wellSpecData();
}
else if ( firstWellSpec )
{
wellSpecData = firstWellSpec->baselineData();
}
return wellSpecData;
}
} // namespace
//--------------------------------------------------------------------------------------------------
@@ -388,7 +419,7 @@ std::expected<QString, QString> 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<Opm::DeckKeyword> 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<RimWellEventWellSpec*>( 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<RimWellSpecData> 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<RimWellEvent*>& 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.
@@ -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<RimWellEvent*>& eventsAtDate,
@@ -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<ReferenceMDType>( static_cast<int>( legacyParameters->referenceMDType() ) );
m_refMD = legacyParameters->storedReferenceMD();
m_customValuesForLateral = legacyParameters->customValuesForLateral();
m_linerDiameter = legacyParameters->linerDiameter();
m_roughnessFactor = legacyParameters->roughnessFactor();
m_pressureDrop = static_cast<PressureDropType>(
static_cast<int>( static_cast<RimMswCompletionParameters::PressureDropType>( legacyParameters->pressureDrop() ) ) );
m_lengthAndDepth = static_cast<LengthAndDepthType>(
static_cast<int>( static_cast<RimMswCompletionParameters::LengthAndDepthType>( legacyParameters->lengthAndDepth() ) ) );
m_refMDType = static_cast<ReferenceMDType>( static_cast<int>( 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" );
@@ -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<ReferenceMDType>;
using PressureDropEnum = caf::AppEnum<PressureDropType>;
using LengthAndDepthEnum = caf::AppEnum<LengthAndDepthType>;
using CompsegsSegmentNumberEnum = caf::AppEnum<CompsegsSegmentNumberType>;
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<RimSegmentInterval*> m_segmentIntervals;
caf::PdmField<PressureDropEnum> m_pressureDrop;
caf::PdmField<LengthAndDepthEnum> m_lengthAndDepth;
caf::PdmField<bool> m_enforceMaxSegmentLength;
caf::PdmField<double> m_maxSegmentLength;
caf::PdmField<bool> m_singleSegmentBeforeFirstPerforation;
caf::PdmField<bool> m_singleSegmentAfterLastPerforation;
caf::PdmField<bool> m_enforceMaxSegmentLength;
caf::PdmField<double> m_maxSegmentLength;
caf::PdmField<bool> m_singleSegmentBeforeFirstPerforation;
caf::PdmField<bool> m_singleSegmentAfterLastPerforation;
caf::PdmField<CompsegsSegmentNumberEnum> m_compsegsSegmentNumber;
};
@@ -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<RimMswCompletionParameters::PressureDropType>( legacyParameters->pressureDrop() );
if ( legacyPressureDrop != RimMswCompletionParameters::PressureDropType::HYDROSTATIC_FRICTION )
{
m_pressureDrop = static_cast<PressureDropType>( static_cast<int>( legacyPressureDrop ) );
}
const auto legacyLengthAndDepth = static_cast<RimMswCompletionParameters::LengthAndDepthType>( legacyParameters->lengthAndDepth() );
if ( legacyLengthAndDepth != RimMswCompletionParameters::LengthAndDepthType::ABS )
{
m_lengthAndDepth = static_cast<LengthAndDepthType>( static_cast<int>( 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 );
@@ -65,6 +65,21 @@ public:
};
using HydrostaticDensityEnum = caf::AppEnum<HydrostaticDensity>;
enum class PressureDropType
{
HYDROSTATIC,
HYDROSTATIC_FRICTION,
HYDROSTATIC_FRICTION_ACCELERATION
};
using PressureDropEnum = caf::AppEnum<PressureDropType>;
enum class LengthAndDepthType
{
ABS,
INC
};
using LengthAndDepthEnum = caf::AppEnum<LengthAndDepthType>;
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<double> 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<HydrostaticDensityEnum> m_hydrostaticDensity;
caf::PdmField<int> m_fluidInPlaceRegion;
caf::PdmField<PressureDropEnum> m_pressureDrop;
caf::PdmField<LengthAndDepthEnum> m_lengthAndDepth;
caf::PdmChildField<RimMswCompletionParameters*> m_mswParameters;
// OBSOLETE fields - kept for backward compatibility when reading old files
@@ -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;
}
@@ -38,6 +38,7 @@ RimWellEventWellSpec::RimWellEventWellSpec()
"WellType",
caf::AppEnum<RimWellPathCompletionSettings::WellType>( 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::WellType>( 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();
}
@@ -27,10 +27,11 @@
struct RimWellSpecData
{
QString groupName;
bool allowCrossFlow = true;
std::optional<double> referenceDepth;
RimWellPathCompletionSettings::WellType wellType = RimWellPathCompletionSettings::OIL;
QString groupName;
bool allowCrossFlow = true;
std::optional<double> referenceDepth;
RimWellPathCompletionSettings::WellType wellType = RimWellPathCompletionSettings::OIL;
RimWellPathCompletionSettings::PressureDropType pressureDrop = RimWellPathCompletionSettings::PressureDropType::HYDROSTATIC_FRICTION;
};
//==================================================================================================
@@ -64,8 +65,10 @@ private:
caf::PdmField<bool> m_allowCrossFlow;
caf::PdmField<std::optional<double>> m_referenceDepth;
caf::PdmField<caf::AppEnum<RimWellPathCompletionSettings::WellType>> m_wellType;
caf::PdmField<RimWellPathCompletionSettings::PressureDropEnum> m_pressureDrop;
caf::PdmField<QString> m_baselineGroupName;
caf::PdmField<bool> m_baselineAllowCrossFlow;
caf::PdmField<std::optional<double>> m_baselineReferenceDepth;
caf::PdmField<caf::AppEnum<RimWellPathCompletionSettings::WellType>> m_baselineWellType;
caf::PdmField<RimWellPathCompletionSettings::PressureDropEnum> m_baselinePressureDrop;
};
@@ -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() &&
@@ -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<caf::PdmObjectHandle*, QString> 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;
}
@@ -177,6 +177,7 @@ private:
caf::PdmField<bool> m_allowCrossFlow;
caf::PdmField<std::optional<double>> m_referenceDepth;
caf::PdmField<caf::AppEnum<RimWellPathCompletionSettings::WellType>> m_wellType;
caf::PdmField<QString> m_pressureDrop;
};
//==================================================================================================
@@ -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<caf::PdmField<double>*>( 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() );
@@ -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}
@@ -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"
+21 -15
View File
@@ -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
@@ -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"
)
@@ -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
@@ -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
+18 -3
View File
@@ -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