Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions docs/rips/PythonExamples/experimental/import_orion_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

This example shows how to:
1. Parse an ORIONEVENTS text file into a structured document
2. Apply its events to the well event timeline (perforations, WCONHIST, WELTARG),
2. Normalize matching keyword events while retaining same-date perforations
3. Apply its events to the well event timeline (perforations, WCONHIST, WELTARG),
materializing FILTER declarations as case-level combined data filters
attached to the perforations
3. Generate Eclipse schedule text from the resulting timeline
4. Insert multiline RAW_TEXT at a selected position in the generated schedule
5. Generate Eclipse schedule text from the resulting timeline

The ORIONEVENTS format is a compact, human-authored description of dated well
events. See rips/orion_events.py for the grammar. A sample input file ships at
Expand Down Expand Up @@ -46,6 +48,36 @@ def main():
f" Variables: { {k: f'{v.kind} {v.value}' for k, v in document.variables.items()} }"
)

# Normalization merges matching keyword events, but events that create or
# expand domain objects remain separate. The sample has three perforations
# at A1_STARTUP; all three are retained.
normalized = rips.orion_events.coalesce_orion_document(document)
source_perforation_count = sum(
event.event_type == "PERFORATION"
for well in document.wells
for event in well.events
)
normalized_perforation_count = sum(
event.event_type == "PERFORATION"
for well in normalized.wells
for event in well.events
)
print(
" Perforations retained during normalization: "
f"{source_perforation_count} -> {normalized_perforation_count}"
)

# RAW_TEXT bodies bypass keyword parsing and formatting. Placement and an
# optional anchor control where each block appears in the generated schedule.
raw_text_events = [
event for event in document.schedule_events if event.event_type == "RAW_TEXT"
]
for event in raw_text_events:
print(
f" RAW_TEXT: {event.raw_placement} {event.raw_anchor or ''} "
f"(priority {event.raw_priority})"
)

# The sample file uses FILTER declarations, so a case is needed to resolve
# the referenced result names and to own the created combined filters.
cases = project.cases()
Expand All @@ -67,6 +99,7 @@ def main():
)
print(f" Events applied: {report.events_applied}")
print(f" Events skipped: {report.events_skipped}")
print(f" Report dates: {report.report_dates}")
for warning in report.warnings:
print(f" WARNING: {warning}")
for error in report.errors:
Expand All @@ -86,11 +119,14 @@ def main():

# Generate Eclipse schedule text from the timeline.
print("\n4. Generating Eclipse schedule text...")
if report.events_applied == 0:
print(" No events applied - skipping schedule generation.")
if report.events_applied == 0 and not report.report_dates:
print(" No events or report dates found - skipping schedule generation.")
return
schedule_text = timeline.generate_schedule_text(
eclipse_case=case, export_msw_for_wells=project.well_paths()
eclipse_case=case,
export_msw_for_wells=project.well_paths(),
additional_dates=report.report_dates,
align_columns=True,
)
if schedule_text:
print(f" Generated schedule text ({len(schedule_text)} characters):")
Expand Down
50 changes: 38 additions & 12 deletions docs/rips/PythonExamples/experimental/well_event_schedule_orion.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@
It demonstrates the full event coverage of the format:
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
2. Partial WELSPECS updates that cumulatively change completion export settings
and generate dated WELSPECS records
3. A FILTER declaration (qualified result name) referenced by a perforation,
materialized as a case-level combined data filter
4. COMMENT attributes preserved on timeline events and emitted before their
generated schedule keywords
5. Same-owner/type/date WCONHIST lines merged into one event, with later
attributes extending or overriding earlier attributes
5. Same-owner/type/date WCONHIST lines merged into one event, while same-date
perforations remain separate
6. Well keyword events: WCONHIST and WELTARG (with attribute translation) and
WRFTPLT (generic Eclipse well keyword pass-through)
7. A GROUP-level MEMBER event expanded to one GRUPTREE record per member
8. SCHEDULE-level keyword events not tied to a well: RPTRST, GRUPTREE, TUNING
9. REPORT dates, passed to generate_schedule_text(additional_dates=...) so
9. Multiline RAW_TEXT inserted at a chosen position without parsing its contents
10. REPORT dates, passed to generate_schedule_text(additional_dates=...) so
they appear as bare DATES keywords (summary-report triggers)
10. Schedule metadata, COMPORD generation and aligned-column output
11. Schedule metadata, COMPORD generation and aligned-column output

