#14557 Orion Events: Rename tubing event to segment

This commit is contained in:
Kristian Bendiksen
2026-08-20 11:02:55 +02:00
parent db1c1f1f13
commit ce12272721
4 changed files with 121 additions and 27 deletions
@@ -9,7 +9,7 @@ written as ORIONEVENTS 2.0 text (see rips/orion_events.py for the grammar) and
applied in one go with rips.orion_events.apply_orion_document().
It demonstrates the full event coverage of the format:
1. TUBING, PERFORATION (incl. a time-of-day date), VALVE and STATE completion
1. SEGMENT, PERFORATION (incl. a time-of-day date), VALVE and STATE completion
events on a well
2. Partial WELLSPEC updates that cumulatively change completion export settings
and generate dated WELSPECS records
@@ -65,7 +65,7 @@ WELL W1
@2024-04-15 WELLSPEC CROSSFLOW=False REFDEPTH=1000 PHASE=OIL
# COMMENT is stored on the event and safely emitted as a schedule comment.
@STARTUP TUBING MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 COMMENT="Install production tubing"
@STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 PRESSURE_COMPONENTS=HFA COMMENT="Install production segment"
# Perforations; COMPLETION_NUMBER groups connections for COMPLUMP.{filter_comment}
@STARTUP + RAMP PERFORATION MDSTART=2000 MDEND=2200 RADIUS=0.05 SKIN=0.5 COMPLETION_NUMBER=1{filter_ref} COMMENT="Open high-priority interval"
+35 -9
View File
@@ -95,7 +95,7 @@ Notes on the grammar:
attribute values, e.g. ``FILTER="SOIL > 0.8 AND PERMX > 200"``.
* Every attribute is ``KEY=VALUE``; bare positional tokens are rejected.
* Event types inside a WELL block are either the built-in completion events
``PERFORATION``, ``TUBING``, ``VALVE``, ``STATE`` and ``WELLSPEC``, or any
``PERFORATION``, ``SEGMENT``, ``VALVE``, ``STATE`` and ``WELLSPEC``, or any
Eclipse well keyword (``WCONHIST``, ``WELTARG``, ``WRFTPLT``, ``WCONPROD``,
...), which
is passed through generically with the well name injected as WELL. Event
@@ -987,8 +987,16 @@ _PERF_KNOWN = {
"FILTER",
"COMMENT",
}
_TUBING_REQUIRED = ("MDSTART", "MDEND")
_TUBING_KNOWN = {"MDSTART", "MDEND", "INNER_DIAMETER", "ROUGHNESS", "COMMENT"}
_SEGMENT_REQUIRED = ("MDSTART", "MDEND")
_SEGMENT_KNOWN = {
"MDSTART",
"MDEND",
"INNER_DIAMETER",
"ROUGHNESS",
"PRESSURE_COMPONENTS",
"COMMENT",
}
_PRESSURE_COMPONENTS = {"H--", "HF-", "HFA"}
_VALVE_REQUIRED = ("MD", "TYPE")
_VALVE_KNOWN = {"MD", "TYPE", "STATE", "CV", "AREA", "COMMENT"} | {
"AICD_STRENGTH",
@@ -1389,6 +1397,8 @@ def _materialize_filter(ctx: _FilterContext, event_filter: EventFilter) -> Any:
def _suspected_typo(event_type: str) -> Optional[str]:
"""Return the built-in event type this one looks like a misspelling of."""
if event_type == "TUBING":
return "SEGMENT"
close = difflib.get_close_matches(event_type, _EVENT_DISPATCH, n=1, cutoff=0.8)
return close[0] if close else None
@@ -1573,7 +1583,7 @@ def _apply_perforation(
report.events_applied += 1
def _apply_tubing(
def _apply_segment(
event: OrionEvent,
well_path: Any,
timeline: Any,
@@ -1581,17 +1591,19 @@ def _apply_tubing(
ctx: Optional[_FilterContext] = None,
) -> None:
if not _check_completion_attrs(
event, "TUBING", _TUBING_KNOWN, _TUBING_REQUIRED, report
event, "SEGMENT", _SEGMENT_KNOWN, _SEGMENT_REQUIRED, report
):
return
attrs = event.attributes
try:
start_md = float(_as_number(attrs["MDSTART"], event.loc))
end_md = float(_as_number(attrs["MDEND"], event.loc))
kwargs: Dict[str, Any] = {
"event_date": _iso_event_date(event.event_date),
"well_path": well_path,
"start_md": float(_as_number(attrs["MDSTART"], event.loc)),
"end_md": float(_as_number(attrs["MDEND"], event.loc)),
"start_md": start_md,
"end_md": end_md,
}
if "INNER_DIAMETER" in attrs:
kwargs["inner_diameter"] = float(
@@ -1599,12 +1611,26 @@ def _apply_tubing(
)
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 OrionParseError(
"PRESSURE_COMPONENTS must be H--, HF-, or HFA", event.loc
)
except OrionParseError as exc:
report.errors.append(str(exc))
report.events_skipped += 1
return
timeline_event = timeline.add_tubing_event(**kwargs)
well_path.completion_settings().add_custom_segment_interval(
start_md=start_md, end_md=end_md
)
if pressure_components is not None:
msw_settings = well_path.msw_settings()
msw_settings.pressure_drop = pressure_components
msw_settings.update()
_apply_event_comment(event, timeline_event)
report.events_applied += 1
@@ -1775,7 +1801,7 @@ _EventDispatch = Callable[
# built-in, which is governed by the on_unknown_event policy).
_EVENT_DISPATCH: Dict[str, _EventDispatch] = {
"PERFORATION": _apply_perforation,
"TUBING": _apply_tubing,
"SEGMENT": _apply_segment,
"VALVE": _apply_valve,
"STATE": _apply_state,
"WELLSPEC": _apply_wellspec,
@@ -1787,7 +1813,7 @@ _EVENT_DISPATCH: Dict[str, _EventDispatch] = {
# block or be emitted as Eclipse keywords.
_COMPLETION_EVENT_TYPES = (
"PERFORATION",
"TUBING",
"SEGMENT",
"VALVE",
"STATE",
"WELLSPEC",
@@ -61,16 +61,33 @@ class FakeCompletionSettings:
self.allow_well_cross_flow = True
self.reference_depth_for_export = None
self.well_type_for_export = "OIL"
self.custom_segment_calls = []
def add_custom_segment_interval(self, **kwargs):
self.custom_segment_calls.append(kwargs)
class FakeMswSettings:
def __init__(self):
self.pressure_drop = "HF-"
self.update_calls = 0
def update(self):
self.update_calls += 1
class FakeWellPath:
def __init__(self, name):
self.name = name
self._completion_settings = FakeCompletionSettings()
self._msw_settings = FakeMswSettings()
def completion_settings(self):
return self._completion_settings
def msw_settings(self):
return self._msw_settings
class FakeProject:
def __init__(self, names, cases=()):
@@ -1100,10 +1117,11 @@ class TestApplying:
assert expected_error in report.errors[0]
assert timeline.wellspec_calls == []
def test_tubing_mapping(self):
def test_segment_mapping_creates_custom_interval_and_sets_pressure_drop(self):
text = (
'ORIONEVENTS 2.0\nWELL "55_33-A-1"\n'
" @2024-01-01 TUBING MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5\n"
" @2024-01-01 SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 "
"ROUGHNESS=1.0e-5 PRESSURE_COMPONENTS=HFA\n"
)
timeline, report = self._apply(text)
assert report.events_applied == 1
@@ -1113,6 +1131,37 @@ class TestApplying:
assert call["inner_diameter"] == 0.15
assert call["roughness"] == pytest.approx(1.0e-5)
well = call["well_path"]
assert well.completion_settings().custom_segment_calls == [
{"start_md": 0.0, "end_md": 2500.0}
]
assert well.msw_settings().pressure_drop == "HFA"
assert well.msw_settings().update_calls == 1
def test_segment_rejects_invalid_pressure_components(self):
text = (
'ORIONEVENTS 2.0\nWELL "55_33-A-1"\n'
" @2024-01-01 SEGMENT MDSTART=0 MDEND=2500 "
"PRESSURE_COMPONENTS=INVALID\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 timeline.tubing_calls == []
def test_tubing_is_reported_as_renamed_event(self):
text = (
'ORIONEVENTS 2.0\nWELL "55_33-A-1"\n'
" @2024-01-01 TUBING MDSTART=0 MDEND=2500\n"
)
timeline, report = self._apply(text)
assert report.events_skipped == 1
assert timeline.keyword_calls == []
assert any("did you mean 'SEGMENT'" in warning for warning in report.warnings)
def test_valve_mapping(self):
text = (
'ORIONEVENTS 2.0\nWELL "55_33-A-1"\n'
@@ -1702,7 +1751,7 @@ class TestOrionEventsIntegration:
"DATE STARTUP = 2024-01-01\n"
"DURATION RAMP = 31 DAYS\n"
f'WELL "{well.name}"\n'
" @STARTUP TUBING MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5\n"
" @STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 PRESSURE_COMPONENTS=HFA\n"
" @STARTUP + RAMP PERFORATION MDSTART=2000 MDEND=2200 RADIUS=0.05 SKIN=0.5 COMPLETION_NUMBER=1\n"
" @2024-05-15T14:45:30.500 PERFORATION MDSTART=2300 MDEND=2350 RADIUS=0.05 SKIN=0.4 COMPLETION_NUMBER=2\n"
" @2024-03-01 VALVE MD=2100 TYPE=ICV STATE=OPEN CV=0.7 AREA=0.0001\n"
@@ -1721,6 +1770,13 @@ class TestOrionEventsIntegration:
assert report.warnings == []
assert report.events_applied == 11
custom_segments = well.descendants(rips.CustomSegmentInterval)
assert any(
segment.start_md == 0.0 and segment.end_md == 2500.0
for segment in custom_segments
)
assert well.msw_settings().pressure_drop == "HFA"
timeline.set_timestamp(timestamp="2024-12-24")
schedule = timeline.generate_schedule_text(
eclipse_case=case, export_msw_for_wells=[well]
+25 -13
View File
@@ -34,7 +34,7 @@ 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 TUBING MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5
@STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 PRESSURE_COMPONENTS=HFA
@STARTUP + RAMP PERFORATION MDSTART=2000 MDEND=2200 RADIUS=0.05 SKIN=0.5 COMPLETION_NUMBER=1 FILTER=POROPERM
@2024-05-15T14:45:30.500 PERFORATION MDSTART=2300 MDEND=2350 RADIUS=0.05
@2024-03-01 VALVE MD=2100 TYPE=ICV STATE=OPEN CV=0.7 AREA=0.0001
@@ -56,9 +56,10 @@ SCHEDULE # schedule-level keywords, not tied to a well
```
document = header , { statement } ;
header = "ORIONEVENTS" , "2.0" ; (* first meaningful line *)
statement = unit_directive | declaration | well_block_open
| schedule_block_open | event_line ;
statement = unit_directive | declaration | report_line | well_block_open
| group_block_open | schedule_block_open | event_line ;
unit_directive = "UNIT" , ( "METRIC" | "FIELD" | "LAB" ) ;
report_line = "REPORT" , date_expr ;
declaration = date_decl | duration_decl | well_decl | filter_decl ;
date_decl = "DATE" , ident , "=" , date_expr ; (* DATE X = 2018-03-01 + 9 *)
@@ -72,8 +73,14 @@ filter_term = [ result_type , "." ] , ident , comp_op , number ;
comp_op = ">" | ">=" | "<" | "<=" ;
well_block_open = "WELL" , ( quoted_string | ident ) ; (* no "=" present *)
group_block_open = "GROUP" , quoted_string ; (* group keyword events *)
schedule_block_open = "SCHEDULE" ; (* well-less keyword events *)
event_line = "@" , date_expr , event_type , { attribute } ;
event_line = "@" , date_expr , event_type , { attribute } ;
event_type = completion_event_type | eclipse_keyword ;
completion_event_type = "PERFORATION" | "SEGMENT" | "VALVE" | "STATE" | "WELLSPEC" ;
segment_attribute = "MDSTART" | "MDEND" | "INNER_DIAMETER" | "ROUGHNESS"
| "PRESSURE_COMPONENTS" | "COMMENT" ;
(* accepted attribute names when event_type is "SEGMENT" *)
date_expr = ( iso_date | iso_datetime | date_ident ) , { sign , term } ;
duration_expr = ( integer | dur_ident ) , { sign , term } , [ "DAYS" | "days" ] ;
@@ -98,9 +105,11 @@ The format is line-oriented. Every non-blank line is dispatched on its first tok
| `DATE` | Declare a typed date variable |
| `DURATION` | Declare a typed whole-day duration variable |
| `FILTER` | Declare a typed cell-filter expression |
| `REPORT` | Add a date that must appear in generated schedule output |
| `WELL` | With `=`: declare a well-name alias. Without `=`: open a well event block |
| `GROUP` | Open a block of group-level keyword events |
| `SCHEDULE` | Open a block of schedule-level keyword events (bare keyword, no arguments) |
| `@` | An event line, appended to the enclosing WELL or SCHEDULE block |
| `@` | An event line, appended to the enclosing WELL, GROUP or SCHEDULE block |
| `#` | Comment (also allowed trailing on any line, outside double quotes) |
## Typed declarations
@@ -142,7 +151,7 @@ Whitespace around `+`/`-` is optional but conventional. A time-of-day is preserv
## Event types
Event lines are `@<date_expr> <EVENT_TYPE> KEY=VALUE ...`. Inside a WELL block, the event type is either one of the four built-in completion events or any Eclipse well keyword.
Event lines are `@<date_expr> <EVENT_TYPE> KEY=VALUE ...`. Inside a WELL block, the event type is either one of the five built-in completion events or any Eclipse well keyword.
### Built-in completion events
@@ -157,14 +166,17 @@ Event lines are `@<date_expr> <EVENT_TYPE> KEY=VALUE ...`. Inside a WELL block,
| `COMPLETION_NUMBER` | no | `completion_number` (COMPLUMP grouping) |
| `FILTER` | no | case-level combined data filter attached to the perforation: a declared `FILTER` variable (`FILTER=POROPERM`) or an inline quoted expression (`FILTER="PORO > 0.4"`) — see [Filter expressions](#filter-expressions) |
**TUBING**`add_tubing_event`
**SEGMENT**`add_tubing_event` and `add_custom_segment_interval`
Each event adds the underlying tubing timeline event and a corresponding **Custom Segment Interval** with the same measured-depth range.
| Attribute | Required | Maps to |
|---|---|---|
| `MDSTART` | yes | `start_md` |
| `MDEND` | yes | `end_md` |
| `INNER_DIAMETER` | no | `inner_diameter` [m] |
| `ROUGHNESS` | no | `roughness` [m] |
| `MDSTART` | yes | tubing `start_md` and custom interval `start_md` |
| `MDEND` | yes | tubing `end_md` and custom interval `end_md` |
| `INNER_DIAMETER` | no | tubing `inner_diameter` [m] |
| `ROUGHNESS` | no | tubing `roughness` [m] |
| `PRESSURE_COMPONENTS` | no | MSW `pressure_drop`: `H--`, `HF-` or `HFA` |
**VALVE**`add_valve_event` (requires an existing perforation at the MD)
@@ -209,7 +221,7 @@ SCHEDULE
@STARTUP TUNING TSINIT=1 TSMAXZ=30 NEWTMX=12
```
Completion event types (`PERFORATION`, `TUBING`, `VALVE`, `STATE`) are rejected in a SCHEDULE block. A later `WELL` line switches back to well events; blocks can be interleaved freely.
Completion event types (`PERFORATION`, `SEGMENT`, `VALVE`, `STATE`, `WELLSPEC`) are rejected in a SCHEDULE block. A later `WELL` line switches back to well events; blocks can be interleaved freely.
## Attributes and quoting
@@ -291,5 +303,5 @@ The returned `ApplyReport` carries `events_applied`, `events_skipped`, `warnings
## Version history
- **2.0** — current: typed `DATE`/`DURATION`/`WELL`/`FILTER` declarations, `WELL`/`SCHEDULE` blocks, signed day-offset chains, ISO datetimes, built-in completion events plus generic keyword pass-through, perforation data filters, multi-error diagnostics, validator CLI.
- **2.0** — current: typed `DATE`/`DURATION`/`WELL`/`FILTER` declarations, `WELL`/`SCHEDULE` blocks, signed day-offset chains, ISO datetimes, built-in completion events (including `SEGMENT` custom intervals and pressure components) plus generic keyword pass-through, perforation data filters, multi-error diagnostics, validator CLI.
- **1.x** — unsupported. Files using `SET` variables and single-quoted well names are rejected with a migration message pointing at this grammar.