#14961 Sim Events: Support segment lengths in SEGMENT events

Add FIXED_SEGMENT_LENGTH, MIN_SEGMENT_LENGTH and MAX_SEGMENT_LENGTH to
SIMEVENTS SEGMENT events. The lengths are stored on RimWellEventSegment
(new AddSegmentEvent arguments, 0 = not set) and applied to the segment
interval when the timeline is applied. Invalid combinations (fixed with
min/max, min > max, non-positive values) are reported as parse errors.
This commit is contained in:
Kristian Bendiksen committed 2026-10-19 09:28:29 +02:00
1 parent c56e921160
commit 8174e21561
10 files changed
+401 -4

No files matched your search

@@ -20,6 +20,7 @@
#include "cafPdmFieldScriptingCapability.h"
#include "cafPdmObjectScriptingCapability.h"
#include "cafPdmUiCheckBoxAndTextEditor.h"
CAF_PDM_SOURCE_INIT( RimWellEventSegment, "WellEventSegment" );
@@ -35,6 +36,13 @@ RimWellEventSegment::RimWellEventSegment()
CAF_PDM_InitScriptableField( &m_innerDiameter, "InnerDiameter", 0.15, "Inner Diameter [m]" );
CAF_PDM_InitScriptableField( &m_roughness, "Roughness", 1.0e-5, "Roughness [m]" );
CAF_PDM_InitScriptableField( &m_fixedSegmentLength, "FixedSegmentLength", std::make_pair( false, 50.0 ), "Fixed Segment Length" );
m_fixedSegmentLength.uiCapability()->setUiEditorTypeName( caf::PdmUiCheckBoxAndTextEditor::uiEditorTypeName() );
CAF_PDM_InitScriptableField( &m_minSegmentLength, "MinSegmentLength", std::make_pair( false, 10.0 ), "Min Segment Length" );
m_minSegmentLength.uiCapability()->setUiEditorTypeName( caf::PdmUiCheckBoxAndTextEditor::uiEditorTypeName() );
CAF_PDM_InitScriptableField( &m_maxSegmentLength, "MaxSegmentLength", std::make_pair( false, 100.0 ), "Max Segment Length" );
m_maxSegmentLength.uiCapability()->setUiEditorTypeName( caf::PdmUiCheckBoxAndTextEditor::uiEditorTypeName() );
setDeletable( true );
}
@@ -109,6 +117,57 @@ void RimWellEventSegment::setRoughness( double roughness )
m_roughness = roughness;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<double> RimWellEventSegment::fixedSegmentLength() const
{
if ( m_fixedSegmentLength().first ) return m_fixedSegmentLength().second;
return std::nullopt;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<double> RimWellEventSegment::minSegmentLength() const
{
if ( m_minSegmentLength().first ) return m_minSegmentLength().second;
return std::nullopt;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<double> RimWellEventSegment::maxSegmentLength() const
{
if ( m_maxSegmentLength().first ) return m_maxSegmentLength().second;
return std::nullopt;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellEventSegment::setFixedSegmentLength( std::optional<double> length )
{
m_fixedSegmentLength = std::make_pair( length.has_value(), length.value_or( m_fixedSegmentLength().second ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellEventSegment::setMinSegmentLength( std::optional<double> length )
{
m_minSegmentLength = std::make_pair( length.has_value(), length.value_or( m_minSegmentLength().second ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellEventSegment::setMaxSegmentLength( std::optional<double> length )
{
m_maxSegmentLength = std::make_pair( length.has_value(), length.value_or( m_maxSegmentLength().second ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -131,6 +190,16 @@ QString RimWellEventSegment::generateScheduleKeyword( const QString& wellName )
.arg( m_innerDiameter(), 0, 'f', 4 )
.arg( m_roughness(), 0, 'e', 3 );
QStringList lengths;
if ( auto length = fixedSegmentLength() ) lengths.push_back( QString( "Fixed seg len: %1 m" ).arg( *length, 0, 'f', 2 ) );
if ( auto length = minSegmentLength() ) lengths.push_back( QString( "Min seg len: %1 m" ).arg( *length, 0, 'f', 2 ) );
if ( auto length = maxSegmentLength() ) lengths.push_back( QString( "Max seg len: %1 m" ).arg( *length, 0, 'f', 2 ) );
if ( !lengths.isEmpty() )
{
line.chop( 1 );
line += ", " + lengths.join( ", " ) + "\n";
}
return line;
}
@@ -145,6 +214,9 @@ void RimWellEventSegment::defineUiOrdering( QString uiConfigName, caf::PdmUiOrde
uiOrdering.add( &m_endMD );
uiOrdering.add( &m_innerDiameter );
uiOrdering.add( &m_roughness );
uiOrdering.add( &m_fixedSegmentLength );
uiOrdering.add( &m_minSegmentLength );
uiOrdering.add( &m_maxSegmentLength );
uiOrdering.skipRemainingFields();
}
@@ -22,6 +22,9 @@
#include "cafPdmField.h"
#include <optional>
#include <utility>
//==================================================================================================
///
/// Segment event - defines changes to segment (tubing/liner) diameter and roughness
@@ -47,6 +50,14 @@ public:
void setInnerDiameter( double diameter );
void setRoughness( double roughness );
// Segmentation. Fixed length cannot be combined with min/max length.
std::optional<double> fixedSegmentLength() const;
std::optional<double> minSegmentLength() const;
std::optional<double> maxSegmentLength() const;
void setFixedSegmentLength( std::optional<double> length );
void setMinSegmentLength( std::optional<double> length );
void setMaxSegmentLength( std::optional<double> length );
// Override from RimWellEvent
EventType eventType() const override;
QString generateScheduleKeyword( const QString& wellName ) const override;
@@ -60,4 +71,8 @@ private:
caf::PdmField<double> m_endMD;
caf::PdmField<double> m_innerDiameter;
caf::PdmField<double> m_roughness;
caf::PdmField<std::pair<bool, double>> m_fixedSegmentLength;
caf::PdmField<std::pair<bool, double>> m_minSegmentLength;
caf::PdmField<std::pair<bool, double>> m_maxSegmentLength;
};
@@ -569,6 +569,14 @@ bool RimWellEventTimeline::applySegmentEvent( const RimWellEventSegment& event,
interval->enableCustomStartDate( true );
interval->setCustomStartDate( event.eventDate().date() );
// The event defines the complete segmentation of the interval
interval->setFixedSegmentLength( std::nullopt );
interval->setMinSegmentLength( std::nullopt );
interval->setMaxSegmentLength( std::nullopt );
interval->setMinSegmentLength( event.minSegmentLength() );
interval->setMaxSegmentLength( event.maxSegmentLength() );
interval->setFixedSegmentLength( event.fixedSegmentLength() );
segmentCollection->updateConnectedEditors();
return true;
}
@@ -335,6 +335,9 @@ RimcWellEventTimeline_addSegmentEvent::RimcWellEventTimeline_addSegmentEvent( ca
CAF_PDM_InitScriptableField( &m_endMd, "EndMd", 0.0, "", "", "", "End Measured Depth" );
CAF_PDM_InitScriptableField( &m_innerDiameter, "InnerDiameter", 0.15, "", "", "", "Inner Diameter [m]" );
CAF_PDM_InitScriptableField( &m_roughness, "Roughness", 1.0e-5, "", "", "", "Roughness [m]" );
CAF_PDM_InitScriptableField( &m_fixedSegmentLength, "FixedSegmentLength", 0.0, "", "", "", "Fixed Segment Length [m] (0 = not set)" );
CAF_PDM_InitScriptableField( &m_minSegmentLength, "MinSegmentLength", 0.0, "", "", "", "Min Segment Length [m] (0 = not set)" );
CAF_PDM_InitScriptableField( &m_maxSegmentLength, "MaxSegmentLength", 0.0, "", "", "", "Max Segment Length [m] (0 = not set)" );
}
//--------------------------------------------------------------------------------------------------
@@ -355,11 +358,34 @@ std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addSegmentEv
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
if ( m_fixedSegmentLength() < 0.0 || m_minSegmentLength() < 0.0 || m_maxSegmentLength() < 0.0 )
{
return std::unexpected( QString( "Segment lengths must be positive (0 = not set)" ) );
}
auto toOptional = []( double length ) { return length > 0.0 ? std::optional<double>( length ) : std::nullopt; };
const auto fixedLength = toOptional( m_fixedSegmentLength() );
const auto minLength = toOptional( m_minSegmentLength() );
const auto maxLength = toOptional( m_maxSegmentLength() );
if ( fixedLength && ( minLength || maxLength ) )
{
return std::unexpected( QString( "Fixed segment length cannot be combined with min/max segment length" ) );
}
if ( minLength && maxLength && *minLength > *maxLength )
{
return std::unexpected( QString( "Min segment length cannot exceed max segment length" ) );
}
auto* event = timeline->addSegmentEvent( m_wellPath(), date );
event->setStartMD( m_startMd() );
event->setEndMD( m_endMd() );
event->setInnerDiameter( m_innerDiameter() );
event->setRoughness( m_roughness() );
event->setFixedSegmentLength( fixedLength );
event->setMinSegmentLength( minLength );
event->setMaxSegmentLength( maxLength );
return event;
}
@@ -155,6 +155,9 @@ private:
caf::PdmField<double> m_endMd;
caf::PdmField<double> m_innerDiameter;
caf::PdmField<double> m_roughness;
caf::PdmField<double> m_fixedSegmentLength;
caf::PdmField<double> m_minSegmentLength;
caf::PdmField<double> m_maxSegmentLength;
};
//==================================================================================================
@@ -68,7 +68,7 @@ WELL W1
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 COMMENT="Install production segment"
STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 MIN_SEGMENT_LENGTH=5 MAX_SEGMENT_LENGTH=50 COMMENT="Install production segment"
# Perforations; COMPLETION_NUMBER groups connections for COMPLUMP. Same-date
# perforations are kept as separate events during normalization.{filter_comment}
+87 -2
View File
@@ -744,6 +744,7 @@ def merge_simulator_events_documents(
result.wells, result.groups, result.schedule_events
)
errors.extend(_wellspec_validation_issues(result.wells))
errors.extend(_segment_validation_issues(result.wells))
if errors:
raise SimulatorEventsParseError(errors=errors)
return result
@@ -849,6 +850,7 @@ def parse_simulator_events(
_set_event_scopes(wells, groups, schedule_events)
errors.extend(_restart_validation_issues(wells, groups, schedule_events))
errors.extend(_wellspec_validation_issues(wells))
errors.extend(_segment_validation_issues(wells))
insert_date_events, report_errors = _expand_report_specs(
report_specs, wells, groups, schedule_events
)
@@ -1016,6 +1018,70 @@ def _wellspec_validation_issues(wells: List[WellBlock]) -> List[ParseIssue]:
return issues
def _segment_validation_issues(wells: List[WellBlock]) -> List[ParseIssue]:
"""Validate the segment-length attributes of SEGMENT events.
Lengths must be positive numbers, a fixed length cannot be combined with
min/max lengths, and the min length cannot exceed the max length.
"""
issues: List[ParseIssue] = []
for well in wells:
for event in well.events:
if event.event_type.upper() != "SEGMENT":
continue
lengths: Dict[str, float] = {}
for name in _SEGMENT_LENGTH_KWARGS:
attr = event.attributes.get(name)
if attr is None:
continue
value = attr.value
if isinstance(value, bool) or not isinstance(value, (int, float)):
issues.append(
ParseIssue(
f"{_event_context(event)}: {name} must be a number, "
f"got {attr.raw!r}",
event.loc,
)
)
elif value <= 0:
issues.append(
ParseIssue(
f"{_event_context(event)}: {name} must be greater "
f"than zero, got {attr.raw}",
event.loc,
)
)
else:
lengths[name] = float(value)
present = [
name for name in _SEGMENT_LENGTH_KWARGS if name in event.attributes
]
if "FIXED_SEGMENT_LENGTH" in present and len(present) > 1:
issues.append(
ParseIssue(
f"{_event_context(event)}: FIXED_SEGMENT_LENGTH cannot be "
"combined with MIN_SEGMENT_LENGTH or MAX_SEGMENT_LENGTH",
event.loc,
)
)
min_length = lengths.get("MIN_SEGMENT_LENGTH")
max_length = lengths.get("MAX_SEGMENT_LENGTH")
if (
min_length is not None
and max_length is not None
and min_length > max_length
):
issues.append(
ParseIssue(
f"{_event_context(event)}: MIN_SEGMENT_LENGTH ({min_length:g}) "
f"exceeds MAX_SEGMENT_LENGTH ({max_length:g})",
event.loc,
)
)
return issues
_SUPPORTED_VERSION = "1.2"
@@ -1788,8 +1854,17 @@ _SEGMENT_KNOWN = {
"MDEND",
"INNER_DIAMETER",
"ROUGHNESS",
"FIXED_SEGMENT_LENGTH",
"MIN_SEGMENT_LENGTH",
"MAX_SEGMENT_LENGTH",
"COMMENT",
}
# SEGMENT length attribute -> add_segment_event keyword-argument name.
_SEGMENT_LENGTH_KWARGS = {
"FIXED_SEGMENT_LENGTH": "fixed_segment_length",
"MIN_SEGMENT_LENGTH": "min_segment_length",
"MAX_SEGMENT_LENGTH": "max_segment_length",
}
_PRESSURE_COMPONENTS = {"H--", "HF-", "HFA"}
_VALVE_REQUIRED = ("MD", "TYPE")
_VALVE_KNOWN = {"MD", "TYPE", "STATE", "CV", "AREA", "COMMENT"} | {
@@ -2559,17 +2634,27 @@ def _apply_segment(
)
if "ROUGHNESS" in attrs:
kwargs["roughness"] = float(_as_number(attrs["ROUGHNESS"], event.loc))
lengths: Dict[str, float] = {}
for name, kwarg in _SEGMENT_LENGTH_KWARGS.items():
if name in attrs:
lengths[kwarg] = float(_as_number(attrs[name], event.loc))
except SimulatorEventsParseError as exc:
_record_event_parse_error(event, exc, report)
return
timeline_event = timeline.add_segment_event(**kwargs)
well_path.segment_collection().add_segment_interval(
timeline_event = timeline.add_segment_event(**kwargs, **lengths)
interval = well_path.segment_collection().add_segment_interval(
start_md=start_md,
end_md=end_md,
diameter=kwargs.get("inner_diameter", 0.152),
roughness_factor=kwargs.get("roughness", 1.0e-5),
)
if "min_segment_length" in lengths:
interval.set_min_segment_length(length=lengths["min_segment_length"])
if "max_segment_length" in lengths:
interval.set_max_segment_length(length=lengths["max_segment_length"])
if "fixed_segment_length" in lengths:
interval.set_fixed_segment_length(length=lengths["fixed_segment_length"])
_apply_event_comment(event, timeline_event)
report.events_applied += 1
@@ -70,13 +70,34 @@ class FakeCompletionSettings:
self.pressure_drop = "HF-"
class FakeSegmentInterval:
def __init__(self):
self.segment_length_calls = []
def set_fixed_segment_length(self, length):
self.segment_length_calls.append(("fixed", length))
return self
def set_min_segment_length(self, length):
self.segment_length_calls.append(("min", length))
return self
def set_max_segment_length(self, length):
self.segment_length_calls.append(("max", length))
return self
class FakeSegmentCollection:
def __init__(self):
self.segment_interval_calls = []
self.intervals = []
self.update_calls = 0
def add_segment_interval(self, **kwargs):
self.segment_interval_calls.append(kwargs)
interval = FakeSegmentInterval()
self.intervals.append(interval)
return interval
def update(self):
self.update_calls += 1
@@ -1445,6 +1466,64 @@ class TestDiagnostics:
with pytest.raises(SimulatorEventsParseError, match="did you mean 'DURATION'"):
parse_simulator_events("SIMEVENTS 1.2\nDURATON X = 5\n")
@pytest.mark.parametrize(
"lengths, expected",
[
(
"FIXED_SEGMENT_LENGTH=20 MIN_SEGMENT_LENGTH=5",
"FIXED_SEGMENT_LENGTH cannot be combined",
),
(
"FIXED_SEGMENT_LENGTH=20 MAX_SEGMENT_LENGTH=50",
"FIXED_SEGMENT_LENGTH cannot be combined",
),
(
"MIN_SEGMENT_LENGTH=60 MAX_SEGMENT_LENGTH=50",
"MIN_SEGMENT_LENGTH \\(60\\) exceeds MAX_SEGMENT_LENGTH \\(50\\)",
),
("MIN_SEGMENT_LENGTH=0", "MIN_SEGMENT_LENGTH must be greater than zero"),
("MAX_SEGMENT_LENGTH=-5", "MAX_SEGMENT_LENGTH must be greater than zero"),
("FIXED_SEGMENT_LENGTH=long", "FIXED_SEGMENT_LENGTH must be a number"),
],
)
def test_invalid_segment_lengths_are_parse_errors(self, lengths, expected):
text = (
'SIMEVENTS 1.2\nWELL "W"\n'
" 2024-01-01 PERFORATION MDSTART=1 MDEND=2\n"
f" 2024-01-01 SEGMENT MDSTART=0 MDEND=2500 {lengths}\n" # line 4
)
with pytest.raises(SimulatorEventsParseError, match=expected) as excinfo:
parse_simulator_events(text)
assert [issue.loc.line for issue in excinfo.value.errors] == [4]
def test_invalid_segment_lengths_are_rejected_when_merging(self):
first = parse_simulator_events(
'SIMEVENTS 1.2\nWELL "W"\n'
" 2024-01-01 SEGMENT MDSTART=0 MDEND=100 FIXED_SEGMENT_LENGTH=10\n"
)
first.wells[0].events[0].attributes.update(
parse_simulator_events(
'SIMEVENTS 1.2\nWELL "W"\n'
" 2024-01-01 SEGMENT MDSTART=0 MDEND=100 MIN_SEGMENT_LENGTH=5\n"
)
.wells[0]
.events[0]
.attributes
)
with pytest.raises(
SimulatorEventsParseError, match="FIXED_SEGMENT_LENGTH cannot be combined"
):
merge_simulator_events_documents([first])
def test_valid_segment_lengths_parse(self):
document = parse_simulator_events(
'SIMEVENTS 1.2\nWELL "W"\n'
" 2024-01-01 SEGMENT MDSTART=0 MDEND=100 MIN_SEGMENT_LENGTH=5 "
"MAX_SEGMENT_LENGTH=5\n"
" 2024-01-01 SEGMENT MDSTART=100 MDEND=200 FIXED_SEGMENT_LENGTH=12.5\n"
)
assert len(document.wells[0].events) == 2
class TestValidatorCli:
def _write(self, tmp_path, text):
@@ -1950,6 +2029,38 @@ class TestApplying:
}
]
def test_segment_min_max_lengths_are_mapped(self):
text = (
'SIMEVENTS 1.2\nWELL "55_33-A-1"\n'
" 2024-01-01 SEGMENT MDSTART=0 MDEND=2500 MIN_SEGMENT_LENGTH=5 "
"MAX_SEGMENT_LENGTH=50\n"
)
timeline, report = self._apply(text)
assert report.errors == []
assert report.events_applied == 1
call = timeline.segment_calls[0]
assert call["min_segment_length"] == 5.0
assert call["max_segment_length"] == 50.0
assert "fixed_segment_length" not in call
interval = call["well_path"].segment_collection().intervals[0]
assert interval.segment_length_calls == [("min", 5.0), ("max", 50.0)]
def test_segment_fixed_length_is_mapped(self):
text = (
'SIMEVENTS 1.2\nWELL "55_33-A-1"\n'
" 2024-01-01 SEGMENT MDSTART=0 MDEND=2500 FIXED_SEGMENT_LENGTH=12\n"
)
timeline, report = self._apply(text)
assert report.events_applied == 1
call = timeline.segment_calls[0]
assert call["fixed_segment_length"] == 12.0
assert "min_segment_length" not in call
assert "max_segment_length" not in call
interval = call["well_path"].segment_collection().intervals[0]
assert interval.segment_length_calls == [("fixed", 12.0)]
def test_segment_rejects_pressure_components(self):
text = (
'SIMEVENTS 1.2\nWELL "55_33-A-1"\n'
@@ -3016,6 +3127,44 @@ class TestSimulatorEventsIntegration:
"INSERT_DATE date should be emitted as a DATES entry"
)
def test_segment_lengths_are_applied_to_interval(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 SEGMENT MDSTART=0 MDEND=1000 MIN_SEGMENT_LENGTH=5 "
"MAX_SEGMENT_LENGTH=50\n"
" 2024-01-01 SEGMENT MDSTART=1000 MDEND=2500 FIXED_SEGMENT_LENGTH=25\n"
)
report = apply_simulator_events_document(document, timeline, project)
assert report.errors == []
assert report.events_applied == 2
events = timeline.descendants(rips.WellEventSegment)
min_max_event = next(e for e in events if e.end_md == 1000.0)
assert min_max_event.min_segment_length == (True, 5.0)
assert min_max_event.max_segment_length == (True, 50.0)
assert min_max_event.fixed_segment_length[0] is False
def check_intervals():
intervals = {
(i.start_md, i.end_md): i for i in well.segment_collection().intervals()
}
min_max = intervals[(0.0, 1000.0)]
assert min_max.min_segment_length == (True, 5.0)
assert min_max.max_segment_length == (True, 50.0)
assert min_max.fixed_segment_length[0] is False
fixed = intervals[(1000.0, 2500.0)]
assert fixed.fixed_segment_length == (True, 25.0)
assert fixed.min_segment_length[0] is False
assert fixed.max_segment_length[0] is False
check_intervals()
timeline.set_timestamp(timestamp="2024-12-24")
check_intervals()
def test_apply_full_event_coverage_and_schedule(self, project_with_case_and_wells):
"""All event kinds from well_event_schedule.py expressed as SIMEVENTS."""
project, case, timeline = project_with_case_and_wells
@@ -115,6 +115,39 @@ class TestWellEventTimeline:
assert event is not None, "Segment event should be created"
def test_add_segment_event_segment_lengths(self, test_well_path):
"""Segment lengths are stored on the event; 0 means not set."""
wellpath, timeline = test_well_path
event = timeline.add_segment_event(
event_date="2024-01-01",
well_path=wellpath,
start_md=500.0,
end_md=1000.0,
min_segment_length=5.0,
max_segment_length=50.0,
)
assert event.min_segment_length == (True, 5.0)
assert event.max_segment_length == (True, 50.0)
assert event.fixed_segment_length[0] is False
with pytest.raises(rips.RipsError, match="cannot be combined"):
timeline.add_segment_event(
well_path=wellpath,
start_md=500.0,
end_md=1000.0,
fixed_segment_length=10.0,
min_segment_length=5.0,
)
with pytest.raises(rips.RipsError, match="cannot exceed"):
timeline.add_segment_event(
well_path=wellpath,
start_md=500.0,
end_md=1000.0,
min_segment_length=60.0,
max_segment_length=50.0,
)
class TestWellEventScheduleApplication:
"""Tests for applying well events to create completions."""
+7 -1
View File
@@ -80,7 +80,8 @@ 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"
| "COMMENT" ;
| "FIXED_SEGMENT_LENGTH" | "MIN_SEGMENT_LENGTH"
| "MAX_SEGMENT_LENGTH" | "COMMENT" ;
(* accepted attribute names when event_type is "SEGMENT" *)
wellspec_attribute = "GROUP" | "CROSSFLOW" | "REFDEPTH" | "PHASE"
| "PRESSURE_COMPONENTS" | "COMMENT" ;
@@ -219,6 +220,11 @@ 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] |
| `FIXED_SEGMENT_LENGTH` | no | segment event `fixed_segment_length` and segment interval fixed segment length [m] |
| `MIN_SEGMENT_LENGTH` | no | segment event `min_segment_length` and segment interval min segment length [m] |
| `MAX_SEGMENT_LENGTH` | no | segment event `max_segment_length` and segment interval max segment length [m] |
The segment lengths control the MSW segmentation of the interval. Lengths must be positive. `FIXED_SEGMENT_LENGTH` cannot be combined with `MIN_SEGMENT_LENGTH` or `MAX_SEGMENT_LENGTH`, and `MIN_SEGMENT_LENGTH` cannot exceed `MAX_SEGMENT_LENGTH`. Violations are reported as parse errors.
**VALVE** → `add_valve_event` (requires an existing perforation at the MD)