Python package for reading and writing reservoir simulator result files including RESTART, INIT, RFT, Summary and GRID files in various formats.
—
Comprehensive 3D grid processing and analysis capabilities for reservoir simulation grids. Supports corner-point grids, structured grids, Local Grid Refinement (LGR), and advanced region selection operations.
Main grid class providing access to 3D reservoir grid structure, cell properties, and spatial operations.
class Grid:
"""3D reservoir grid operations and analysis."""
def __init__(self, filename: str):
"""
Load grid from GRID or EGRID file.
Args:
filename (str): Path to grid file (.GRID or .EGRID)
"""
def save_EGRID(self, filename: str):
"""Save grid in EGRID format."""
def get_name(self) -> str:
"""Get grid name."""
def get_cell_dims(self) -> tuple:
"""
Get grid dimensions.
Returns:
tuple: (nx, ny, nz) grid dimensions
"""
def get_cell_volume(self, i: int, j: int, k: int) -> float:
"""
Get cell volume.
Args:
i, j, k (int): Cell indices
Returns:
float: Cell volume in cubic meters
"""
def get_cell_corner(self, i: int, j: int, k: int, corner: int) -> tuple:
"""
Get cell corner coordinates.
Args:
i, j, k (int): Cell indices
corner (int): Corner index (0-7)
Returns:
tuple: (x, y, z) coordinates
"""
def active_index(self, i: int, j: int, k: int) -> int:
"""
Get active index for cell.
Args:
i, j, k (int): Cell indices
Returns:
int: Active index or -1 if inactive
"""
def global_index(self, i: int, j: int, k: int) -> int:
"""Get global index for cell."""
def get_xyz(self, i: int, j: int, k: int) -> tuple:
"""
Get cell center coordinates.
Args:
i, j, k (int): Cell indices
Returns:
tuple: (x, y, z) center coordinates
"""
def get_corner_xyz(self, corner: int) -> tuple:
"""Get corner coordinates for all cells."""
def grid_value(self, x: float, y: float, z: float) -> tuple:
"""
Find cell containing point.
Args:
x, y, z (float): World coordinates
Returns:
tuple: (i, j, k) indices or None if outside
"""
def depth(self, i: int, j: int, k: int) -> float:
"""Get cell depth (negative Z)."""
def cell_invalid(self, i: int, j: int, k: int) -> bool:
"""Check if cell is invalid."""
def cell_valid(self, i: int, j: int, k: int) -> bool:
"""Check if cell is valid."""
def get_active_index(self, i: int, j: int, k: int) -> int:
"""Get active index (raises exception if inactive)."""
def try_get_global_index(self, i: int, j: int, k: int) -> int:
"""Try to get global index, returns -1 if invalid."""
def get_global_index(self, i: int, j: int, k: int) -> int:
"""Get global index (raises exception if invalid)."""
def get_ijk(self, global_index: int) -> tuple:
"""
Get IJK indices from global index.
Args:
global_index (int): Global cell index
Returns:
tuple: (i, j, k) indices
"""
def get_xyz_from_ijk(self, i: int, j: int, k: int) -> tuple:
"""Get XYZ coordinates from IJK indices."""
def distance(self, i1: int, j1: int, k1: int, i2: int, j2: int, k2: int) -> float:
"""Calculate distance between two cells."""
def num_active(self) -> int:
"""Get number of active cells."""
def get_num_active(self) -> int:
"""Get number of active cells (alias)."""
def get_num_lgr(self) -> int:
"""Get number of Local Grid Refinements."""
def wells(self) -> list:
"""Get list of wells intersecting the grid."""
def grid_attach(self, lgr_grid):
"""Attach Local Grid Refinement."""
def grid_get_lgr(self, lgr_name: str):
"""Get Local Grid Refinement by name."""Access to individual cell properties and metadata.
class Cell:
"""Individual grid cell representation."""
@property
def active(self) -> bool:
"""Check if cell is active."""
@property
def i(self) -> int:
"""I-index of cell."""
@property
def j(self) -> int:
"""J-index of cell."""
@property
def k(self) -> int:
"""K-index of cell."""
@property
def global_index(self) -> int:
"""Global index of cell."""
@property
def volume(self) -> float:
"""Volume of cell."""
@property
def dimensions(self) -> tuple:
"""Cell dimensions (dx, dy, dz)."""Advanced region selection capabilities for creating masks based on various criteria.
class ResdataRegion:
"""Region selection and masking operations."""
def __init__(self, grid: Grid, preselect: bool = True):
"""
Create region selector.
Args:
grid (Grid): Grid to operate on
preselect (bool): Whether to preselect all cells
"""
def select_equal(self, kw: ResdataKW, value: float):
"""Select cells where keyword equals value."""
def select_less(self, kw: ResdataKW, value: float):
"""Select cells where keyword is less than value."""
def select_more(self, kw: ResdataKW, value: float):
"""Select cells where keyword is greater than value."""
def select_in_range(self, kw: ResdataKW, min_value: float, max_value: float):
"""Select cells where keyword is in range."""
def select_active(self):
"""Select only active cells."""
def select_inactive(self):
"""Select only inactive cells."""
def select_small(self, kw: ResdataKW, num_cells: int):
"""Select cells with smallest keyword values."""
def select_large(self, kw: ResdataKW, num_cells: int):
"""Select cells with largest keyword values."""
def select_thin(self, kw: ResdataKW, num_cells: int):
"""Select thinnest cells based on keyword."""
def select_thick(self, kw: ResdataKW, num_cells: int):
"""Select thickest cells based on keyword."""
def select_box(self, i1: int, i2: int, j1: int, j2: int, k1: int, k2: int):
"""
Select cells in box region.
Args:
i1, i2 (int): I-index range (inclusive)
j1, j2 (int): J-index range (inclusive)
k1, k2 (int): K-index range (inclusive)
"""
def select_islice(self, i1: int, i2: int):
"""Select I-slice of cells."""
def select_jslice(self, j1: int, j2: int):
"""Select J-slice of cells."""
def select_kslice(self, k1: int, k2: int):
"""Select K-slice of cells."""
def invert(self):
"""Invert current selection."""
def copy(self) -> ResdataRegion:
"""Create copy of region."""
def reset(self):
"""Reset selection to all cells."""Utilities for creating synthetic grids for testing and modeling.
class GridGenerator:
"""Grid generation utilities."""
@classmethod
def create_rectangular(cls, nx: int, ny: int, nz: int,
dx: float, dy: float, dz: float) -> Grid:
"""
Create rectangular grid.
Args:
nx, ny, nz (int): Grid dimensions
dx, dy, dz (float): Cell sizes
Returns:
Grid: Generated rectangular grid
"""
@classmethod
def create_GRDECL(cls, nx: int, ny: int, nz: int,
coord: list, zcorn: list, actnum: list = None) -> Grid:
"""
Create grid from GRDECL format data.
Args:
nx, ny, nz (int): Grid dimensions
coord (list): Coordinate data
zcorn (list): Z-corner data
actnum (list, optional): Active cell flags
Returns:
Grid: Generated grid
"""from resdata.grid import Grid
# Load grid
grid = Grid("SIMULATION.EGRID")
# Get basic information
nx, ny, nz = grid.get_cell_dims()
print(f"Grid dimensions: {nx} x {ny} x {nz}")
print(f"Total cells: {nx * ny * nz}")
print(f"Active cells: {grid.num_active()}")
# Get cell properties
i, j, k = 10, 15, 5 # Cell indices
if grid.cell_valid(i, j, k):
volume = grid.get_cell_volume(i, j, k)
x, y, z = grid.get_xyz(i, j, k)
depth = grid.depth(i, j, k)
print(f"Cell ({i},{j},{k}):")
print(f" Volume: {volume:.2f} m³")
print(f" Center: ({x:.1f}, {y:.1f}, {z:.1f})")
print(f" Depth: {depth:.1f} m")from resdata.grid import Grid, ResdataRegion
from resdata.resfile import ResdataFile
# Load grid and data
grid = Grid("SIMULATION.EGRID")
restart = ResdataFile("SIMULATION.UNRST")
# Get keyword data
porosity = restart.get_kw("PORO")
pressure = restart.get_kw("PRESSURE")
# Create region selector
region = ResdataRegion(grid, preselect=True)
# Select high porosity cells
region.select_more(porosity, 0.2) # Porosity > 20%
# Further refine: high pressure in high porosity zone
region.select_more(pressure, 200.0) # Pressure > 200 bar
# Select a specific layer
layer_region = ResdataRegion(grid, preselect=False)
layer_region.select_kslice(10, 10) # Just layer 10
print(f"High porosity, high pressure cells: {region.active_size()}")
print(f"Layer 10 cells: {layer_region.active_size()}")from resdata.grid import Grid
from resdata.resfile import ResdataFile
import numpy as np
# Load data
grid = Grid("SIMULATION.EGRID")
restart = ResdataFile("SIMULATION.UNRST")
# Get pressure data
pressure_kw = restart.get_kw("PRESSURE")
pressure_data = pressure_kw.numpy_copy()
# Create arrays for analysis
nx, ny, nz = grid.get_cell_dims()
avg_pressure_by_layer = np.zeros(nz)
# Calculate average pressure by layer
for k in range(nz):
layer_pressures = []
for j in range(ny):
for i in range(nx):
if grid.cell_valid(i, j, k):
active_idx = grid.active_index(i, j, k)
if active_idx >= 0:
layer_pressures.append(pressure_data[active_idx])
if layer_pressures:
avg_pressure_by_layer[k] = np.mean(layer_pressures)
print("Average pressure by layer:")
for k, avg_p in enumerate(avg_pressure_by_layer):
if avg_p > 0:
print(f" Layer {k+1}: {avg_p:.1f} bar")from resdata.grid import GridGenerator
# Create simple rectangular grid
grid = GridGenerator.create_rectangular(
nx=10, ny=10, nz=5, # 10x10x5 cells
dx=100.0, dy=100.0, dz=10.0 # 100m x 100m x 10m cells
)
print(f"Generated grid: {grid.get_cell_dims()}")
print(f"Active cells: {grid.num_active()}")
# Check cell properties
volume = grid.get_cell_volume(5, 5, 2) # Middle cell
x, y, z = grid.get_xyz(5, 5, 2)
print(f"Cell (5,5,2): volume={volume:.0f} m³, center=({x:.0f},{y:.0f},{z:.0f})")from resdata.grid import Grid
grid = Grid("SIMULATION.EGRID")
# Find cell containing a point
x, y, z = 1000.0, 2000.0, -1500.0 # World coordinates
cell_ijk = grid.grid_value(x, y, z)
if cell_ijk:
i, j, k = cell_ijk
print(f"Point ({x}, {y}, {z}) is in cell ({i}, {j}, {k})")
# Get cell properties
if grid.cell_valid(i, j, k):
volume = grid.get_cell_volume(i, j, k)
center_x, center_y, center_z = grid.get_xyz(i, j, k)
print(f"Cell center: ({center_x:.1f}, {center_y:.1f}, {center_z:.1f})")
print(f"Cell volume: {volume:.2f} m³")
else:
print(f"Point ({x}, {y}, {z}) is outside the grid")# Grid dimensions
GridDimensions = tuple[int, int, int] # (nx, ny, nz)
# Cell coordinates
CellIndices = tuple[int, int, int] # (i, j, k)
WorldCoordinates = tuple[float, float, float] # (x, y, z)
# Cell properties
CellCorners = tuple[tuple[float, float, float], ...] # 8 corner coordinatesInstall with Tessl CLI
npx tessl i tessl/pypi-resdata