Experimental
This page shows Python examples from the experimental folder.
Import Orion Events
import_orion_events.py
1#!/usr/bin/env python3
2
3"""
4Example: import an ORIONEVENTS well-event-timeline file into ResInsight.
5
6This example shows how to:
71. Parse an ORIONEVENTS text file into a structured document
82. Normalize matching keyword events while retaining same-date perforations
93. Apply its events to the well event timeline (perforations, WCONHIST, WELTARG),
10 materializing FILTER declarations as case-level combined data filters
11 attached to the perforations
124. Insert multiline RAW_TEXT at a selected position in the generated schedule
135. Generate Eclipse schedule text from the resulting timeline
14
15The ORIONEVENTS format is a compact, human-authored description of dated well
16events. See rips/orion_events.py for the grammar. A sample input file ships at
17rips/example_input_files/well_events.orion.
18
19The well names in the file ("55_33-A-1", ...) must match well paths that exist
20in the open project, so this example assumes a project with matching wells and
21an Eclipse case is already loaded. The case must also hold the results the
22FILTER expressions reference (PORO and PERMX in the sample file); a missing
23result raises before any event is applied.
24"""
25
26import os
27
28import rips
29import rips.orion_events
30
31
32def main():
33 resinsight = rips.Instance.find()
34 project = resinsight.project
35
36 print("Import ORIONEVENTS Example")
37 print("=" * 50)
38
39 # Locate the sample ORIONEVENTS file shipped alongside the rips package.
40 orion_file = os.path.join(
41 os.path.dirname(rips.__file__), "example_input_files", "well_events.orion"
42 )
43 print(f"\n1. Parsing: {orion_file}")
44 document = rips.orion_events.parse_orion_events_file(orion_file)
45 print(f" Version: {document.version}, units: {document.unit_system}")
46 print(f" Wells: {[w.well_name for w in document.wells]}")
47 print(
48 f" Variables: { {k: f'{v.kind} {v.value}' for k, v in document.variables.items()} }"
49 )
50
51 # Normalization merges matching keyword events, but events that create or
52 # expand domain objects remain separate. The sample has three perforations
53 # at A1_STARTUP; all three are retained.
54 normalized = rips.orion_events.coalesce_orion_document(document)
55 source_perforation_count = sum(
56 event.event_type == "PERFORATION"
57 for well in document.wells
58 for event in well.events
59 )
60 normalized_perforation_count = sum(
61 event.event_type == "PERFORATION"
62 for well in normalized.wells
63 for event in well.events
64 )
65 print(
66 " Perforations retained during normalization: "
67 f"{source_perforation_count} -> {normalized_perforation_count}"
68 )
69
70 # RAW_TEXT bodies bypass keyword parsing and formatting. Placement and an
71 # optional anchor control where each block appears in the generated schedule.
72 raw_text_events = [
73 event for event in document.schedule_events if event.event_type == "RAW_TEXT"
74 ]
75 for event in raw_text_events:
76 print(
77 f" RAW_TEXT: {event.raw_placement} {event.raw_anchor or ''} "
78 f"(priority {event.raw_priority})"
79 )
80
81 # The sample file uses FILTER declarations, so a case is needed to resolve
82 # the referenced result names and to own the created combined filters.
83 cases = project.cases()
84 if not cases:
85 print(
86 "\nNo Eclipse case loaded - the sample file uses FILTER, "
87 "which needs a case. Load a case and rerun."
88 )
89 return
90 case = cases[0]
91
92 # Apply the parsed document to the shared well event timeline.
93 print("\n2. Applying events to the timeline...")
94 well_path_coll = project.descendants(rips.WellPathCollection)[0]
95 timeline = well_path_coll.event_timeline()
96
97 report = rips.orion_events.apply_orion_document(
98 document, timeline, project, case=case, on_unknown_well="warn"
99 )
100 print(f" Events applied: {report.events_applied}")
101 print(f" Events skipped: {report.events_skipped}")
102 print(f" Report dates: {report.report_dates}")
103 for warning in report.warnings:
104 print(f" WARNING: {warning}")
105 for error in report.errors:
106 print(f" ERROR: {error}")
107
108 # FILTER declarations referenced by applied perforations now exist as
109 # combined data filters under the case's "Data Filters" node; they are
110 # carried onto the perforation intervals when completions are materialized
111 # with timeline.set_timestamp().
112 print("\n3. Case-level data filters created from FILTER declarations:")
113 data_filters = case.data_filter_collection().filters()
114 if data_filters:
115 for cell_filter in data_filters:
116 print(f" {cell_filter.name}")
117 else:
118 print(" (none - no applied perforation referenced a filter)")
119
120 # Generate Eclipse schedule text from the timeline.
121 print("\n4. Generating Eclipse schedule text...")
122 if report.events_applied == 0 and not report.report_dates:
123 print(" No events or report dates found - skipping schedule generation.")
124 return
125 schedule_text = timeline.generate_schedule_text(
126 eclipse_case=case,
127 export_msw_for_wells=project.well_paths(),
128 additional_dates=report.report_dates,
129 align_columns=True,
130 )
131 if schedule_text:
132 print(f" Generated schedule text ({len(schedule_text)} characters):")
133 print(" " + "=" * 60)
134 for line in schedule_text.split("\n"):
135 print(f" {line}")
136 print(" " + "=" * 60)
137 else:
138 print(" No schedule text generated.")
139
140 print("\nExample completed.")
141
142
143if __name__ == "__main__":
144 main()
Well Event Schedule Orion
well_event_schedule_orion.py
1#!/usr/bin/env python3
2
3"""
4Example: the well_event_schedule.py timeline expressed as an ORIONEVENTS file.
5
6This is the ORIONEVENTS counterpart to well_event_schedule.py: instead of
7calling the WellEventTimeline API methods one by one, the same events are
8written as ORIONEVENTS 2.0 text (see rips/orion_events.py for the grammar) and
9applied in one go with rips.orion_events.apply_orion_document().
10
11It demonstrates the full event coverage of the format:
121. SEGMENT, PERFORATION (incl. a time-of-day date), VALVE and STATE completion
13 events on a well
142. Partial WELSPECS updates that cumulatively change completion export settings
15 and generate dated WELSPECS records
163. A FILTER declaration (qualified result name) referenced by a perforation,
17 materialized as a case-level combined data filter
184. COMMENT attributes preserved on timeline events and emitted before their
19 generated schedule keywords
205. Same-owner/type/date WCONHIST lines merged with conflict diagnostics, and
21 historical keyword values carried forward to later partial events, while
22 same-date perforations remain separate
236. Well keyword events: WCONHIST and WELTARG (with attribute translation) and
24 WRFTPLT (generic Eclipse well keyword pass-through)
257. A GROUP-level MEMBER event expanded to one GRUPTREE record per member
268. SCHEDULE-level keyword events not tied to a well: RPTRST, GRUPTREE, TUNING
279. Multiline RAW_TEXT inserted at a chosen position without parsing its contents
2810. Recurring REPORT dates with explicit and implicit end dates, passed to
29 generate_schedule_text(additional_dates=...) as summary-report triggers
3011. Schedule metadata, COMPORD generation and aligned-column output
31
32The ORIONEVENTS text is built inline with the name of the first well path in
33the project (like well_event_schedule.py, which uses wells[0]), so the example
34works with any project that has at least one well path. Applying a FILTER needs
35a loaded Eclipse case (to resolve the result name and own the created filter),
36so the FILTER parts are included only when the project has a case.
37"""
38
39import rips
40import rips.orion_events
41
42
43def build_orion_text(well_name, with_filter):
44 # 'static.PORO' restricts the result lookup to STATIC_NATIVE results; an
45 # unqualified name would search STATIC_NATIVE, DYNAMIC_NATIVE, GENERATED.
46 filter_decl = 'FILTER HIPORO = "static.PORO > 0.15"\n' if with_filter else ""
47 filter_comment = (
48 "\n # The first one is restricted to cells passing the HIPORO filter."
49 if with_filter
50 else ""
51 )
52 filter_ref = " FILTER=HIPORO" if with_filter else ""
53 return f"""\
54ORIONEVENTS 2.0
55UNIT METRIC
56
57# Typed declarations
58DATE STARTUP = 2024-01-01
59DURATION RAMP = 31 DAYS
60{filter_decl}
61WELL W1 = "{well_name}"
62
63WELL W1
64 # WELSPECS updates completion export settings and emits WELSPECS. Attributes
65 # are optional: the second event inherits GROUP from the first event.
66 @2024-01-05 WELSPECS GROUP="ORION_GROUP" CROSSFLOW=True REFDEPTH=1002 PHASE=WATER
67 @2024-04-15 WELSPECS CROSSFLOW=False REFDEPTH=1000 PHASE=OIL
68
69 # COMMENT is stored on the event and safely emitted as a schedule comment.
70 @STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5 PRESSURE_COMPONENTS=HFA COMMENT="Install production segment"
71
72 # Perforations; COMPLETION_NUMBER groups connections for COMPLUMP. Same-date
73 # perforations are kept as separate events during normalization.{filter_comment}
74 @STARTUP + RAMP PERFORATION MDSTART=2000 MDEND=2200 RADIUS=0.05 SKIN=0.5 COMPLETION_NUMBER=1{filter_ref} COMMENT="Open high-priority interval"
75 @STARTUP + RAMP PERFORATION MDSTART=2400 MDEND=2600 RADIUS=0.05 SKIN=0.3 COMPLETION_NUMBER=2
76
77 # Time-of-day is preserved and emitted as the TIME field of DATES
78 @2024-05-15T14:45:30.500 PERFORATION MDSTART=2300 MDEND=2350 RADIUS=0.05 SKIN=0.4 COMPLETION_NUMBER=3
79
80 # Valve in the first perforation; state event for documentation
81 @2024-03-01 VALVE MD=2100 TYPE=ICV STATE=OPEN CV=0.7 AREA=0.0001
82 @2024-02-15 STATE STATE=OPEN
83
84 # Matching owner/type/date lines merge. The second line extends the first.
85 # Conflicting GRAT values produce a warning, and the later value wins.
86 @2024-01-15 WCONHIST STATUS=OPEN CMODE=RESV GRAT=4756545.5 COMMENT="Start production history controls"
87 @2024-01-15 WCONHIST ORAT=3999.99 WRAT=0.01 GRAT=550678.44 VFP=1
88
89 # Later partial keyword events inherit historical values for the same well
90 # and keyword. This event overrides WRAT and inherits STATUS, CMODE, ORAT,
91 # GRAT and VFP from January 15; COMMENT is event-local and is not inherited.
92 @2024-01-20 WCONHIST WRAT=0.03
93
94 # WRFTPLT is passed through as a generic Eclipse keyword.
95 @2024-05-01 WELTARG CMODE=ORAT VALUE=5000.0
96 @2024-06-01 WRFTPLT OUTPUT_RFT=YES OUTPUT_PLT=NO OUTPUT_SEGMENT=NO
97
98# MEMBER expands into one GRUPTREE record per unique comma-delimited member.
99GROUP "OP"
100 @STARTUP MEMBER MEMBERS="{well_name},OBSERVER" COMMENT="Define operating group members"
101
102# Schedule-level keywords (not tied to a well)
103SCHEDULE
104 @STARTUP RPTRST BASIC=2 FREQ=1
105 @STARTUP GRUPTREE CHILD_GROUP=OP PARENT_GROUP=FIELD
106 @STARTUP TUNING TSINIT=1 TSMAXZ=30 TMAXWC=1 NEWTMX=12 NEWTMN=1 LITMAX=50 LITMIN=1 MXWSIT=50 MXWPIT=50
107
108 # RAW_TEXT preserves its body verbatim. This block is emitted after RPTRST;
109 # PRIORITY orders multiple raw blocks sharing the same placement and anchor.
110 @STARTUP RAW_TEXT PLACEMENT=AFTER_KEYWORD ANCHOR=RPTRST PRIORITY=10
111-- Custom schedule text not modeled by the timeline API
112WTRACER
113 '{well_name}' 'ORION_TRACER' 1.0 /
114/
115END_RAW_TEXT
116
117# Recurring report dates become bare DATES keywords. The first series ends at
118# the last @ event; the second uses an explicit inclusive end date.
119REPORT STARTUP EVERY MONTH
120REPORT 2024-07-01 EVERY 3 MONTHS UNTIL STARTUP + 365
121"""
122
123
124def main():
125 resinsight = rips.Instance.find()
126 project = resinsight.project
127
128 print("Well Event Schedule (ORIONEVENTS) Example")
129 print("=" * 50)
130
131 print("\n1. Finding well")
132 wells = project.well_paths()
133 if not wells:
134 print(" No well paths in project - load a project with wells first.")
135 return
136 well_path = wells[0]
137 print(" Well name:", well_path.name)
138
139 # A FILTER needs a case; without one, build the text without the filter.
140 cases = project.cases()
141 case = cases[0] if cases else None
142 if case is None:
143 print(" No Eclipse case loaded - FILTER parts are left out.")
144
145 print("\n2. Parsing ORIONEVENTS text...")
146 orion_text = build_orion_text(well_path.name, with_filter=case is not None)
147 print(orion_text)
148 document = rips.orion_events.parse_orion_events(orion_text)
149 print(f" Wells: {[w.well_name for w in document.wells]}")
150 source_well_event_count = sum(len(w.events) for w in document.wells)
151 source_perforation_count = sum(
152 event.event_type == "PERFORATION"
153 for well in document.wells
154 for event in well.events
155 )
156 normalized = rips.orion_events.coalesce_orion_document(document)
157 merged_well_event_count = sum(len(w.events) for w in normalized.wells)
158 merged_perforation_count = sum(
159 event.event_type == "PERFORATION"
160 for well in normalized.wells
161 for event in well.events
162 )
163 print(f" Source well-event lines: {source_well_event_count}")
164 print(f" Events after same-date merge: {merged_well_event_count}")
165 print(
166 " Perforations retained during merge: "
167 f"{source_perforation_count} -> {merged_perforation_count}"
168 )
169 print(f" Groups: {[group.group_name for group in document.groups]}")
170 print(f" Schedule events: {len(document.schedule_events)}")
171
172 print("\n3. Applying events to the timeline...")
173 well_path_coll = project.descendants(rips.WellPathCollection)[0]
174 timeline = well_path_coll.event_timeline()
175 report = rips.orion_events.apply_orion_document(
176 document, timeline, project, case=case
177 )
178 print(f" Events applied: {report.events_applied}")
179 print(f" Events skipped: {report.events_skipped}")
180 print(f" Report dates: {report.report_dates}")
181 for warning in report.warnings:
182 print(f" WARNING: {warning}")
183 for error in report.errors:
184 print(f" ERROR: {error}")
185
186 # Apply events up to a date to materialize completions
187 timeline.set_timestamp(timestamp="2024-12-24")
188
189 print("\n4. Verifying created completions and WELSPECS settings...")
190 completion_settings = well_path.completion_settings()
191 print(" Completion export settings after the latest WELSPECS:")
192 print(f" Group: {completion_settings.group_name_for_export}")
193 print(f" Cross-flow: {completion_settings.allow_well_cross_flow}")
194 print(f" Ref. depth: {completion_settings.reference_depth_for_export}")
195 print(f" Phase: {completion_settings.well_type_for_export}")
196
197 perforations = well_path.completions().perforations().perforations()
198 print(f" Perforations created: {len(perforations)}")
199 for perf in perforations:
200 # The HIPORO filter was carried from the perforation event onto the
201 # materialized perforation interval.
202 cell_filter = perf.cell_filter()
203 filter_note = f" (filter: {cell_filter.name})" if cell_filter else ""
204 print(
205 f" - MD {perf.start_measured_depth:.0f} to "
206 f"{perf.end_measured_depth:.0f}m{filter_note}"
207 )
208
209 print("\n5. Generating Eclipse schedule text from events...")
210 if case is None:
211 print(" No Eclipse case loaded - skipping schedule generation.")
212 return
213 # REPORT dates become bare DATES keywords via additional_dates. Aligned output
214 # adds column-title comments; the schedule header identifies its timestamp and
215 # user, and each generated WELSPECS record has a matching COMPORD INPUT record.
216 schedule_text = timeline.generate_schedule_text(
217 eclipse_case=case,
218 export_msw_for_wells=[well_path],
219 additional_dates=report.report_dates,
220 align_columns=True,
221 )
222 if schedule_text:
223 print(f" Generated schedule text ({len(schedule_text)} characters)")
224 print(" " + "=" * 60)
225 for line in schedule_text.split("\n"):
226 print(f" {line}")
227 print(" " + "=" * 60)
228
229 expected_keywords = [
230 "DATES",
231 "WELSPECS",
232 "COMPORD",
233 "COMPDAT",
234 "COMPLUMP",
235 "WCONHIST",
236 "WELTARG",
237 "WRFTPLT",
238 "RPTRST",
239 "GRUPTREE",
240 "TUNING",
241 "WTRACER",
242 ]
243 found = [kw for kw in expected_keywords if kw in schedule_text]
244 print(f"\n Keywords found: {', '.join(found)}")
245 if "14:45:30.500" in schedule_text:
246 print(" DATES keyword preserves event time-of-day (14:45:30.500)")
247 else:
248 print(" Warning: No schedule text was generated")
249
250 print("\nExample completed.")
251
252
253if __name__ == "__main__":
254 main()