Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python3

"""
Example: import an ORIONEVENTS well-event-timeline file into ResInsight.

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)
3. 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
rips/example_input_files/well_events.orion.

The well names in the file ("55_33-A-1", ...) must match well paths that exist
in the open project, so this example assumes a project with matching wells and
an Eclipse case is already loaded.
"""

import os

import rips


def main():
resinsight = rips.Instance.find()
project = resinsight.project

print("Import ORIONEVENTS Example")
print("=" * 50)

# Locate the sample ORIONEVENTS file shipped alongside the rips package.
orion_file = os.path.join(
os.path.dirname(rips.__file__), "example_input_files", "well_events.orion"
)
print(f"\n1. Parsing: {orion_file}")
document = rips.orion_events.parse_orion_events_file(orion_file)
print(f" Version: {document.version}, units: {document.unit_system}")
print(f" Wells: {[w.well_name for w in document.wells]}")
print(
f" Variables: { {k: f'{v.kind} {v.value}' for k, v in document.variables.items()} }"
)

# Apply the parsed document to the shared well event timeline.
print("\n2. Applying events to the timeline...")
well_path_coll = project.descendants(rips.WellPathCollection)[0]
timeline = well_path_coll.event_timeline()

report = rips.orion_events.apply_orion_document(
document, timeline, project, on_unknown_well="warn"
)
print(f" Events applied: {report.events_applied}")
print(f" Events skipped: {report.events_skipped}")
for warning in report.warnings:
print(f" WARNING: {warning}")
for error in report.errors:
print(f" ERROR: {error}")

# Generate Eclipse schedule text from the timeline.
print("\n3. Generating Eclipse schedule text...")
cases = project.cases()
if not cases:
print(" No Eclipse case loaded - skipping schedule generation.")
return

case = cases[0]
schedule_text = timeline.generate_schedule_text(
eclipse_case=case, export_msw_for_wells=project.well_paths()
)
if schedule_text:
print(f" Generated schedule text ({len(schedule_text)} characters):")
print(" " + "=" * 60)
for line in schedule_text.split("\n"):
print(f" {line}")
print(" " + "=" * 60)
else:
print(" No schedule text generated.")

print("\nExample completed.")


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env python3

"""
Example: the well_event_schedule.py timeline expressed as an ORIONEVENTS file.

This is the ORIONEVENTS counterpart to well_event_schedule.py: instead of
calling the WellEventTimeline API methods one by one, the same events are
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
events on a well
2. Well keyword events: WCONHIST and WELTARG (with attribute translation) and
WRFTPLT (generic Eclipse well keyword pass-through)
3. SCHEDULE-level keyword events not tied to a well: RPTRST, GRUPTREE, TUNING
4. Generating Eclipse schedule text from the resulting timeline

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
works with any project that has at least one well path.
"""

import rips


def build_orion_text(well_name):
return f"""\
ORIONEVENTS 2.0
UNIT METRIC

# Typed declarations
DATE STARTUP = 2024-01-01
DURATION RAMP = 31 DAYS

WELL W1 = "{well_name}"

WELL W1
# Tubing installed early (MD 0-2500m)
@STARTUP TUBING MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5

# Perforations; COMPLETION_NUMBER groups connections for COMPLUMP
@STARTUP + RAMP PERFORATION MDSTART=2000 MDEND=2200 RADIUS=0.05 SKIN=0.5 COMPLETION_NUMBER=1
@2024-04-01 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

# Valve in the first perforation; state event for documentation
@2024-03-01 VALVE MD=2100 TYPE=ICV STATE=OPEN CV=0.7 AREA=0.0001
@2024-02-15 STATE STATE=OPEN

# Well keyword events; WRFTPLT is passed through as a generic Eclipse keyword
@2024-01-15 WCONHIST STATUS=OPEN CMODE=RESV ORAT=3999.99 WRAT=0.01 GRAT=550678.44 VFP=1
@2024-05-01 WELTARG CMODE=ORAT VALUE=5000.0
@2024-06-01 WRFTPLT OUTPUT_RFT=YES OUTPUT_PLT=NO OUTPUT_SEGMENT=NO

# Schedule-level keywords (not tied to a well)
SCHEDULE
@STARTUP RPTRST BASIC=2 FREQ=1
@STARTUP GRUPTREE CHILD=OP PARENT=FIELD
@STARTUP TUNING TSINIT=1 TSMAXZ=30 TMAXWC=1 NEWTMX=12 NEWTMN=1 LITMAX=50 LITMIN=1 MXWSIT=50 MXWPIT=50
"""


def main():
resinsight = rips.Instance.find()
project = resinsight.project

print("Well Event Schedule (ORIONEVENTS) Example")
print("=" * 50)

print("\n1. Finding well")
wells = project.well_paths()
if not wells:
print(" No well paths in project - load a project with wells first.")
return
well_path = wells[0]
print(" Well name:", well_path.name)

