Files
ResInsight/GrpcInterface/Python/rips/well_events.py
T
Kristian Bendiksen a4dad97024 #13425 Add time-dependent well events and schedule data generation
Implement timeline-based event system for managing temporal changes in
well completions, perforations, valves, and production controls.

Features:
- Event timeline container with date-based querying
- Six event types: PERF, VALVE, TUBING, WSTATE, WTYPE, WCONTROL
- Schedule keyword generation (DATES, COMPDAT, WELSEGS, WCONPROD, WCONINJE)
- Multi-well schedule generation support
- Python GRPC API for event management
- YAML configuration file support
- Comprehensive test suite

Implementation:
- New directory: ApplicationLibCode/ProjectDataModel/WellEvents/
- Event classes inherit from RimWellEvent base class
- RimWellEventTimeline integrated into RimWellPath
- RicScheduleDataGenerator for multi-well schedule export
- Python API: event_timeline(), add_perf_event(), add_valve_event(), etc.
- Tests verify all event types and YAML config parsing

#13425 Add date tracking to valves and diameter/roughness intervals

Add creation date fields to RimWellPathValve and RimDiameterRoughnessInterval
to support filtering objects by date during completion data export.

- Add m_useCustomStartDate and m_startDate fields
- Add enableCustomStartDate(), setCustomStartDate(), isActiveOnDate() methods
- Set creation dates when applying valve and tubing events in RimWellEventTimeline

#13425 Add date-based filtering for schedule data export

Add optional export date parameter to MSW data collection functions to filter
valves and perforations based on their creation dates during schedule export.

- Add exportDate parameter to collectWsegvalvData, collectWsegAicdData,
  collectWsegSicdData, and collectCompsegData functions
- Filter valves by checking isActiveOnDate() on the associated RimWellPathValve
- Add RimPerforationInterval reference to RicMswPerforation for date filtering
- Pass export date from RicScheduleDataGenerator through the extraction chain

This ensures segment numbers reflect the full model while only including
objects active on the export date in output keywords.

Add test to prove compsegs changes over time.

#13425 Fix mypy type checking errors in well_events.py

- Add type annotations to all function parameters
- Fix generic type hints (dict -> Dict[str, Any])
- Import Any type for proper type safety
- Add type: ignore for method reassignment
- All 31 tests pass

#13425 Add schedule-level keyword events for non-well-specific keywords

Add a new event type called "Keyword Event" for schedule-level Eclipse
keywords (RPTRST, GRUPTREE, RPTSCHED, etc.) that are NOT tied to a
specific well path. This complements the existing RimWellEventKeyword
which operates at the well level.

- Add RimKeywordEvent class inheriting from RimWellEvent with null wellPath
- Add SCHEDULE_KEYWORD enum value to EventType
- Add addKeywordEvent() method to RimWellEventTimeline
- Add AddKeywordEvent Python API method to RimcWellEventTimeline
- Update RicScheduleDataGenerator to output global keywords after well sections
- Add Python wrapper add_keyword_event() with type inference
- Add tests and examples for RPTRST, GRUPTREE keywords
2026-02-12 08:15:22 +01:00

321 lines
9.8 KiB
Python

