Skip to content

Overview

PetroScript writes reservoir simulation models in Python. You state the grid, rock, fluids, wells and schedule by keyword; PetroScript checks the model at every step, compiles it to a deck for OPM Flow or JutulDarcy, runs it, and reads the results back by name. It also loads the decks you already have, so you can edit them with the same verbs and write them back.

Look anything up with ps.help(name): a class (ps.help(ps.Produce)), a deck keyword (ps.help("WCONPROD")), a summary mnemonic (ps.help("FOPR")), a result name or an issue code. A wrong name is refused with the nearest right one.

pip install . # from a clone of this repository; Python 3.10 or newer

PetroScript builds, checks, compiles and writes decks on its own. To run them it uses OPM Flow (PETROSCRIPT_FLOW, or flow on PATH) or JutulDarcy (PETROSCRIPT_JUTULDARCY); NeqSim makes lift tables (PETROSCRIPT_NEQSIM). See Install and engines.

A waterflood in a 3 x 3 box, in FIELD units (ft, psia, stb/day). The model’s parts take keywords, and every bare number is in the model’s units:

import petroscript as ps
fluid = ps.fluid.OilWater(
oil=ps.pvt.DeadOil(pressure=[14.7, 3500.0], oil_fvf=[1.06, 1.0], oil_viscosity=[1.0, 1.2]),
water=ps.pvt.Water(
reference_pressure=3000.0,
fvf=1.0,
compressibility=3.0e-6,
viscosity=0.5,
viscosibility=0.0,
),
oil_surface_density=53.0,
water_surface_density=64.0,
)
relperm = ps.relperm.OilWater(
sw=[0.20, 0.45, 0.80], krw=[0.0, 0.2, 1.0], krow=[1.0, 0.6, 0.0], pcow=[0.0, 0.0, 0.0]
)
model = ps.Model(
name="WATERFLOOD",
units="field",
start="2020-01-01",
grid=ps.grid.Cartesian(shape=(3, 3, 1), dx=100.0, dy=100.0, dz=20.0, top=2500.0),
rock=ps.Rock(porosity=0.25, permeability=100.0, vertical_ratio=1.0,
compressibility=3.0e-6, reference_pressure=14.7), # 1/psi at psia
fluid=fluid,
relperm=[relperm],
initial=ps.initial.Equilibrium(
datum_depth=2510.0, datum_pressure=3000.0, oil_water_contact=2600.0, gas_oil_contact=0.0
),
wells=[
ps.Well(name="PROD1", i=3, j=3, bhp_reference_depth=2510.0),
ps.Well(name="INJ1", i=1, j=1, bhp_reference_depth=2510.0),
],
schedule=ps.Schedule(
report=[ps.Every(30.0)],
events=[
ps.Event(
"2020-01-01",
ps.Perforate(well="PROD1", k=(1, 1), diameter=0.3),
ps.Perforate(well="INJ1", k=(1, 1), diameter=0.3),
ps.Produce(well="PROD1", oil_rate=220.0, bhp_limit=1200.0),
ps.Inject(well="INJ1", phase="water", rate=360.0, bhp_limit=4500.0),
)
],
),
summary=ps.Summary(
field=["oil_production_rate"], wells={"PROD1": ["bottom_hole_pressure"]}
),
)
for issue in model.check(): # PetroScript's own checks, before any simulator
print(issue.severity, issue.address, issue)
deck = ps.compile(model) # a deck for OPM Flow, in memory
deck.write(".") # ./WATERFLOOD.DATA

With Flow installed, run = deck.run(folder="run") runs it and run.field("oil_production_rate") reads a result as a table of date, value and unit. A model that contradicts itself is refused with ps.ModelError, naming the address and the fix: a well with no control, a missing fluid, a date past the end.

To start from a deck you have: m = ps.load("CASE.DATA"), then m.at(date, ...), m.set(...) and ps.compile(m). Edit a loaded deck shows how.

PetroScript is licensed under the Apache License 2.0. © 2026 Blauweiss EDV LLC. PetroScript by Blauweiss AI.For AI agents: llms.txt