The ORIONEVENTS text is built inline with the name of the first well path in
the project (like well_event_schedule.py, which uses wells[0]), so the example
Expand Down Expand Up @@ -59,17 +60,18 @@ def build_orion_text(well_name, with_filter):
WELL W1 = "{well_name}"

WELL W1
# WELLSPEC updates completion export settings and emits WELSPECS. Attributes
# WELSPECS updates completion export settings and emits WELSPECS. Attributes
# are optional: the second event inherits GROUP from the first event.
@2024-01-05 WELLSPEC GROUP="ORION_GROUP" CROSSFLOW=True REFDEPTH=1002 PHASE=WATER
@2024-04-15 WELLSPEC CROSSFLOW=False REFDEPTH=1000 PHASE=OIL
@2024-01-05 WELSPECS GROUP="ORION_GROUP" CROSSFLOW=True REFDEPTH=1002 PHASE=WATER
@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"

# Perforations; COMPLETION_NUMBER groups connections for COMPLUMP.{filter_comment}
# Perforations; COMPLETION_NUMBER groups connections for COMPLUMP. Same-date
# perforations are kept as separate events during normalization.{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"
@2024-04-01 PERFORATION MDSTART=2400 MDEND=2600 RADIUS=0.05 SKIN=0.3 COMPLETION_NUMBER=2
@STARTUP + RAMP PERFORATION MDSTART=2400 MDEND=2600 RADIUS=0.05 SKIN=0.3 COMPLETION_NUMBER=2

# Time-of-day is preserved and emitted as the TIME field of DATES
@2024-05-15T14:45:30.500 PERFORATION MDSTART=2300 MDEND=2350 RADIUS=0.05 SKIN=0.4 COMPLETION_NUMBER=3
Expand Down Expand Up @@ -97,6 +99,15 @@ def build_orion_text(well_name, with_filter):
@STARTUP GRUPTREE CHILD_GROUP=OP PARENT_GROUP=FIELD
@STARTUP TUNING TSINIT=1 TSMAXZ=30 TMAXWC=1 NEWTMX=12 NEWTMN=1 LITMAX=50 LITMIN=1 MXWSIT=50 MXWPIT=50

# RAW_TEXT preserves its body verbatim. This block is emitted after RPTRST;
# PRIORITY orders multiple raw blocks sharing the same placement and anchor.
@STARTUP RAW_TEXT PLACEMENT=AFTER_KEYWORD ANCHOR=RPTRST PRIORITY=10
-- Custom schedule text not modeled by the timeline API
WTRACER
'{well_name}' 'ORION_TRACER' 1.0 /
/
END_RAW_TEXT

# Report dates: emitted as bare DATES keywords so Eclipse/Flow writes a
# summary report at these dates even though no events fall on them.
REPORT 2024-07-01
Expand Down Expand Up @@ -131,10 +142,24 @@ def main():
document = rips.orion_events.parse_orion_events(orion_text)
print(f" Wells: {[w.well_name for w in document.wells]}")
source_well_event_count = sum(len(w.events) for w in document.wells)
source_perforation_count = sum(
event.event_type == "PERFORATION"
for well in document.wells
for event in well.events
)
normalized = rips.orion_events.coalesce_orion_document(document)
merged_well_event_count = sum(len(w.events) for w in normalized.wells)
merged_perforation_count = sum(
event.event_type == "PERFORATION"
for well in normalized.wells
for event in well.events
)
print(f" Source well-event lines: {source_well_event_count}")
print(f" Events after same-date merge: {merged_well_event_count}")
print(
" Perforations retained during merge: "
f"{source_perforation_count} -> {merged_perforation_count}"
)
print(f" Groups: {[group.group_name for group in document.groups]}")
print(f" Schedule events: {len(document.schedule_events)}")

