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.
Install
Section titled “Install”pip install . # from a clone of this repository; Python 3.10 or newerPetroScript 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 first model
Section titled “A first model”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 memorydeck.write(".") # ./WATERFLOOD.DATAWith 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