# PetroScript > PetroScript writes reservoir simulation models in Python: a model is one typed, frozen value (`ps.Model`), checked at every step, compiled to a deck for OPM Flow or JutulDarcy, run, and read back by name. It also loads existing Eclipse/Flow decks so they can be edited with the same verbs and written back. Look things up before guessing: `ps.help(name)` answers for a class (`ps.help(ps.Produce)`), a deck keyword (`ps.help("WCONPROD")`), a summary mnemonic (`ps.help("WBHP")`), a result name or an issue code, and `ps.help()` lists everything. A wrong name is refused with the nearest right one. The fastest start is a worked example: `examples/waterflood.py` (from scratch) or `examples/load_and_edit.py` (a deck you have). How it works: - `import petroscript as ps`. The model's parts take keywords: `ps.Model(name="X", ...)`, `ps.Well(name="P1", i=1, j=1)`; `ps.Model("X")` is a `TypeError`. `ps.Event(date, *changes)`, `ps.Every(30, 31)` and `ps.Quantity(250.0, "bar")` take their values in order. - Nothing changes in place. `m.at(date, *changes)`, `m.set(address, value)` (or `m.set({address: value, ...})`), `m.until(date)`, `m.apply(edit)` and `x.replace(**fields)` each return a new, checked value: keep it (`m = m.at(...)`). Facts that only hold together go in one `m.set({...})`: a new well, its perforation and its control. - Everything a model states has an address: `wells/P1`, `rock/porosity`, `fluid/1/oil`, `initial/1/datum_pressure`, `schedule/2025-01-01/P1/control`, `summary/wells/P1`. `m.get`, `m.set`, `m.find("schedule/*/P1/*")` and `m.diff(other)` work by address. - A well is an identity (`ps.Well`); what it does is dated: `ps.Perforate(well=, k=(k1, k2), diameter=)`, `ps.Produce(well=, oil_rate=..., bhp_limit=...)`, `ps.Inject(well=, phase="water", rate=..., bhp_limit=...)`, in a `ps.Event` or `m.at`. The one quantity given bare is the target, every other is a `*_limit`. `ps.Adjust` changes one target and keeps the rest; a new `ps.Produce` replaces the whole control. - Kinds live in the namespace of their keyword: `grid=ps.grid.Cartesian(...)`, `fluid=ps.fluid.OilWater|GasWater|BlackOil(...)` with `ps.pvt` tables, `relperm=[ps.relperm.OilWater(...)]`, `initial=ps.initial.Equilibrium(...)`. Tables are named columns (lists or arrays). - Results are asked for by name in `ps.Summary(field=[...], wells={"*": [...]})` and read by the same name: `run.field("oil_production_rate")`, `run.wells("bottom_hole_pressure")` (polars frames with date, value, unit). Flow mnemonics (`FOPR`) are refused with their name. - Units: every bare number is in the model's unit system, `units="metric"` (m, barsa, sm3/day, sm3/sm3, kg/m3) or `"field"` (ft, psia, stb/day, Mscf/day, **Mscf/stb** for a gas-oil ratio, lb/ft3). A water cut is a fraction. Rule thresholds and lift-table axes follow the same rule. Install: `pip install .` from a clone (Python 3.10+; numpy, polars, pydantic). Building, checking, compiling and writing need nothing else. Running needs an engine, each found by one environment variable: OPM Flow (`PETROSCRIPT_FLOW`, or `flow` on PATH), JutulDarcy (`PETROSCRIPT_JUTULDARCY`), NeqSim for lift tables (`PETROSCRIPT_NEQSIM`). `ps.simulators()` lists the deck simulators found (never NeqSim, which makes tables, not runs). A first model, checked, compiled and written (FIELD units): ```python import petroscript as ps m = ps.Model( name="FIRST", units="field", start="2025-01-01", grid=ps.grid.Cartesian(shape=(5, 5, 1), dx=200.0, dy=200.0, dz=30.0, top=8000.0), rock=ps.Rock(porosity=0.2, permeability=100.0, vertical_ratio=0.1, compressibility=4.0e-6, reference_pressure=3500.0), fluid=ps.fluid.OilWater( oil=ps.pvt.DeadOil(pressure=[1000.0, 4000.0], oil_fvf=[1.10, 1.08], oil_viscosity=[2.0, 2.2]), water=ps.pvt.Water(reference_pressure=3500.0, fvf=1.01, 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.2, 0.8], krw=[0.0, 0.5], krow=[1.0, 0.0], pcow=[0.0, 0.0])], initial=ps.initial.Equilibrium(datum_depth=8000.0, datum_pressure=3500.0, oil_water_contact=8100.0), wells=[ps.Well(name="PROD", i=3, j=3), ps.Well(name="INJ", i=1, j=1)], schedule=ps.Schedule(end="2027-01-01", report="monthly", events=[ps.Event("2025-01-01", ps.Perforate(well="PROD", k=(1, 1), diameter=0.5), ps.Perforate(well="INJ", k=(1, 1), diameter=0.5), ps.Produce(well="PROD", oil_rate=200.0, bhp_limit=1500.0), ps.Inject(well="INJ", phase="water", rate=200.0, bhp_limit=5000.0))]), summary=ps.Summary(field=["oil_production_rate", "water_cut"], wells={"*": ["bottom_hole_pressure"]}), ) print([str(issue) for issue in m.check()]) # PetroScript's own checks, no simulator needed deck = ps.compile(m) # "jutuldarcy" as a second argument for JutulDarcy deck.write("first") # first/FIRST.DATA; deck.run(folder=...) runs it ``` The five refusals newcomers meet most, and their fixes: 1. `TypeError: ... takes 1 positional argument but 2 were given`: a name passed by position. Use `name=`. 2. `[well.control.missing] Well 'P2' has no reservoir control`: the well was set before its control. Put `"wells/P2"`, `"schedule//P2/perforation/1"` and `"schedule//P2/control"` in one `m.set({...})`. 3. `AttributeError` on an invented name or method (`ps.Fluid`, `m.compile()`, `summary.add`): `ps.compile(m)`, `ps.load(path)` and `ps.results.open(folder)` are functions of `ps`; values are frozen, so add a summary request with `m.set("summary/wells/*", [*m.summary.wells.get("*", ()), "water_cut"])`. `m.summary.replace(wells=...)` drops every well's other requests. 4. `[results.unknown] 'FOPR' is a Flow mnemonic`: read results by PetroScript's names (`ps.help("FOPR")` gives `oil_production_rate`). 5. A rule or table that runs but is wrong by 1000: a FIELD gas-oil ratio is Mscf/stb (800 scf/stb is `0.8`). Check the unit system with `m.units` and `m.check()`. ## Start - [README](https://petroscript.blauweiss.ai/docs.md): what PetroScript is, install, a first model, where to learn - [Install and engines](https://petroscript.blauweiss.ai/docs/start/install.md): pip install, and how OPM Flow, JutulDarcy and NeqSim are found - [Quickstart](https://petroscript.blauweiss.ai/docs/quickstart.md): one gas field built, checked, run, read and edited, every block run - [Examples](https://petroscript.blauweiss.ai/docs/examples.md): small scripts that each run end to end, one per topic ## How-to - [Build a model from a word problem](https://petroscript.blauweiss.ai/docs/how-to/from-a-word-problem.md): the questions to answer, then each part by its keyword - [Edit a loaded deck](https://petroscript.blauweiss.ai/docs/how-to/edit-a-loaded-deck.md): add a well, change a control, add results, edit the grid - [Add production history and a forecast](https://petroscript.blauweiss.ai/docs/how-to/history.md): observed rates from a frame, then a forecast - [Build the grid and the rock](https://petroscript.blauweiss.ai/docs/how-to/grid-and-rock.md): per-layer and per-cell values, dip, inactive cells, grid edits - [Describe the fluids](https://petroscript.blauweiss.ai/docs/how-to/describe-the-fluids.md): dead oil, live oil with gas, wet gas, gas-water, compositional - [Control wells, groups and networks](https://petroscript.blauweiss.ai/docs/how-to/wells-and-controls.md): targets and limits of each kind of well - [Add a rule that fires during the run](https://petroscript.blauweiss.ai/docs/how-to/rules.md): conditions, thresholds in the model's units, runs - [Use regions and faults](https://petroscript.blauweiss.ai/docs/how-to/regions-and-faults.md): saturation, PVT and initial-state regions; faults - [Add an aquifer](https://petroscript.blauweiss.ai/docs/how-to/aquifers.md): Fetkovich and Carter-Tracy aquifers on a model - [Run a thermal model](https://petroscript.blauweiss.ai/docs/how-to/thermal.md): the thermal package and an injection temperature - [Read a finished run](https://petroscript.blauweiss.ai/docs/how-to/read-a-finished-run.md): time series and cell arrays by name - [Run in JutulDarcy](https://petroscript.blauweiss.ai/docs/how-to/run-in-jutuldarcy.md): the second simulator and what it refuses - [Generate a lift table with NeqSim](https://petroscript.blauweiss.ai/docs/how-to/lift-table-neqsim.md): a complete ps.lift.generate call and its refusals ## Concepts and errors - Learn PetroScript: the docs in the order to read them - [One value, checked at every step](https://petroscript.blauweiss.ai/docs/concepts/one-value.md): frozen models, checked verbs, addresses, what is never filled in - [Units and bare numbers](https://petroscript.blauweiss.ai/docs/concepts/units.md): each quantity's unit in FIELD, METRIC and LAB - [Errors and what to do](https://petroscript.blauweiss.ai/docs/troubleshooting/errors.md): each common refusal with its fix - [How PetroScript works](https://petroscript.blauweiss.ai/docs/concepts/mental-model.md): the mental model and the engineer's pipeline ## Reference - [API map](https://petroscript.blauweiss.ai/docs/reference/api-map.md): every public name with its signature, on one page - [Reference pages by topic](https://petroscript.blauweiss.ai/docs/reference.md): grid, rock, fluids, relative permeability, wells, summary and more - [Keyword crosswalk](https://petroscript.blauweiss.ai/docs/reference/keywords.md): each deck keyword and the PetroScript name that writes it ## Optional - [llms-full.txt](https://petroscript.blauweiss.ai/llms-full.txt): this file followed by the pages above, in one text - [The book](https://petroscript.blauweiss.ai/docs/book.md): chapters plus the full API reference from the docstrings - [SPE1 walkthrough](https://petroscript.blauweiss.ai/docs/how-to/spe1-walkthrough.md): a benchmark deck stated as a model - [Architecture](https://petroscript.blauweiss.ai/docs/contributing/architecture.md): how the compiler and the engines are built