Expand All @@ -155,9 +180,9 @@ def main():
# Apply events up to a date to materialize completions
timeline.set_timestamp(timestamp="2024-12-24")

print("\n4. Verifying created completions and WELLSPEC settings...")
print("\n4. Verifying created completions and WELSPECS settings...")
completion_settings = well_path.completion_settings()
print(" Completion export settings after the latest WELLSPEC:")
print(" Completion export settings after the latest WELSPECS:")
print(f" Group: {completion_settings.group_name_for_export}")
print(f" Cross-flow: {completion_settings.allow_well_cross_flow}")
print(f" Ref. depth: {completion_settings.reference_depth_for_export}")
Expand Down Expand Up @@ -207,6 +232,7 @@ def main():
"RPTRST",
"GRUPTREE",
"TUNING",
"WTRACER",
]
found = [kw for kw in expected_keywords if kw in schedule_text]
print(f"\n Keywords found: {', '.join(found)}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,12 @@
view.show_grid_box = False
view.set_visible(False)
view.update()

# Tile all visible 3D views
resinsight.project.tile_views()

# Link the views, then unlink the last view from the group
project_views = resinsight.project.views()
if len(project_views) >= 2:
resinsight.project.link_views(views=project_views)
resinsight.project.unlink_views(views=[project_views[-1]])
104 changes: 92 additions & 12 deletions docs/rips/generated/generated_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ class PhaseSelection(StrEnum):
PHASE_GAS = "PHASE_GAS"
PHASE_WAT = "PHASE_WAT"

class Placement(StrEnum):
AFTER_DATE = "AFTER_DATE"
BEFORE_KEYWORD = "BEFORE_KEYWORD"
AFTER_KEYWORD = "AFTER_KEYWORD"
END_OF_DATE = "END_OF_DATE"

class PorePressureReservoirSource(StrEnum):
GRID = "GRID"
LAS_FILE = "LAS_FILE"
Expand Down Expand Up @@ -2399,6 +2405,27 @@ def __init__(self, pb2_object: Optional[PdmObject_pb2.PdmObject]=None, channel:
if PressureTableItem.__custom_init__ is not None:
PressureTableItem.__custom_init__(self, pb2_object=pb2_object, channel=channel)

class RawTextEvent(WellEvent):
"""
RawTextEvent

Attributes:
anchor_keyword (str): Anchor Keyword
placement (Placement): One of [AFTER_DATE, BEFORE_KEYWORD, AFTER_KEYWORD, END_OF_DATE]
priority (int): Priority
text (str): Text
"""
__custom_init__ = None #: Assign a custom init routine to be run at __init__

def __init__(self, pb2_object: Optional[PdmObject_pb2.PdmObject]=None, channel: Optional[grpc.Channel]=None) -> None:
self.anchor_keyword: str = ""
self.placement: Placement = Placement.AFTER_DATE
self.priority: int = 0
self.text: str = ""
WellEvent.__init__(self, pb2_object, channel)
if RawTextEvent.__custom_init__ is not None:
RawTextEvent.__custom_init__(self, pb2_object=pb2_object, channel=channel)

class RegularSurface(SurfaceInterface):
"""
Attributes:
Expand Down Expand Up @@ -2549,6 +2576,18 @@ def import_summary_case(self, file_name: str="") -> FileSummaryCase:
return self._call_pdm_method_return_value("importSummaryCase", FileSummaryCase, file_name=file_name)


def link_views(self, views: List[View]=[]) -> None:
"""
Link the specified 3D views

Arguments:
views (List[View]):
Returns:

"""
self._call_pdm_method_void("linkViews", views=views)


def summary_case(self, case_id: int=-1) -> Optional[FileSummaryCase]:
"""
Find Summary Case
Expand All @@ -2573,6 +2612,30 @@ def surface_folder(self, folder_name: str="") -> SurfaceCollection:
return self._call_pdm_method_return_value("surfaceFolder", SurfaceCollection, folder_name=folder_name)


def tile_views(self, ) -> None:
"""
Tile all visible 3D view windows

Arguments:

Returns:

"""
self._call_pdm_method_void("tileViews")


def unlink_views(self, views: List[View]=[]) -> None:
"""
Unlink the specified 3D views

Arguments:
views (List[View]):
Returns:

"""
self._call_pdm_method_void("unlinkViews", views=views)


def valve_templates(self, ) -> ValveTemplateCollection:
"""
Get Valve Template Collection
Expand Down Expand Up @@ -4040,6 +4103,22 @@ def add_perf_event(self, event_date: str="2024-01-01", well_path: Optional[WellP
return self._call_pdm_method_return_value("AddPerfEvent", WellEventPerf, event_date=event_date, well_path=well_path, start_md=start_md, end_md=end_md, diameter=diameter, skin_factor=skin_factor, state=state, completion_number=completion_number)


def add_raw_text_event_internal(self, event_date: str="2024-01-01", text: str="", placement: Placement=Placement.AFTER_DATE, anchor_keyword: str="", priority: int=0) -> RawTextEvent:
"""
Add raw text at a specific position in a dated schedule section

Arguments:
event_date (str): Event Date (YYYY-MM-DD)
text (str): Raw schedule text
placement (Placement): One of [AFTER_DATE, BEFORE_KEYWORD, AFTER_KEYWORD, END_OF_DATE]
anchor_keyword (str): Keyword used for before/after placement
priority (int): Ascending order among raw text events at the same position
Returns:
RawTextEvent
"""
return self._call_pdm_method_return_value("AddRawTextEventInternal", RawTextEvent, event_date=event_date, text=text, placement=placement, anchor_keyword=anchor_keyword, priority=priority)


def add_state_event(self, event_date: str="2024-01-01", well_path: Optional[WellPath]=None, well_state: WellState=WellState.OPEN) -> WellEventState:
"""
Add a well state event to the timeline
Expand Down Expand Up @@ -4366,6 +4445,18 @@ def add_extraction_curve(self, case: Optional[Reservoir]=None, well_path: Option
return self._call_pdm_method_return_value("AddExtractionCurve", WellLogExtractionCurve, case=case, well_path=well_path, property_type=property_type, property_name=property_name, time_step=time_step)


class FileWellPath(WellPath):
"""
Well Paths Loaded From File

"""
__custom_init__ = None #: Assign a custom init routine to be run at __init__

def __init__(self, pb2_object: Optional[PdmObject_pb2.PdmObject]=None, channel: Optional[grpc.Channel]=None) -> None:
WellPath.__init__(self, pb2_object, channel)
if FileWellPath.__custom_init__ is not None:
FileWellPath.__custom_init__(self, pb2_object=pb2_object, channel=channel)

class WellPathAicdParameters(PdmObjectBase):
"""
Attributes:
Expand Down Expand Up @@ -4409,18 +4500,6 @@ def __init__(self, pb2_object: Optional[PdmObject_pb2.PdmObject]=None, channel:
if WellPathAicdParameters.__custom_init__ is not None:
WellPathAicdParameters.__custom_init__(self, pb2_object=pb2_object, channel=channel)

class FileWellPath(WellPath):
"""
Well Paths Loaded From File

"""
__custom_init__ = None #: Assign a custom init routine to be run at __init__

def __init__(self, pb2_object: Optional[PdmObject_pb2.PdmObject]=None, channel: Optional[grpc.Channel]=None) -> None:
WellPath.__init__(self, pb2_object, channel)
if FileWellPath.__custom_init__ is not None:
FileWellPath.__custom_init__(self, pb2_object=pb2_object, channel=channel)

class WellPathCompletionSettings(PdmObjectBase):
"""
Attributes:
Expand Down Expand Up @@ -4853,6 +4932,7 @@ def class_dict() -> Dict[str, Type[PdmObjectBase]]:
classes['PressureTableItem'] = PressureTableItem
classes['Project'] = Project
classes['PropertyFilter'] = PropertyFilter
classes['RawTextEvent'] = RawTextEvent
classes['RegularFileSurface'] = RegularFileSurface
classes['RegularSurface'] = RegularSurface
classes['ResampleData'] = ResampleData
Expand Down
Loading