print("\n2. Parsing ORIONEVENTS text...")
orion_text = build_orion_text(well_path.name)
print(orion_text)
document = rips.orion_events.parse_orion_events(orion_text)
print(f" Wells: {[w.well_name for w in document.wells]}")
print(f" Well events: {sum(len(w.events) for w in document.wells)}")
print(f" Schedule events: {len(document.schedule_events)}")

print("\n3. Applying events to the timeline...")
well_path_coll = project.descendants(rips.WellPathCollection)[0]
timeline = well_path_coll.event_timeline()
report = rips.orion_events.apply_orion_document(document, timeline, project)
print(f" Events applied: {report.events_applied}")
print(f" Events skipped: {report.events_skipped}")
for warning in report.warnings:
print(f" WARNING: {warning}")
for error in report.errors:
print(f" ERROR: {error}")

# Apply events up to a date to materialize completions
timeline.set_timestamp(timestamp="2024-12-24")

print("\n4. Verifying created completions...")
perforations = well_path.completions().perforations().perforations()
print(f" Perforations created: {len(perforations)}")
for perf in perforations:
print(
f" - MD {perf.start_measured_depth:.0f} to {perf.end_measured_depth:.0f}m"
)

print("\n5. Generating Eclipse schedule text from events...")
cases = project.cases()
if not cases:
print(" No Eclipse case loaded - skipping schedule generation.")
return

case = cases[0]
schedule_text = timeline.generate_schedule_text(
eclipse_case=case, export_msw_for_wells=[well_path]
)
if schedule_text:
print(f" Generated schedule text ({len(schedule_text)} characters)")
print(" " + "=" * 60)
for line in schedule_text.split("\n"):
print(f" {line}")
print(" " + "=" * 60)

expected_keywords = [
"DATES",
"COMPDAT",
"COMPLUMP",
"WCONHIST",
"WELTARG",
"WRFTPLT",
"RPTRST",
"GRUPTREE",
"TUNING",
]
found = [kw for kw in expected_keywords if kw in schedule_text]
print(f"\n Keywords found: {', '.join(found)}")
if "14:45:30.500" in schedule_text:
print(" DATES keyword preserves event time-of-day (14:45:30.500)")
else:
print(" Warning: No schedule text was generated")

print("\nExample completed.")


if __name__ == "__main__":
main()
13 changes: 13 additions & 0 deletions GrpcInterface/Python/rips/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import os
import sys
from types import ModuleType
from typing import List

# Configure null handler to prevent "No handler found" warnings
Expand Down Expand Up @@ -30,6 +31,18 @@
from . import well_events as well_events # noqa: F401, E402
from . import category_mapping as category_mapping # noqa: F401, E402


def __getattr__(name: str) -> ModuleType:
# Lazy import (PEP 562) so `python -m rips.orion_events` does not trigger
# runpy's "found in sys.modules after import of package" warning while
# `rips.orion_events` still resolves after a plain `import rips`.
if name == "orion_events":
import importlib

return importlib.import_module(".orion_events", __name__)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__: List[str] = []
for key in class_dict(): # noqa: F405
__all__.append(key)
Expand Down
25 changes: 25 additions & 0 deletions GrpcInterface/Python/rips/example_input_files/well_events.orion
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Example ORIONEVENTS well-event-timeline file (version 2.0).
# Parsed by rips.orion_events; see that module's docstring for the grammar.
# Validate with: python3 -m rips.orion_events well_events.orion

ORIONEVENTS 2.0
UNIT METRIC

# Typed declarations: dates, whole-day durations, and well-name aliases.
DATE A1_STARTUP = 2018-01-01
DATE A2_STARTUP = 2018-03-01 + 9
DURATION RAMP = 5 DAYS

WELL A1 = "55_33-A-1"

WELL A1
@A1_STARTUP PERFORATION MDSTART=1644.49 MDEND=1664.28 RADIUS=0.12065 SKIN=5 COMPLETION_NUMBER=1
@A1_STARTUP PERFORATION MDSTART=1664.28 MDEND=1674.18 RADIUS=0.12065 SKIN=5 COMPLETION_NUMBER=2
@A1_STARTUP PERFORATION MDSTART=1674.18 MDEND=1689.82 RADIUS=0.12065 SKIN=5 COMPLETION_NUMBER=3
@A1_STARTUP + RAMP WCONHIST STATUS=OPEN CMODE=ORAT VFP=1
@A1_STARTUP + RAMP WELTARG CMODE=BHP VALUE=50

WELL "55_33-A-2"
@A2_STARTUP - 1 PERFORATION MDSTART=1692.79 MDEND=1706 RADIUS=0.12065 SKIN=5 COMPLETION_NUMBER=1
@A2_STARTUP WCONHIST STATUS=OPEN CMODE=ORAT VFP=2
@A2_STARTUP WELTARG CMODE=BHP VALUE=50
Loading