"""
Well Events module for managing time-dependent well events.
This module provides functionality for creating and managing well events
in a timeline-based event system. Events can be perforation events, valve events,
tubing changes, well state changes, and production/injection control changes.
"""
from typing import Any, Dict
from datetime import date, datetime
from .pdmobject import add_method
from .resinsight_classes import Case
from .generated.generated_classes import (
KeywordEvent,
WellEventKeyword,
WellEventTimeline,
)
def _format_date(event_date: str | date | datetime) -> str:
"""Convert date to ISO format string (YYYY-MM-DD)."""
if isinstance(event_date, str):
return event_date
elif isinstance(event_date, datetime):
return event_date.strftime("%Y-%m-%d")
elif isinstance(event_date, date):
return event_date.strftime("%Y-%m-%d")
else:
raise TypeError(
f"event_date must be a string, date, or datetime, not {type(event_date)}"
)
@add_method(WellEventTimeline)
def add_well_keyword_event(
self: WellEventTimeline,
event_date: str | date | datetime,
well_path: Any,
keyword_name: str,
keyword_data: Dict[str, Any],
) -> WellEventKeyword:
"""Add a well keyword event with arbitrary keyword data.
This is a convenience method that automatically infers types from Python
values and calls the underlying GRPC method with parallel arrays.
Type inference rules:
- str → STRING
- int → INT
- float → DOUBLE
- bool → INT (1 or 0)
Arguments:
event_date: Date string in YYYY-MM-DD format, date, or datetime object
well_path (WellPath): The well path object
keyword_name (str): Keyword name (e.g., "WCONHIST", "WELTARG", "WRFTPLT")
keyword_data (dict): Dictionary mapping keyword item names to values
Returns:
WellEventKeyword: The created keyword event object
Raises:
TypeError: If keyword_data contains unsupported value types
Example:
```python
# Get the timeline
well_path = project.well_paths()[0]
timeline = well_path.event_timeline()
# Add WCONHIST - Historical production data
timeline.add_well_keyword_event(
event_date="2018-04-01",
well_path=well_path,
keyword_name="WCONHIST",
keyword_data={
"WELL": well_path.name,
"STATUS": "OPEN",
"CMODE": "RESV",
"ORAT": 3999.98999,
"WRAT": 0.01,
"GRAT": 550678.438,
"VFP_TABLE": 1
}
)
# Add WELTARG - Change target
timeline.add_well_keyword_event(
event_date="2018-05-01",
well_path=well_path,
keyword_name="WELTARG",
keyword_data={
"WELL": well_path.name,
"CMODE": "ORAT",
"NEW_VALUE": 5000.0
}
)
# Add WRFTPLT - Enable RFT output
timeline.add_well_keyword_event(
event_date="2018-06-01",
well_path=well_path,
keyword_name="WRFTPLT",
keyword_data={
"WELL": well_path.name,
"OUTPUT_RFT": "YES",
"OUTPUT_PLT": "NO"
}
)
# Generate schedule
case = project.cases()[0]
schedule_text = timeline.generate_schedule_text(eclipse_case=case)
print(schedule_text)
```
"""
# Type inference and conversion
item_names = []
item_types = []
item_values = []
for name, value in keyword_data.items():
item_names.append(name)
if isinstance(value, str):
item_types.append("STRING")
item_values.append(value)
elif isinstance(value, bool):
# Handle bool before int (bool is subclass of int in Python)
item_types.append("INT")
item_values.append("1" if value else "0")
elif isinstance(value, int):
item_types.append("INT")
item_values.append(str(value))
elif isinstance(value, float):
item_types.append("DOUBLE")
item_values.append(str(value))
else:
raise TypeError(
f"Unsupported type for keyword item '{name}': {type(value).__name__}. "
"Supported types: str, int, float, bool"
)
# Format date
date_str = _format_date(event_date)
# Call internal GRPC method with parallel arrays
return self.add_well_keyword_event_internal(
event_date=date_str,
well_path=well_path,
keyword_name=keyword_name,
item_names=item_names,
item_types=item_types,
item_values=item_values,
)
@add_method(WellEventTimeline)
def add_keyword_event(
self: WellEventTimeline,
event_date: str | date | datetime,
keyword_name: str,
keyword_data: Dict[str, Any],
) -> KeywordEvent:
"""Add a schedule-level keyword event (not tied to a specific well path).
This is for global schedule keywords like RPTRST, GRUPTREE, RPTSCHED, etc.
that apply to the entire simulation rather than a specific well.
Type inference rules:
- str → STRING
- int → INT
- float → DOUBLE
- bool → INT (1 or 0)
Arguments:
event_date: Date string in YYYY-MM-DD format, date, or datetime object
keyword_name (str): Keyword name (e.g., "RPTRST", "GRUPTREE", "RPTSCHED")
keyword_data (dict): Dictionary mapping keyword item names to values
Returns:
KeywordEvent: The created keyword event object
Raises:
TypeError: If keyword_data contains unsupported value types
Example:
```python
# Get the timeline
well_path_coll = project.descendants(rips.WellPathCollection)[0]
timeline = well_path_coll.event_timeline()
# Add RPTRST - Report restart settings (schedule-level, not well-specific)
timeline.add_keyword_event(
event_date="2024-01-01",
keyword_name="RPTRST",
keyword_data={
"BASIC": 2,
"FREQ": 1,
}
)
# Add GRUPTREE - Group tree definition
timeline.add_keyword_event(
event_date="2024-01-01",
keyword_name="GRUPTREE",
keyword_data={
"CHILD": "OP",
"PARENT": "FIELD",
}
)
# Add RPTSCHED - Report schedule settings
timeline.add_keyword_event(
event_date="2024-01-01",
keyword_name="RPTSCHED",
keyword_data={
"FIP": 1,
"WELLS": 2,
}
)
```
"""
# Type inference and conversion
item_names = []
item_types = []
item_values = []
for name, value in keyword_data.items():
item_names.append(name)
if isinstance(value, str):
item_types.append("STRING")
item_values.append(value)
elif isinstance(value, bool):
# Handle bool before int (bool is subclass of int in Python)
item_types.append("INT")
item_values.append("1" if value else "0")
elif isinstance(value, int):
item_types.append("INT")
item_values.append(str(value))
elif isinstance(value, float):
item_types.append("DOUBLE")
item_values.append(str(value))
else:
raise TypeError(
f"Unsupported type for keyword item '{name}': {type(value).__name__}. "
"Supported types: str, int, float, bool"
)
# Format date
date_str = _format_date(event_date)
# Call internal GRPC method with parallel arrays
return self.add_keyword_event_internal(
event_date=date_str,
keyword_name=keyword_name,
item_names=item_names,
item_types=item_types,
item_values=item_values,
)
@add_method(WellEventTimeline)
def generate_schedule_text(self: WellEventTimeline, eclipse_case: Case) -> str:
"""Generate Eclipse schedule text for all wells in the collection.
The timeline is shared across all wells in the well path collection.
This method generates schedule data for all wells that have events in
the timeline.
This is a convenience wrapper around generate_schedule() that returns the
text directly instead of a DataContainerString.
Arguments:
eclipse_case (Case): Eclipse case to use for schedule generation.
Returns:
str: Eclipse schedule text containing DATES, COMPDAT, WELSEGS, WCONPROD, etc.
for all wells in the collection.
Example:
```python
# Get the timeline (shared across all wells)
well_path = project.well_paths()[0]
timeline = well_path.event_timeline()
# Add events for multiple wells
timeline.add_perf_event(
event_date="2024-01-01",
well_name="WELL-1",
start_md=1000,
end_md=1500,
diameter=0.1,
skin_factor=0.5,
state="OPEN"
)
timeline.add_perf_event(
event_date="2024-02-01",
well_name="WELL-2",
start_md=2000,
end_md=2500,
diameter=0.1,
state="OPEN"
)
# Generate schedule text for all wells
case = project.cases()[0]
schedule_text = timeline.generate_schedule_text(eclipse_case=case)
print(schedule_text)
```
"""
container = self.generate_schedule(eclipse_case_id=eclipse_case.id)
if container and container.values:
# Workaround: Concatenate all values in case the schedule text
# was split by comma parsing in the gRPC layer
return "".join(container.values)
return ""