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()