The OutReader module provides tools for extracting data from CMG .out output files, including well index data and connection information.
The OutReader module can:
- Detect simulator type (IMEX, GEM, STARS)
- Extract well index (WI) data from output files
- Parse connection information
- Support both IMEX and GEM formats
Detect the type of CMG simulator from output file:
from rsimpy.cmg.outreader import utils
file_type = utils.get_file_type("simulation.out")
print(f"Simulator type: {file_type}")
# Returns: 'GEM', 'IMEX', or 'STARS'Parameters:
- file_path (
strorPath): Path to .out file
Returns: str - Simulator type ('GEM', 'IMEX', 'STARS')
Raises:
FileNotFoundError: If file doesn't existValueError: If file format is not recognized
Example:
from rsimpy.cmg.outreader import utils
try:
file_type = utils.get_file_type("test.out")
if file_type == 'GEM':
print("Compositional simulation")
elif file_type == 'IMEX':
print("Black oil simulation")
elif file_type == 'STARS':
print("Thermal simulation")
except FileNotFoundError:
print("Output file not found")
except ValueError:
print("Unrecognized file format")Extract well index data from output files.
Constructor:
from rsimpy.cmg.outreader import wi
out_wi = wi.OutWI(
verbose=False,
wi_only=True,
encoding='utf-8'
)Parameters:
- verbose (
bool, optional): Print progress messages (default: False) - wi_only (
bool, optional): Extract only well index data (default: True) - encoding (
str, optional): File encoding (default: 'utf-8')
process(file_path, prune=True)
Process output file to extract WI data:
out_wi.process("simulation.out", prune=False)Parameters:
- file_path (
strorPath): Path to output file - prune (
bool, optional): Keep only last timestep (default: True)
get()
Retrieve extracted well index data:
wi_data = out_wi.get()
# Returns: dict with structure {(date, days): {well_name: {'con_data': [...]}}}get_table()
Get well index data as pandas DataFrame:
df = out_wi.get_table()
# Returns: DataFrame with columns for well, date, connection data, etc.prune()
Keep only the last timestep data:
out_wi.prune()from rsimpy.cmg.outreader import utils
# Check multiple output files
files = [
"gem_simulation.out",
"imex_simulation.out",
"stars_simulation.out"
]
for file in files:
try:
sim_type = utils.get_file_type(file)
print(f"{file}: {sim_type}")
except Exception as e:
print(f"{file}: Error - {e}")from rsimpy.cmg.outreader import wi
# Create WI extractor
out_wi = wi.OutWI(verbose=True, wi_only=True, encoding='utf-8')
# Process GEM output file
out_wi.process("gem_simulation.out", prune=False)
# Get data
wi_data = out_wi.get()
print(f"Found {len(wi_data)} timesteps")
# Access specific timestep
for (date, days), wells in wi_data.items():
print(f"\nTimestep: {date} (day {days})")
for well_name, well_data in wells.items():
n_connections = len(well_data['con_data'])
print(f" {well_name}: {n_connections} connections")
break # Just show first timestep
# Prune to keep only last timestep
out_wi.prune()
print(f"After pruning: {len(out_wi.get())} timesteps")from rsimpy.cmg.outreader import wi
import pandas as pd
import matplotlib.pyplot as plt
# Extract WI data
out_wi = wi.OutWI(verbose=False, wi_only=True)
out_wi.process("imex_simulation.out", prune=False)
# Get as DataFrame
df = out_wi.get_table()
print("Well Index Data Summary:")
print(f" Total records: {len(df)}")
print(f" Wells: {df['well'].nunique()}")
print(f" Unique wells: {df['well'].unique()}")
# Analyze by well
well_summary = df.groupby('well').size()
print("\nConnections per well:")
print(well_summary)
# Plot connection count evolution (if multiple timesteps)
if 'date' in df.columns or 'days' in df.columns:
time_col = 'days' if 'days' in df.columns else 'date'
conn_evolution = df.groupby([time_col, 'well']).size().reset_index(name='count')
fig, ax = plt.subplots(figsize=(12, 6))
for well in conn_evolution['well'].unique()[:5]: # First 5 wells
well_data = conn_evolution[conn_evolution['well'] == well]
ax.plot(well_data[time_col], well_data['count'], marker='o', label=well)
ax.set_xlabel('Time')
ax.set_ylabel('Number of Connections')
ax.set_title('Connection Count Evolution')
ax.legend()
ax.grid(True)
plt.savefig('wi_evolution.png')from rsimpy.cmg.outreader import wi
# Extract from multiple cases
cases = {
'Base': 'base_case.out',
'Optimized': 'optimized_case.out',
'Sensitivity': 'sensitivity_case.out'
}
wi_comparison = {}
for case_name, file_path in cases.items():
out_wi = wi.OutWI(verbose=False, wi_only=True)
out_wi.process(file_path, prune=True) # Keep only last timestep
wi_comparison[case_name] = out_wi.get_table()
# Compare connection counts
for case_name, df in wi_comparison.items():
print(f"\n{case_name}:")
print(f" Total connections: {len(df)}")
print(f" Wells: {df['well'].nunique()}")
# Wells with most connections
top_wells = df['well'].value_counts().head(5)
print(f" Top 5 wells by connections:")
for well, count in top_wells.items():
print(f" {well}: {count}")from rsimpy.cmg.outreader import wi
import pandas as pd
# Extract WI data
out_wi = wi.OutWI(verbose=True)
out_wi.process("simulation.out", prune=False)
# Get as DataFrame
df = out_wi.get_table()
# Export to CSV
df.to_csv("well_index_data.csv", index=False)
print("Exported WI data to CSV")
# Export summary statistics
summary = df.groupby('well').agg({
'well': 'count' # Connection count
}).rename(columns={'well': 'connection_count'})
summary.to_csv("wi_summary.csv")
print("Exported summary to CSV")
# If you have connection detail columns, you can analyze them
if 'con_data' in df.columns:
# Process connection data
# (actual structure depends on what OutWI extracts)
passfrom rsimpy.cmg.outreader import wi
from pathlib import Path
import time
def monitor_simulation(out_file, interval=60):
"""Monitor simulation progress by checking WI in output file"""
out_wi = wi.OutWI(verbose=False, wi_only=True)
last_size = 0
while True:
out_path = Path(out_file)
if not out_path.exists():
print(f"Waiting for {out_file}...")
time.sleep(interval)
continue
current_size = out_path.stat().st_size
if current_size > last_size:
try:
out_wi.process(out_file, prune=True)
wi_data = out_wi.get()
if wi_data:
latest = list(wi_data.keys())[-1]
date, days = latest
n_wells = len(wi_data[latest])
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] "
f"Day {days:.1f} ({date}): {n_wells} wells")
last_size = current_size
except Exception as e:
print(f"Error processing file: {e}")
time.sleep(interval)
# Usage (run in separate script or background)
# monitor_simulation("simulation.out", interval=30)The well index data returned by get() has the following structure:
{
(date_str, days_float): {
'WELL-01': {
'con_data': [
# Connection data list
# Structure depends on simulator type
]
},
'WELL-02': {...},
...
},
...
}The DataFrame returned by get_table() typically includes:
well: Well namedate: Date string (if available)days: Simulation days (if available)- Additional columns depend on simulator type and connection data
- Large output files can take time to process
- Use
prune=Trueif you only need the final timestep - The
wi_onlyoption focuses on well index data, making processing faster - Consider processing in chunks for very large files
- Focused primarily on well index extraction
- Support for IMEX and GEM formats (STARS support may be limited)
- Output file must be well-formed and follow CMG format conventions
- Getting Started
- DatReader Module - For input files
- SR3Reader Module - For results files
- Quick Examples